├── .gitignore ├── CONTRIBUTING.md ├── FOSMessageBundle.php ├── Tests ├── Functional │ ├── Entity │ │ ├── Thread.php │ │ ├── Message.php │ │ ├── UserProvider.php │ │ └── User.php │ ├── WebTestCase.php │ ├── FunctionalTest.php │ ├── Form │ │ └── UserToUsernameTransformer.php │ ├── EntityManager │ │ ├── MessageManager.php │ │ └── ThreadManager.php │ └── TestKernel.php └── Model │ └── ThreadTest.php ├── Document ├── ThreadMetadata.php ├── MessageMetadata.php └── Message.php ├── Resources ├── views │ ├── Message │ │ ├── sent.html.twig │ │ ├── deleted.html.twig │ │ ├── newThread.html.twig │ │ ├── inbox.html.twig │ │ ├── search.html.twig │ │ ├── thread.html.twig │ │ └── threads_list.html.twig │ └── layout.html.twig ├── doc │ ├── 05-permissions.md │ ├── 04-spam-detection.md │ ├── 03-templating.md │ ├── 90-multiple-recipients.md │ ├── 00-index.md │ ├── 90-sending-a-message-programatically.md │ ├── 06-events.md │ ├── 99-config-reference.md │ ├── 01-installation.md │ ├── 02-basic-usage.md │ ├── 01b-odm-models.md │ └── 01a-orm-models.md ├── translations │ ├── validators.ar.yml │ ├── validators.ru.yml │ ├── validators.en.yml │ ├── validators.pl.yml │ ├── validators.pt_BR.yml │ ├── FOSMessageBundle.sv.yml │ ├── validators.fr.yml │ ├── FOSMessageBundle.it.yml │ ├── FOSMessageBundle.fa.yml │ ├── FOSMessageBundle.nl.yml │ ├── FOSMessageBundle.sk.yml │ ├── FOSMessageBundle.sl.yml │ ├── FOSMessageBundle.en.yml │ ├── FOSMessageBundle.ar.yml │ ├── FOSMessageBundle.es.yml │ ├── FOSMessageBundle.pt_BR.yml │ ├── FOSMessageBundle.cs.yml │ ├── FOSMessageBundle.fr.yml │ ├── FOSMessageBundle.pl.yml │ ├── FOSMessageBundle.de.yml │ └── FOSMessageBundle.ru.yml ├── config │ ├── doctrine │ │ ├── MessageMetadata.mongodb.xml │ │ ├── MessageMetadata.orm.xml │ │ ├── ThreadMetadata.mongodb.xml │ │ ├── ThreadMetadata.orm.xml │ │ ├── Thread.orm.xml │ │ ├── Message.orm.xml │ │ ├── Message.mongodb.xml │ │ └── Thread.mongodb.xml │ ├── spam_detection.xml │ ├── orm.xml │ ├── mongodb.xml │ ├── validator.xml │ ├── routing.xml │ ├── form.xml │ ├── validation.xml │ └── config.xml └── meta │ └── LICENSE ├── MessageBuilder ├── ReplyMessageBuilder.php ├── NewThreadMessageBuilder.php └── AbstractMessageBuilder.php ├── Search ├── QueryFactoryInterface.php ├── FinderInterface.php ├── Query.php ├── Finder.php └── QueryFactory.php ├── .php_cs.dist ├── SpamDetection ├── NoopSpamDetector.php ├── SpamDetectorInterface.php └── AkismetSpamDetector.php ├── Sender ├── SenderInterface.php └── Sender.php ├── phpunit ├── Validator ├── Spam.php ├── Authorization.php ├── SelfRecipient.php ├── ReplyAuthorization.php ├── SpamValidator.php ├── AuthorizationValidator.php ├── SelfRecipientValidator.php └── ReplyAuthorizationValidator.php ├── FormModel ├── AbstractMessage.php ├── ReplyMessage.php ├── NewThreadMessage.php └── NewThreadMultipleMessage.php ├── Security ├── ParticipantProviderInterface.php ├── AuthorizerInterface.php ├── Authorizer.php └── ParticipantProvider.php ├── Model ├── ParticipantInterface.php ├── ReadableInterface.php ├── MessageMetadata.php ├── MessageInterface.php ├── ThreadMetadata.php ├── ThreadInterface.php └── Message.php ├── Event ├── ThreadEvent.php ├── ReadableEvent.php ├── MessageEvent.php └── FOSMessageEvents.php ├── Deleter ├── DeleterInterface.php └── Deleter.php ├── FormFactory ├── NewThreadMessageFormFactory.php ├── ReplyMessageFormFactory.php └── AbstractMessageFormFactory.php ├── Reader ├── ReaderInterface.php └── Reader.php ├── ModelManager ├── MessageManager.php ├── ThreadManager.php ├── ReadableManagerInterface.php └── MessageManagerInterface.php ├── Entity ├── Message.php ├── MessageMetadata.php ├── ThreadMetadata.php └── Thread.php ├── Composer ├── ComposerInterface.php └── Composer.php ├── phpunit.xml.dist ├── .travis.yml ├── .github └── workflows │ └── ci.yml ├── FormHandler ├── ReplyMessageFormHandler.php ├── NewThreadMessageFormHandler.php ├── NewThreadMultipleMessageFormHandler.php └── AbstractMessageFormHandler.php ├── Provider ├── ProviderInterface.php └── Provider.php ├── UPGRADING.md ├── Util └── LegacyFormHelper.php ├── FormType ├── NewThreadMultipleMessageFormType.php ├── ReplyMessageFormType.php ├── RecipientsType.php └── NewThreadMessageFormType.php ├── README.md ├── composer.json ├── DataTransformer └── RecipientsDataTransformer.php ├── Twig └── Extension │ └── MessageExtension.php └── DependencyInjection └── Configuration.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | phpunit.xml 3 | .phpunit 4 | vendor 5 | .php_cs.cache 6 | .php_cs 7 | Tests/Functional/cache 8 | Tests/Functional/logs 9 | var 10 | .phpunit.result.cache -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | FOSMessageBundle is an open source project. When contributing please follow the Symfony2 coding standards and provide 5 | test cases where possible. 6 | -------------------------------------------------------------------------------- /FOSMessageBundle.php: -------------------------------------------------------------------------------- 1 | {% trans from 'FOSMessageBundle' %}sent{% endtrans %} 6 | 7 | {% include '@FOSMessage/Message/threads_list.html.twig' with {'threads': threads} %} 8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /Resources/views/Message/deleted.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@FOSMessage/layout.html.twig' %} 2 | 3 | {% block fos_message_content %} 4 | 5 |

{% trans from 'FOSMessageBundle' %}deleted{% endtrans %}

6 | 7 | {% include '@FOSMessage/Message/threads_list.html.twig' with {'threads': threads} %} 8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /MessageBuilder/ReplyMessageBuilder.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ReplyMessageBuilder extends AbstractMessageBuilder 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /Search/QueryFactoryInterface.php: -------------------------------------------------------------------------------- 1 | in(__DIR__) 5 | ->exclude('Tests/Functional/cache') 6 | ; 7 | 8 | return PhpCsFixer\Config::create() 9 | ->setRules(array( 10 | '@Symfony' => true, 11 | 'ordered_imports' => true, 12 | 'phpdoc_order' => true, 13 | )) 14 | ->setFinder($finder) 15 | ; 16 | -------------------------------------------------------------------------------- /Tests/Functional/WebTestCase.php: -------------------------------------------------------------------------------- 1 | {% trans from 'FOSMessageBundle' %}send_new{% endtrans %} 6 | 7 |
8 | {{ form_widget(form) }} 9 | 10 | 11 |
12 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /SpamDetection/NoopSpamDetector.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface SenderInterface 13 | { 14 | /** 15 | * Sends the given message. 16 | */ 17 | public function send(MessageInterface $message); 18 | } 19 | -------------------------------------------------------------------------------- /phpunit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | {% trans from 'FOSMessageBundle' %}inbox{% endtrans %} 6 | 7 | {% trans from 'FOSMessageBundle' %}send_new{% endtrans %} 8 | 9 | {% include '@FOSMessage/Message/threads_list.html.twig' with {'threads': threads} %} 10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /Resources/views/Message/search.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@FOSMessage/layout.html.twig' %} 2 | 3 | {% block fos_message_content %} 4 | 5 |

{% trans from 'FOSMessageBundle' %}search{% endtrans %}

6 | 7 |

{% transchoice threads|length with {'%num%': threads|length } from 'FOSMessageBundle' %} 8 | threads_found 9 | {% endtranschoice %}

10 | 11 | {% include '@FOSMessage/Message/threads_list.html.twig' with {'threads': threads} %} 12 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /Validator/Spam.php: -------------------------------------------------------------------------------- 1 | body; 18 | } 19 | 20 | /** 21 | * @param string 22 | */ 23 | public function setBody($body) 24 | { 25 | $this->body = $body; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Security/ParticipantProviderInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface ParticipantProviderInterface 13 | { 14 | /** 15 | * Gets the current authenticated user. 16 | * 17 | * @return ParticipantInterface 18 | */ 19 | public function getAuthenticatedParticipant(); 20 | } 21 | -------------------------------------------------------------------------------- /Validator/Authorization.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface ParticipantInterface 13 | { 14 | /** 15 | * Gets the unique identifier of the participant. 16 | * 17 | * @return mixed 18 | */ 19 | public function getId(); 20 | } 21 | -------------------------------------------------------------------------------- /Tests/Functional/FunctionalTest.php: -------------------------------------------------------------------------------- 1 | 'guilhem', 11 | 'PHP_AUTH_PW' => 'pass', 12 | )); 13 | $crawler = $client->request('GET', '/sent'); 14 | 15 | $response = $client->getResponse(); 16 | $this->assertEquals(200, $response->getStatusCode()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Validator/ReplyAuthorization.php: -------------------------------------------------------------------------------- 1 | thread = $thread; 18 | } 19 | 20 | /** 21 | * @return ThreadInterface 22 | */ 23 | public function getThread() 24 | { 25 | return $this->thread; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /FormModel/ReplyMessage.php: -------------------------------------------------------------------------------- 1 | thread; 22 | } 23 | 24 | public function setThread(ThreadInterface $thread) 25 | { 26 | $this->thread = $thread; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Event/ReadableEvent.php: -------------------------------------------------------------------------------- 1 | readable = $readable; 18 | } 19 | 20 | /** 21 | * @return ReadableInterface 22 | */ 23 | public function getReadable() 24 | { 25 | return $this->readable; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Model/ReadableInterface.php: -------------------------------------------------------------------------------- 1 | getThread()); 17 | 18 | $this->message = $message; 19 | } 20 | 21 | /** 22 | * @return MessageInterface 23 | */ 24 | public function getMessage() 25 | { 26 | return $this->message; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Resources/doc/05-permissions.md: -------------------------------------------------------------------------------- 1 | Messaging permissions 2 | ====================== 3 | 4 | The default permissions authorizer service will authenticate a user if they're a 5 | participant of the thread and is very permissive by default. 6 | 7 | You can implement your own permissions service to replace the built in service and tell 8 | FOSMessageBundle about it: 9 | 10 | ```yaml 11 | # app/config/config.yml 12 | 13 | fos_message: 14 | authorizer: app.authorizer 15 | ``` 16 | 17 | Any such service must implement `FOS\MessageBundle\Security\AuthorizerInterface`. 18 | 19 | [Return to the documentation index](00-index.md) 20 | -------------------------------------------------------------------------------- /SpamDetection/SpamDetectorInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface SpamDetectorInterface 13 | { 14 | /** 15 | * Tells whether or not a new message looks like spam. 16 | * 17 | * @param NewThreadMessage $message 18 | * 19 | * @return bool true if it is spam, false otherwise 20 | */ 21 | public function isSpam(NewThreadMessage $message); 22 | } 23 | -------------------------------------------------------------------------------- /Resources/translations/validators.ar.yml: -------------------------------------------------------------------------------- 1 | fos_message: 2 | recipient: 3 | blank: لم يتم تحديد إي مستقبل 4 | recipients: 5 | blank: لم يتم تحديد المستقبلين 6 | subject: 7 | blank: رجاء ادخل العنوان 8 | short: العنوان قصير جدا|العنوان قصير جدا 9 | long: العنوان طويل جدا|العنوان طويل جدا 10 | body: 11 | blank: رجاء ادخل محتوى الرساله 12 | short: المحتوى قصير جدا 13 | spam: عذرا , لكن رسلاتك تبدو كرسالة مزعجة 14 | not_authorized: إنك لا تملك التصريح اللازم لإرسال هذه الرساله 15 | reply_not_authorized: إنك لا تملك التصريح اللازم لرد على هذه الرساله 16 | self_recipient: لا يمكنك إرسال رساله لنفسك 17 | 18 | -------------------------------------------------------------------------------- /Deleter/DeleterInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface DeleterInterface 13 | { 14 | /** 15 | * Marks the thread as deleted by the current authenticated user. 16 | */ 17 | public function markAsDeleted(ThreadInterface $thread); 18 | 19 | /** 20 | * Marks the thread as undeleted by the current authenticated user. 21 | */ 22 | public function markAsUndeleted(ThreadInterface $thread); 23 | } 24 | -------------------------------------------------------------------------------- /Resources/translations/validators.ru.yml: -------------------------------------------------------------------------------- 1 | fos_message: 2 | recipient: 3 | blank: Не указан адресат 4 | recipients: 5 | blank: Не указаны адресаты 6 | subject: 7 | blank: Укажите тему 8 | too_short: Название темы слишком короткое|Название темы слишком короткое 9 | too_long: Название темы слишком длинное|Название темы слишком длинное 10 | body: 11 | blank: Введите текст сообщения 12 | too_short: Текст сообщения слишком короткий 13 | spam: Сожалеем, но ваше сообщение похоже на спам 14 | not_authorized: Вы не можете отправить это сообщение 15 | self_recipient: Вы не можете отправить сообщение самому себе 16 | -------------------------------------------------------------------------------- /FormFactory/NewThreadMessageFormFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class NewThreadMessageFormFactory extends AbstractMessageFormFactory 13 | { 14 | /** 15 | * Creates a new thread message. 16 | * 17 | * @return FormInterface 18 | */ 19 | public function create() 20 | { 21 | return $this->formFactory->createNamed($this->formName, $this->formType, $this->createModelInstance()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Reader/ReaderInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface ReaderInterface 13 | { 14 | /** 15 | * Marks the readable as read by the current authenticated user. 16 | */ 17 | public function markAsRead(ReadableInterface $readable); 18 | 19 | /** 20 | * Marks the readable as unread by the current authenticated user. 21 | */ 22 | public function markAsUnread(ReadableInterface $readable); 23 | } 24 | -------------------------------------------------------------------------------- /Resources/doc/04-spam-detection.md: -------------------------------------------------------------------------------- 1 | Spam detection 2 | ============== 3 | 4 | Using Akismet 5 | ------------- 6 | 7 | Install AkismetBundle (https://github.com/ornicar/OrnicarAkismetBundle). 8 | 9 | Then, set the spam detector service accordingly:: 10 | 11 | ```yaml 12 | # app/config/config.yml 13 | 14 | fos_message: 15 | spam_detector: fos_message.akismet_spam_detector 16 | ``` 17 | 18 | Other strategies 19 | ---------------- 20 | 21 | You can use any spam detector service, including one of your own, provided the 22 | class implements ``FOS\MessageBundle\SpamDetection\SpamDetectorInterface``. 23 | 24 | [Return to the documentation index](00-index.md) 25 | -------------------------------------------------------------------------------- /Resources/translations/validators.en.yml: -------------------------------------------------------------------------------- 1 | fos_message: 2 | recipient: 3 | blank: No recipient specified 4 | recipients: 5 | blank: No recipients specified 6 | subject: 7 | blank: Please enter a subject 8 | short: The subject is too short|The subject is too short 9 | long: The subject is too long|The subject is too long 10 | body: 11 | blank: Please enter a body 12 | short: The body is too short 13 | spam: Sorry, your message looks like spam 14 | not_authorized: You are not allowed to send this message 15 | reply_not_authorized: You are not allowed to reply to this message 16 | self_recipient: You cannot send a message to yourself 17 | 18 | -------------------------------------------------------------------------------- /Resources/translations/validators.pl.yml: -------------------------------------------------------------------------------- 1 | fos_message: 2 | recipient: 3 | blank: Nie zdefiniowano odbiorcy 4 | recipients: 5 | blank: Nie zdefiniowano żadnych odbiorców 6 | subject: 7 | blank: Wprowadź temat 8 | short: Temat jest za krótki|Temat jest za krótki 9 | long: Temat jest za długi|Temat jest za długi 10 | body: 11 | blank: Wprowadź treść 12 | short: Treść jest za krótka 13 | spam: Przykro mi, Twoja wiadomość wygląda jak spam 14 | not_authorized: Nie masz uprawnień, aby wysłać tą wiadomość 15 | reply_not_authorized: Nie masz uprawnień, aby odpowiadać na tą wiadomość 16 | self_recipient: Nie możesz wysyłać wiadomości do siebie 17 | 18 | -------------------------------------------------------------------------------- /ModelManager/MessageManager.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | abstract class MessageManager implements MessageManagerInterface 14 | { 15 | /** 16 | * Creates an empty message instance. 17 | * 18 | * @return MessageInterface 19 | */ 20 | public function createMessage() 21 | { 22 | $class = $this->getClass(); 23 | 24 | return new $class(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ModelManager/ThreadManager.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | abstract class ThreadManager implements ThreadManagerInterface 14 | { 15 | /** 16 | * Creates an empty comment thread instance. 17 | * 18 | * @return ThreadInterface 19 | */ 20 | public function createThread() 21 | { 22 | $class = $this->getClass(); 23 | 24 | return new $class(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Resources/translations/validators.pt_BR.yml: -------------------------------------------------------------------------------- 1 | fos_message: 2 | recipient: 3 | blank: Nenhum destinatário especificado 4 | recipients: 5 | blank: Nenhum destinatário especificado 6 | subject: 7 | blank: Insira um assunto 8 | short: O assunto é muito curto|O assunto é muito curto 9 | long: O assunto é muito longo|O assunto é muito longo 10 | body: 11 | blank: Insira uma mensagem 12 | short: A mensagem é muito curta 13 | spam: Desculpe, sua mensagem parece ser um spam 14 | not_authorized: Você não tem permissão para enviar esta mensagem 15 | reply_not_authorized: Você não tem permissão para responder a esta mensagem 16 | self_recipient: Não é possível enviar uma mensagem para si mesmo 17 | 18 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.sv.yml: -------------------------------------------------------------------------------- 1 | messenger: Meddelanden 2 | inbox: Inkorg 3 | sent: Skickat 4 | send_new: Skriv nytt meddelande 5 | search: Sök 6 | threads_found: "%num% tråd hittad | %num% trådar hittade" 7 | 8 | message_info: "Av %sender%, den %date%" 9 | reply: Svara 10 | 11 | subject: Ämne 12 | starter: Avsändare 13 | startdate: Påbörjad den 14 | messages: Meddelanden 15 | last_message: Senaste meddelande 16 | actions: Åtgärder 17 | new: Ny 18 | goto_last: Gå till senaste meddelande 19 | on: "den %date%" 20 | by: "av %sender%" 21 | no_thread: Det finns inga trådar 22 | delete: Ta bort 23 | -------------------------------------------------------------------------------- /Entity/Message.php: -------------------------------------------------------------------------------- 1 | metadata; 19 | } 20 | 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function addMetadata(ModelMessageMetadata $meta) 25 | { 26 | $meta->setMessage($this); 27 | parent::addMetadata($meta); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/Functional/Entity/UserProvider.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface ComposerInterface 14 | { 15 | /** 16 | * Starts composing a message, starting a new thread. 17 | * 18 | * @return AbstractMessageBuilder 19 | */ 20 | public function newThread(); 21 | 22 | /** 23 | * Starts composing a message in a reply to a thread. 24 | * 25 | * @return AbstractMessageBuilder 26 | */ 27 | public function reply(ThreadInterface $thread); 28 | } 29 | -------------------------------------------------------------------------------- /Resources/translations/validators.fr.yml: -------------------------------------------------------------------------------- 1 | fos_message: 2 | recipient: 3 | blank: Pas de destinataire spécifié 4 | recipients: 5 | blank: Pas de destinataires spécifié 6 | subject: 7 | blank: Vous devez entrer un sujet 8 | short: Le sujet est trop court|Le sujet est trop court 9 | long: Le sujet est trop long|Le sujet est trop long 10 | body: 11 | blank: Vous devez entrer un message 12 | short: Le message est trop court 13 | spam: Il semble que votre message soit un SPAM, il n'a pas été envoyé 14 | not_authorized: Vous n'avez pas le droit d'envoyer ce message 15 | reply_not_authorized: Vous n'avez pas de droit de répondre dans cette discussion 16 | self_recipient: Vous ne pouvez pas vous envoyer un message à vous-même 17 | -------------------------------------------------------------------------------- /Entity/MessageMetadata.php: -------------------------------------------------------------------------------- 1 | id; 19 | } 20 | 21 | /** 22 | * @return MessageInterface 23 | */ 24 | public function getMessage() 25 | { 26 | return $this->message; 27 | } 28 | 29 | public function setMessage(MessageInterface $message) 30 | { 31 | $this->message = $message; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.it.yml: -------------------------------------------------------------------------------- 1 | messenger: Messaggi 2 | inbox: Posta in arrivo 3 | sent: Posta inviata 4 | deleted: Cestino 5 | send_new: Scrivere un nuovo messaggio 6 | search: Cercare 7 | threads_found: "%num% conversazione trovata con | %num% conversazioni trovate con" 8 | 9 | message_info: "Di %sender%, il %date%" 10 | reply: Rispondere 11 | 12 | subject: Oggetto 13 | starter: Iniziato da 14 | startdate: Iniziato il 15 | messages: Messaggi 16 | last_message: Ultimo messaggio 17 | actions: Azioni 18 | new: Nuovo 19 | goto_last: Vedere l’ultimo messaggio 20 | on: "Il %date%" 21 | by: "Di %sender%" 22 | no_thread: Nessuna conversazione 23 | -------------------------------------------------------------------------------- /Tests/Functional/Entity/User.php: -------------------------------------------------------------------------------- 1 | id; 21 | } 22 | 23 | /** 24 | * @return ThreadInterface 25 | */ 26 | public function getThread() 27 | { 28 | return $this->thread; 29 | } 30 | 31 | public function setThread(ThreadInterface $thread) 32 | { 33 | $this->thread = $thread; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.fa.yml: -------------------------------------------------------------------------------- 1 | messenger: پیام‌رسان 2 | inbox: صندوق دریافتی 3 | sent: ارسالی 4 | deleted: حذف‌شده 5 | send_new: فرستادن یک پیام جدید 6 | search: جستجو 7 | threads_found: "%num% مورد یافت شد | %num% مورد یافت شد" 8 | 9 | message_info: توسط %sender%، در %date% 10 | reply: پاسخ 11 | 12 | subject: موضوع 13 | starter: شروع‌کننده 14 | startdate: تاریخ شروع 15 | messages: پیام‌ها 16 | last_message: آخرین پیام 17 | actions: عملیات‌ها 18 | new: جدید 19 | goto_last: برو به آخرین پیام 20 | on: در %date% 21 | by: توسط %sender% 22 | no_thread: هیچ مورد جدیدی برای نمایش وجود ندارد. 23 | delete: حذف 24 | undelete: از حذف درآوردن 25 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.nl.yml: -------------------------------------------------------------------------------- 1 | messenger: Messenger 2 | inbox: Ontvangen 3 | sent: Verzonden 4 | deleted: Verwijderd 5 | send_new: Verstuur een nieuw bericht 6 | search: Zoek 7 | threads_found: "%num% bericht gevonden met | %num% berichten gevonden met" 8 | 9 | message_info: "Door %sender%, op %date%" 10 | reply: Reageer 11 | 12 | subject: Onderwerp 13 | starter: Starter 14 | startdate: Start datum 15 | messages: Berichten 16 | last_message: Laatste bericht 17 | actions: Acties 18 | new: Nieuw 19 | goto_last: Ga naar laatste bericht 20 | on: "Op %date%" 21 | by: "Door %sender%" 22 | no_thread: Geen onderwerpen gevonden 23 | delete: Verwijder 24 | -------------------------------------------------------------------------------- /Resources/config/doctrine/MessageMetadata.mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.sk.yml: -------------------------------------------------------------------------------- 1 | messenger: Messenger 2 | inbox: Prijaté správy 3 | sent: Odoslané správy 4 | deleted: Vymazané správy 5 | send_new: Napísať správu 6 | search: Hľadať 7 | threads_found: "%num% Konverzácia nájdená s | %num% Konverzácií nájdených s" 8 | 9 | message_info: "Od %sender%, %date%" 10 | reply: Odpovedať 11 | 12 | subject: Predmet 13 | starter: Iniciátor 14 | startdate: Začaté 15 | messages: Počet správ 16 | last_message: Posledná správa 17 | actions: Akcie 18 | new: Nová 19 | goto_last: Ísť na poslednú správu 20 | on: "%date%" 21 | by: "od %sender%" 22 | no_thread: Neexistujú správy, ktoré by bolo možné zobraziť 23 | delete: Vymazať 24 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ./Tests 11 | 12 | 13 | 14 | 15 | 16 | ./ 17 | 18 | ./Resources 19 | ./Tests 20 | ./vendor 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Model/MessageMetadata.php: -------------------------------------------------------------------------------- 1 | participant; 16 | } 17 | 18 | public function setParticipant(ParticipantInterface $participant) 19 | { 20 | $this->participant = $participant; 21 | } 22 | 23 | /** 24 | * @return bool 25 | */ 26 | public function getIsRead() 27 | { 28 | return $this->isRead; 29 | } 30 | 31 | /** 32 | * @param bool $isRead 33 | */ 34 | public function setIsRead($isRead) 35 | { 36 | $this->isRead = (bool) $isRead; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Resources/doc/90-multiple-recipients.md: -------------------------------------------------------------------------------- 1 | Configuring multiple recipients support 2 | ======================================= 3 | 4 | Configure your application 5 | 6 | ```yaml 7 | # app/config/config.yml 8 | 9 | fos_message: 10 | db_driver: orm 11 | thread_class: AppBundle\Entity\Thread 12 | message_class: AppBundle\Entity\Message 13 | new_thread_form: 14 | type: FOS\MessageBundle\FormType\NewThreadMultipleMessageFormType 15 | handler: fos_message.new_thread_multiple_form.handler 16 | model: FOS\MessageBundle\FormModel\NewThreadMultipleMessage 17 | name: message 18 | ``` 19 | 20 | Currently multiple functionality is based on FOSUserBundle but you can define custom form type for 21 | multiple recipients and use your own recipients transformer. 22 | -------------------------------------------------------------------------------- /Search/FinderInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface FinderInterface 13 | { 14 | /** 15 | * Finds threads of a participant, matching a given query. 16 | * 17 | * @param Query $query 18 | * 19 | * @return ThreadInterface[] 20 | */ 21 | public function find(Query $query); 22 | 23 | /** 24 | * Finds threads of a participant, matching a given query. 25 | * 26 | * @param Query $query 27 | * 28 | * @return Builder a query builder suitable for pagination 29 | */ 30 | public function getQueryBuilder(Query $query); 31 | } 32 | -------------------------------------------------------------------------------- /Resources/doc/00-index.md: -------------------------------------------------------------------------------- 1 | Getting started with FOSMessageBundle 2 | ===================================== 3 | 4 | ## Installation 5 | 6 | Installation of FOSMessageBundle is handled through composer. 7 | 8 | [See the installation instructions](01-installation.md) 9 | 10 | ## Usage 11 | 12 | Below is a collection of documentation on how to use and interact with FOSMessageBundle. 13 | 14 | - [Basic Usage](02-basic-usage.md) 15 | - [Templating](03-templating.md) 16 | - [Spam Detection](04-spam-detection.md) 17 | - [Permissions](05-permissions.md) 18 | - [Bundle Events](06-events.md) 19 | - [Multiple Recipients](90-multiple-recipients.md) 20 | - [Sending a message programatically](90-sending-a-message-programatically.md) 21 | - [Configuration Reference](99-config-reference.md) 22 | - [Using other UserBundles](99-using-other-user-bundles.md) 23 | -------------------------------------------------------------------------------- /Resources/config/spam_detection.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.sl.yml: -------------------------------------------------------------------------------- 1 | messenger: Messenger 2 | inbox: Prejeto 3 | sent: Poslano 4 | deleted: Izbrisano 5 | send_new: Pošlji novo sporočilo 6 | search: Iskanje 7 | threads_found: "%num% najden pogovor | %num% najdena pogovora | %num% najdeni pogovori | %num% najdenih pogovorov" 8 | 9 | message_info: "Od %sender%, %date%" 10 | reply: Odgovori 11 | 12 | subject: Zadeva 13 | starter: Prvi avtor 14 | startdate: Začetni datum 15 | messages: Sporočila 16 | last_message: Zadnje sporočilo 17 | actions: Akcije 18 | new: Novo 19 | goto_last: Pojdi na zadnje sporočilo 20 | on: "%date%" 21 | by: "Od %sender%" 22 | no_thread: Ni pogovorov za prikaz 23 | delete: Izbriši 24 | undelete: Odbriši 25 | -------------------------------------------------------------------------------- /FormFactory/ReplyMessageFormFactory.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class ReplyMessageFormFactory extends AbstractMessageFormFactory 14 | { 15 | /** 16 | * Creates a reply message. 17 | * 18 | * @param ThreadInterface $thread the thread we answer to 19 | * 20 | * @return FormInterface 21 | */ 22 | public function create(ThreadInterface $thread) 23 | { 24 | $message = $this->createModelInstance(); 25 | $message->setThread($thread); 26 | 27 | return $this->formFactory->createNamed($this->formName, $this->formType, $message); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.en.yml: -------------------------------------------------------------------------------- 1 | messenger: Messenger 2 | inbox: Inbox 3 | sent: Sent 4 | deleted: Deleted 5 | send_new: Send a new message 6 | search: Search 7 | threads_found: "%num% thread found with | %num% threads found with" 8 | 9 | message_info: "By %sender%, on %date%" 10 | reply: Reply 11 | 12 | subject: Subject 13 | starter: Starter 14 | startdate: Start date 15 | messages: Messages 16 | last_message: Last message 17 | actions: Actions 18 | new: New 19 | goto_last: Go to last message 20 | on: "On %date%" 21 | by: "By %sender%" 22 | no_thread: There is no thread to show 23 | delete: Delete 24 | undelete: Undelete 25 | recipient: Recipient 26 | recipients: Recipients 27 | body: Body 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | cache: 6 | directories: 7 | - .phpunit 8 | - $HOME/.composer/cache/files 9 | 10 | branches: 11 | only: 12 | - master 13 | 14 | matrix: 15 | fast_finish: true 16 | include: 17 | - php: 5.5 18 | env: COMPOSER_FLAGS="--prefer-lowest" SYMFONY_DEPRECATIONS_HELPER=weak 19 | - php: 7.2 20 | env: SYMFONY_LTS='^3' SYMFONY_DEPRECATIONS_HELPER=weak 21 | - php: 7.2 22 | 23 | before_install: 24 | - echo "memory_limit=4G" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini 25 | - phpenv config-rm xdebug.ini 26 | - composer self-update 27 | - if [ "$SYMFONY_LTS" != "" ]; then composer require --dev --no-update symfony/lts=$SYMFONY_LTS; fi 28 | 29 | install: 30 | - composer update $COMPOSER_FLAGS 31 | - ./phpunit install 32 | 33 | script: ./phpunit 34 | 35 | dist: trusty 36 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.ar.yml: -------------------------------------------------------------------------------- 1 | messenger: نظام مراسلة 2 | inbox: صندوق الوارد 3 | sent: المرسلة 4 | deleted: المحذوفة 5 | send_new: إنشاء رسالة 6 | search: بحث 7 | threads_found: "{0} لا يوجد نتائج مطابقة | {1} نتيجة واحدة مطابقة| ]1,Inf] %num% نتائج مطابقة" 8 | 9 | message_info: من %sender%, في %date% 10 | reply: رد 11 | 12 | subject: العنوان 13 | starter: البادئ 14 | startdate: تاريخ الإنشاء 15 | messages: رسائل 16 | last_message: أخر رسالة 17 | actions: الإجراءات 18 | new: جديد 19 | goto_last: الذهاب لأخر رسالة 20 | on: في %date% 21 | by: من %sender% 22 | no_thread: لا يوجد أي محادثات 23 | delete: حذف 24 | undelete: استعادة 25 | recipient: المرسل إليه 26 | recipients: المرسل إليهم 27 | body: المحتوى 28 | -------------------------------------------------------------------------------- /Resources/views/layout.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% trans from 'FOSMessageBundle' %}messenger{% endtrans %} 6 | 7 | 8 | 9 | 10 |

{% trans from 'FOSMessageBundle' %}messenger{% endtrans %}

11 | 12 | 17 | 18 | {% block fos_message_content %}{% endblock %} 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.es.yml: -------------------------------------------------------------------------------- 1 | messenger: Mensajes 2 | inbox: Carpeta de entrada 3 | sent: Enviado 4 | deleted: Eliminados 5 | send_new: Enviar nuevo mensaje 6 | search: Buscar 7 | threads_found: "%num% hilo encontrado | %num% hilos encontrados" 8 | 9 | message_info: "Por %sender%, el %date%" 10 | reply: Responder 11 | 12 | subject: Asunto 13 | starter: Emisor 14 | startdate: Fecha de emisión 15 | messages: Mensajes 16 | last_message: Último mensaje 17 | actions: Acciones 18 | new: Nuevo 19 | goto_last: Ir al último mensaje 20 | on: "En %date%" 21 | by: "Por %sender%" 22 | no_thread: No hay hilos que mostrar 23 | delete: Eliminar 24 | undelete: Restaurar 25 | recipient: Destinatario 26 | recipients: Destinatario 27 | body: Cuerpo 28 | -------------------------------------------------------------------------------- /Resources/config/doctrine/MessageMetadata.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.pt_BR.yml: -------------------------------------------------------------------------------- 1 | messenger: Mensagens 2 | inbox: Caixa de entrada 3 | sent: Envio 4 | deleted: Apagado 5 | send_new: Enviar uma nova mensagem 6 | search: Buscar 7 | threads_found: "%num% tópico encontrado | %num% tópicos encontrados" 8 | 9 | message_info: Por %sender%, em %date% 10 | reply: Responder 11 | 12 | subject: Assunto 13 | starter: Emissor 14 | startdate: Data de emissão 15 | messages: Mensagens 16 | last_message: Última mensagem 17 | actions: Ações 18 | new: Novo 19 | goto_last: Vá para a última mensagem 20 | on: Em %date% 21 | by: Por %sender% 22 | no_thread: Não há tópicos para mostrar 23 | delete: Apagar 24 | undelete: Restaurar 25 | recipient: Destinatário 26 | recipients: Destinatários 27 | body: Corpo 28 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.cs.yml: -------------------------------------------------------------------------------- 1 | messenger: Zprávy 2 | inbox: Přijaté 3 | sent: Odeslané 4 | deleted: Smazané 5 | send_new: Poslat novou zprávu 6 | search: Hledat 7 | threads_found: "{1}Nalezena %num% konverzace s|[2,4]Nalezeny %num% konverzace s|[5,Inf]Nalezeno %count% konverzací s" 8 | 9 | message_info: "Od %sender% z %date%" 10 | reply: Odpovědět 11 | 12 | subject: Předmět 13 | starter: Započal 14 | startdate: Začátek 15 | messages: Zprávy 16 | last_message: Poslední zpráva 17 | actions: Akce 18 | new: Nová 19 | goto_last: Jít na poslední zprávu 20 | on: "Z %date%" 21 | by: "Od %sender%" 22 | no_thread: Žádná konverzace ke zobrazení 23 | delete: Smazat 24 | undelete: Obnovit 25 | recipient: Příjemce 26 | recipients: Příjemci 27 | body: Obsah 28 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.fr.yml: -------------------------------------------------------------------------------- 1 | messenger: Messagerie 2 | inbox: Boîte de réception 3 | sent: Envoyés 4 | deleted: Supprimés 5 | send_new: Envoyer un nouveau message 6 | search: Rechercher 7 | threads_found: "%num% conversation trouvée avec | %num% conversations trouvées avec" 8 | 9 | message_info: "De %sender%, le %date%" 10 | reply: Répondre 11 | 12 | subject: Sujet 13 | starter: Débuté par 14 | startdate: Débuté le 15 | messages: Messages 16 | last_message: Dernier message 17 | actions: Actions 18 | new: Nouveau 19 | goto_last: Aller au dernier message 20 | on: "Le %date%" 21 | by: "De %sender%" 22 | no_thread: Aucune conversation 23 | delete: Supprimer 24 | undelete: Restaurer 25 | recipient: Destinataire 26 | recipients: Destinataires 27 | body: Corps 28 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.pl.yml: -------------------------------------------------------------------------------- 1 | messenger: Wiadomości 2 | inbox: Skrzynka Odbiorcza 3 | sent: Wysłane 4 | deleted: Usunięte 5 | send_new: Wyślij nową wiadomość 6 | search: Szukaj 7 | threads_found: "Znaleziono %num% wątek | Znaleziono %num% wątki | Znaleziono %num% wątków" 8 | 9 | message_info: "Od %sender%, %date%" 10 | reply: Odpowiedz 11 | 12 | subject: Temat 13 | starter: Nadawca 14 | startdate: Data rozpoczęcia 15 | messages: Wiadomości 16 | last_message: Ostatnia wiadomość 17 | actions: Akcje 18 | new: Nowa 19 | goto_last: Idź do ostatniej wiadomości 20 | on: "data %date%" 21 | by: "Od %sender%" 22 | no_thread: Brak wiadomości do wyświetlenia 23 | delete: Usuń 24 | undelete: Przywróć 25 | recipient: Odbiorca 26 | recipients: Odbiorcy 27 | body: Treść 28 | -------------------------------------------------------------------------------- /Resources/views/Message/thread.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@FOSMessage/layout.html.twig' %} 2 | 3 | {% block fos_message_content %} 4 | 5 |

{{ thread.subject }}

6 | 7 | {% for message in thread.messages %} 8 |
9 |
10 | {% trans with {'%sender%': message.sender|e, '%date%': message.createdAt|date} from 'FOSMessageBundle' %}message_info{% endtrans %} 11 |
12 | 13 |
14 | {{ message.body }} 15 |
16 |
17 | {% endfor %} 18 | 19 |

{% trans from 'FOSMessageBundle' %}reply{% endtrans %}

20 | 21 |
22 | {{ form_widget(form) }} 23 | 24 | 25 |
26 | 27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.de.yml: -------------------------------------------------------------------------------- 1 | messenger: Messenger 2 | inbox: Posteingang 3 | sent: Postausgang 4 | deleted: Gelöscht 5 | send_new: Neue Nachricht senden 6 | search: Suche 7 | threads_found: "%num% Konversation gefunden mit | %num% Konversationen gefunden mit" 8 | 9 | message_info: "Von %sender%, am %date%" 10 | reply: Antworten 11 | 12 | subject: Betreff 13 | starter: Beginner 14 | startdate: Angefangen am 15 | messages: Nachrichten 16 | last_message: Letzte Nachricht 17 | actions: Aktionen 18 | new: Neu 19 | goto_last: Gehe zur letzten Nachricht 20 | on: "Am %date%" 21 | by: "Von %sender%" 22 | no_thread: Es gibt keine Konversation zum Anzeigen 23 | delete: löschen 24 | undelete: wiederherstellen 25 | recipient: Empfänger 26 | recipients: Empfänger 27 | body: Nachricht 28 | -------------------------------------------------------------------------------- /Resources/translations/FOSMessageBundle.ru.yml: -------------------------------------------------------------------------------- 1 | messenger: Сообщения 2 | inbox: Входящие 3 | sent: Отправленные 4 | deleted: Удаленные 5 | send_new: Отправить новое сообщение 6 | search: Поиск 7 | threads_found: "найдена %num% цепочка сообщений | найдено %num% цепочки сообщений | найдено %num% цепочек сообщений" 8 | 9 | message_info: "От %sender%, %date%" 10 | reply: Ответить 11 | 12 | subject: Тема 13 | starter: Отправитель 14 | startdate: Дата начала 15 | messages: Сообщения 16 | last_message: Последнее сообщение 17 | actions: Действия 18 | new: Новое 19 | goto_last: К последнему сообщению 20 | on: "Дата %date%" 21 | by: "От %sender%" 22 | no_thread: Нет цепочек для отображения 23 | delete: Удалить 24 | undelete: Восстановить 25 | recipient: Кому 26 | recipients: Кому 27 | body: Сообщение 28 | -------------------------------------------------------------------------------- /Resources/config/doctrine/ThreadMetadata.mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Validator/SpamValidator.php: -------------------------------------------------------------------------------- 1 | spamDetector = $spamDetector; 19 | } 20 | 21 | /** 22 | * Indicates whether the constraint is valid. 23 | * 24 | * @param object $value 25 | * @param Constraint $constraint 26 | */ 27 | public function validate($value, Constraint $constraint) 28 | { 29 | if ($this->spamDetector->isSpam($value)) { 30 | $this->context->addViolation($constraint->message); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: ~ 4 | pull_request: ~ 5 | 6 | jobs: 7 | run: 8 | runs-on: ${{ matrix.operating-system }} 9 | strategy: 10 | matrix: 11 | operating-system: ['ubuntu-latest'] 12 | php-versions: ['5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0'] 13 | phpunit-versions: ['latest'] 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: Setup PHP 19 | uses: shivammathur/setup-php@v2 20 | with: 21 | php-version: ${{ matrix.php-versions }} 22 | extensions: mbstring, intl 23 | ini-values: post_max_size=256M, max_execution_time=180 24 | tools: phpunit:${{ matrix.phpunit-versions }} 25 | 26 | - name: Install Dependencies 27 | run: composer install --no-interaction --no-suggest --prefer-dist 28 | 29 | - name: Process the tests 30 | run: vendor/bin/simple-phpunit 31 | -------------------------------------------------------------------------------- /Validator/AuthorizationValidator.php: -------------------------------------------------------------------------------- 1 | authorizer = $authorizer; 19 | } 20 | 21 | /** 22 | * Indicates whether the constraint is valid. 23 | * 24 | * @param object $recipient 25 | * @param Constraint $constraint 26 | */ 27 | public function validate($recipient, Constraint $constraint) 28 | { 29 | if ($recipient && !$this->authorizer->canMessageParticipant($recipient)) { 30 | $this->context->addViolation($constraint->message); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Validator/SelfRecipientValidator.php: -------------------------------------------------------------------------------- 1 | participantProvider = $participantProvider; 19 | } 20 | 21 | /** 22 | * Indicates whether the constraint is valid. 23 | * 24 | * @param object $recipient 25 | * @param Constraint $constraint 26 | */ 27 | public function validate($recipient, Constraint $constraint) 28 | { 29 | if ($recipient === $this->participantProvider->getAuthenticatedParticipant()) { 30 | $this->context->addViolation($constraint->message); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/Functional/Form/UserToUsernameTransformer.php: -------------------------------------------------------------------------------- 1 | getUsername(); 22 | } 23 | 24 | /** 25 | * Transforms a username string into a UserInterface instance. 26 | * 27 | * @param string $value Username 28 | * 29 | * @throws UnexpectedTypeException if the given value is not a string 30 | * 31 | * @return UserInterface the corresponding UserInterface instance 32 | */ 33 | public function reverseTransform($value) 34 | { 35 | return new User(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Resources/meta/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT license 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /FormHandler/ReplyMessageFormHandler.php: -------------------------------------------------------------------------------- 1 | composer->reply($message->getThread()) 27 | ->setSender($this->getAuthenticatedParticipant()) 28 | ->setBody($message->getBody()) 29 | ->getMessage(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /FormModel/NewThreadMessage.php: -------------------------------------------------------------------------------- 1 | subject; 29 | } 30 | 31 | /** 32 | * @param string 33 | */ 34 | public function setSubject($subject) 35 | { 36 | $this->subject = $subject; 37 | } 38 | 39 | /** 40 | * @return ParticipantInterface 41 | */ 42 | public function getRecipient() 43 | { 44 | return $this->recipient; 45 | } 46 | 47 | /** 48 | * @param ParticipantInterface 49 | */ 50 | public function setRecipient($recipient) 51 | { 52 | $this->recipient = $recipient; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Model/MessageInterface.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | interface MessageInterface extends ReadableInterface 11 | { 12 | /** 13 | * Gets the message unique id. 14 | * 15 | * @return mixed 16 | */ 17 | public function getId(); 18 | 19 | /** 20 | * @return ThreadInterface 21 | */ 22 | public function getThread(); 23 | 24 | /** 25 | * @param ThreadInterface 26 | */ 27 | public function setThread(ThreadInterface $thread); 28 | 29 | /** 30 | * @return \DateTime 31 | */ 32 | public function getCreatedAt(); 33 | 34 | /** 35 | * @return string 36 | */ 37 | public function getBody(); 38 | 39 | /** 40 | * @param string 41 | */ 42 | public function setBody($body); 43 | 44 | /** 45 | * @return ParticipantInterface 46 | */ 47 | public function getSender(); 48 | 49 | /** 50 | * @param ParticipantInterface 51 | */ 52 | public function setSender(ParticipantInterface $sender); 53 | } 54 | -------------------------------------------------------------------------------- /SpamDetection/AkismetSpamDetector.php: -------------------------------------------------------------------------------- 1 | akismet = $akismet; 24 | $this->participantProvider = $participantProvider; 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function isSpam(NewThreadMessage $message) 31 | { 32 | return $this->akismet->isSpam(array( 33 | 'comment_author' => (string) $this->participantProvider->getAuthenticatedParticipant(), 34 | 'comment_content' => $message->getBody(), 35 | )); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Resources/config/doctrine/ThreadMetadata.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /FormHandler/NewThreadMessageFormHandler.php: -------------------------------------------------------------------------------- 1 | composer->newThread() 27 | ->setSubject($message->getSubject()) 28 | ->addRecipient($message->getRecipient()) 29 | ->setSender($this->getAuthenticatedParticipant()) 30 | ->setBody($message->getBody()) 31 | ->getMessage(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ModelManager/ReadableManagerInterface.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | interface ReadableManagerInterface 15 | { 16 | /** 17 | * Marks the readable as read by this participant 18 | * Must be applied directly to the storage, 19 | * without modifying the readable state. 20 | * We want to show the unread readables on the page, 21 | * as well as marking them as read. 22 | * 23 | * @param ReadableInterface $readable 24 | * @param ParticipantInterface $user 25 | */ 26 | public function markAsReadByParticipant(ReadableInterface $readable, ParticipantInterface $user); 27 | 28 | /** 29 | * Marks the readable as unread by this participant. 30 | * 31 | * @param ReadableInterface $readable 32 | * @param ParticipantInterface $user 33 | */ 34 | public function markAsUnreadByParticipant(ReadableInterface $readable, ParticipantInterface $user); 35 | } 36 | -------------------------------------------------------------------------------- /Security/AuthorizerInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface AuthorizerInterface 14 | { 15 | /** 16 | * Tells if the current user is allowed 17 | * to see this thread. 18 | * 19 | * @param ThreadInterface $thread 20 | * 21 | * @return bool 22 | */ 23 | public function canSeeThread(ThreadInterface $thread); 24 | 25 | /** 26 | * Tells if the current participant is allowed 27 | * to delete this thread. 28 | * 29 | * @param ThreadInterface $thread 30 | * 31 | * @return bool 32 | */ 33 | public function canDeleteThread(ThreadInterface $thread); 34 | 35 | /** 36 | * Tells if the current participant is allowed 37 | * to send a message to this other participant. 38 | * 39 | * @param ParticipantInterface $participant the one we want to send a message to 40 | * 41 | * @return bool 42 | */ 43 | public function canMessageParticipant(ParticipantInterface $participant); 44 | } 45 | -------------------------------------------------------------------------------- /Resources/config/doctrine/Thread.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Provider/ProviderInterface.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | interface ProviderInterface 13 | { 14 | /** 15 | * Gets the thread in the inbox of the current user. 16 | * 17 | * @return ThreadInterface[] 18 | */ 19 | public function getInboxThreads(); 20 | 21 | /** 22 | * Gets the thread in the sentbox of the current user. 23 | * 24 | * @return ThreadInterface[] 25 | */ 26 | public function getSentThreads(); 27 | 28 | /** 29 | * Gets the deleted threads of the current user. 30 | * 31 | * @return ThreadInterface[] 32 | */ 33 | public function getDeletedThreads(); 34 | 35 | /** 36 | * Gets a thread by its ID 37 | * Performs authorization checks 38 | * Marks the thread as read. 39 | * 40 | * @return ThreadInterface 41 | */ 42 | public function getThread($threadId); 43 | 44 | /** 45 | * Tells how many unread messages the authenticated participant has. 46 | * 47 | * @return int the number of unread messages 48 | */ 49 | public function getNbUnreadMessages(); 50 | } 51 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | Upgrade from 1.2 to 1.3 2 | ======================= 3 | 4 | The 1.3 version added the support for Symfony 3.0+. Several changes were made for the code to work with the new version. 5 | 6 | * **BC break**: `Controller\MessageController` now implements `Symfony\Component\DependencyInjection\ContainerAwareInterface` 7 | instead of extending the abstract class `Symfony\Component\DependencyInjection\ContainerAware` (removed in Symfony 3.0). 8 | If you relied on this (for instance by using `MessageController instanceof ContainerAware`), you may have to change 9 | your code. 10 | 11 | * Form types are now classes names instead of service references (the usage of service references is therefore deprecated). 12 | If you used your own form types for new threads and replies, you should update your configuration: 13 | 14 | Before: 15 | 16 | ```yaml 17 | fos_message: 18 | # ... 19 | 20 | new_thread_form: 21 | type: app.custom_new_thread_form_service 22 | reply_form: 23 | type: app.custom_reply_form_service 24 | ``` 25 | 26 | After: 27 | 28 | ```yaml 29 | fos_message: 30 | # ... 31 | 32 | new_thread_form: 33 | type: AppBundle\Form\Type\NewThreadFormType 34 | reply_form: 35 | type: AppBundle\Form\Type\ReplyFormType 36 | ``` 37 | -------------------------------------------------------------------------------- /Resources/config/doctrine/Message.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /FormHandler/NewThreadMultipleMessageFormHandler.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class NewThreadMultipleMessageFormHandler extends AbstractMessageFormHandler 15 | { 16 | /** 17 | * Composes a message from the form data. 18 | * 19 | * @param AbstractMessage $message 20 | * 21 | * @throws \InvalidArgumentException if the message is not a NewThreadMessage 22 | * 23 | * @return MessageInterface the composed message ready to be sent 24 | */ 25 | public function composeMessage(AbstractMessage $message) 26 | { 27 | if (!$message instanceof NewThreadMultipleMessage) { 28 | throw new \InvalidArgumentException(sprintf('Message must be a NewThreadMultipleMessage instance, "%s" given', get_class($message))); 29 | } 30 | 31 | return $this->composer->newThread() 32 | ->setSubject($message->getSubject()) 33 | ->addRecipients($message->getRecipients()) 34 | ->setSender($this->getAuthenticatedParticipant()) 35 | ->setBody($message->getBody()) 36 | ->getMessage(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MessageBuilder/NewThreadMessageBuilder.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class NewThreadMessageBuilder extends AbstractMessageBuilder 14 | { 15 | /** 16 | * The thread subject. 17 | * 18 | * @param string 19 | * 20 | * @return NewThreadMessageBuilder (fluent interface) 21 | */ 22 | public function setSubject($subject) 23 | { 24 | $this->thread->setSubject($subject); 25 | 26 | return $this; 27 | } 28 | 29 | /** 30 | * @param ParticipantInterface $recipient 31 | * 32 | * @return NewThreadMessageBuilder (fluent interface) 33 | */ 34 | public function addRecipient(ParticipantInterface $recipient) 35 | { 36 | $this->thread->addParticipant($recipient); 37 | 38 | return $this; 39 | } 40 | 41 | /** 42 | * @param Collection $recipients 43 | * 44 | * @return NewThreadMessageBuilder 45 | */ 46 | public function addRecipients(Collection $recipients) 47 | { 48 | foreach ($recipients as $recipient) { 49 | $this->addRecipient($recipient); 50 | } 51 | 52 | return $this; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Resources/config/orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | %fos_message.message_class%Metadata 10 | %fos_message.thread_class%Metadata 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | %fos_message.message_class% 20 | %fos_message.message_meta_class% 21 | 22 | 23 | 24 | 25 | %fos_message.thread_class% 26 | %fos_message.thread_meta_class% 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Resources/config/mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | %fos_message.message_class%Metadata 10 | %fos_message.thread_class%Metadata 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | %fos_message.message_class% 19 | %fos_message.message_meta_class% 20 | 21 | 22 | 23 | 24 | %fos_message.thread_class% 25 | %fos_message.thread_meta_class% 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Event/FOSMessageEvents.php: -------------------------------------------------------------------------------- 1 | authorizer = $authorizer; 25 | $this->participantProvider = $participantProvider; 26 | } 27 | 28 | /** 29 | * Indicates whether the constraint is valid. 30 | * 31 | * @param object $value 32 | * @param Constraint $constraint 33 | */ 34 | public function validate($value, Constraint $constraint) 35 | { 36 | $sender = $this->participantProvider->getAuthenticatedParticipant(); 37 | $recipients = $value->getThread()->getOtherParticipants($sender); 38 | 39 | foreach ($recipients as $recipient) { 40 | if (!$this->authorizer->canMessageParticipant($recipient)) { 41 | $this->context->addViolation($constraint->message); 42 | 43 | return; 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ModelManager/MessageManagerInterface.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | interface MessageManagerInterface extends ReadableManagerInterface 17 | { 18 | /** 19 | * Tells how many unread, non-spam, messages this participant has. 20 | * 21 | * @param ParticipantInterface $participant 22 | * 23 | * @return int the number of unread messages 24 | */ 25 | public function getNbUnreadMessageByParticipant(ParticipantInterface $participant); 26 | 27 | /** 28 | * Creates an empty message instance. 29 | * 30 | * @return MessageInterface 31 | */ 32 | public function createMessage(); 33 | 34 | /** 35 | * Saves a message. 36 | * 37 | * @param MessageInterface $message 38 | * @param bool $andFlush Whether to flush the changes (default true) 39 | */ 40 | public function saveMessage(MessageInterface $message, $andFlush = true); 41 | 42 | /** 43 | * Returns the message's fully qualified class MessageManagerInterface. 44 | * 45 | * @return string 46 | */ 47 | public function getClass(); 48 | } 49 | -------------------------------------------------------------------------------- /Resources/doc/90-sending-a-message-programatically.md: -------------------------------------------------------------------------------- 1 | Sending a message programatically 2 | ================================= 3 | 4 | This bundle comes with a set of forms that already should make it easy for your 5 | users to start sending messages to one another. Sometimes however you want to be 6 | able to send a message from your code without any form being involved. 7 | 8 | - A welcome message 9 | - A message thread for a team that was just created 10 | - A service notification message 11 | 12 | Composing a message 13 | ------------------- 14 | 15 | The service container contains a service to compose messages and one to send them. 16 | This is probably all you will need in many cases. 17 | 18 | To compose a message we retrieve the composer service and compose our message: 19 | 20 | ```php 21 | $sender = $this->get('security.context')->getToken()->getUser(); 22 | $threadBuilder = $this->get('fos_message.composer')->newThread(); 23 | $threadBuilder 24 | ->addRecipient($recipient) // Retrieved from your backend, your user manager or ... 25 | ->setSender($sender) 26 | ->setSubject('Stof commented on your pull request #456789') 27 | ->setBody('You have a typo, : mondo instead of mongo. Also for coding standards ...'); 28 | ``` 29 | 30 | Sending a message 31 | ----------------- 32 | 33 | Now all you have to do to send your message is get the sender and tell it to send 34 | 35 | ```php 36 | $sender = $this->get('fos_message.sender'); 37 | $sender->send($threadBuilder->getMessage()); 38 | ``` 39 | 40 | That's it, your message should now have been sent 41 | -------------------------------------------------------------------------------- /Search/Query.php: -------------------------------------------------------------------------------- 1 | original = $original; 27 | $this->escaped = $escaped; 28 | } 29 | 30 | /** 31 | * @return string original 32 | */ 33 | public function getOriginal() 34 | { 35 | return $this->original; 36 | } 37 | 38 | /** 39 | * @param string $original 40 | */ 41 | public function setOriginal($original) 42 | { 43 | $this->original = $original; 44 | } 45 | 46 | /** 47 | * @return string escaped 48 | */ 49 | public function getEscaped() 50 | { 51 | return $this->escaped; 52 | } 53 | 54 | /** 55 | * @param string $escaped 56 | */ 57 | public function setEscaped($escaped) 58 | { 59 | $this->escaped = $escaped; 60 | } 61 | 62 | /** 63 | * Converts to the original term string. 64 | * 65 | * @return string 66 | */ 67 | public function __toString() 68 | { 69 | return (string) $this->getOriginal(); 70 | } 71 | 72 | public function isEmpty() 73 | { 74 | return empty($this->original); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Tests/Functional/EntityManager/MessageManager.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class MessageManager extends BaseMessageManager 18 | { 19 | public function getNbUnreadMessageByParticipant(ParticipantInterface $participant) 20 | { 21 | return 3; 22 | } 23 | 24 | public function markAsReadByParticipant(ReadableInterface $readable, ParticipantInterface $participant) 25 | { 26 | } 27 | 28 | public function markAsUnreadByParticipant(ReadableInterface $readable, ParticipantInterface $participant) 29 | { 30 | } 31 | 32 | public function markIsReadByThreadAndParticipant(ThreadInterface $thread, ParticipantInterface $participant, $isRead) 33 | { 34 | } 35 | 36 | protected function markIsReadByParticipant(MessageInterface $message, ParticipantInterface $participant, $isRead) 37 | { 38 | } 39 | 40 | public function saveMessage(MessageInterface $message, $andFlush = true) 41 | { 42 | } 43 | 44 | public function getClass() 45 | { 46 | return Message::class; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Security/Authorizer.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Authorizer implements AuthorizerInterface 14 | { 15 | /** 16 | * @var ParticipantProviderInterface 17 | */ 18 | protected $participantProvider; 19 | 20 | public function __construct(ParticipantProviderInterface $participantProvider) 21 | { 22 | $this->participantProvider = $participantProvider; 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public function canSeeThread(ThreadInterface $thread) 29 | { 30 | return $this->getAuthenticatedParticipant() && $thread->isParticipant($this->getAuthenticatedParticipant()); 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function canDeleteThread(ThreadInterface $thread) 37 | { 38 | return $this->canSeeThread($thread); 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function canMessageParticipant(ParticipantInterface $participant) 45 | { 46 | return true; 47 | } 48 | 49 | /** 50 | * Gets the current authenticated user. 51 | * 52 | * @return ParticipantInterface 53 | */ 54 | protected function getAuthenticatedParticipant() 55 | { 56 | return $this->participantProvider->getAuthenticatedParticipant(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Util/LegacyFormHelper.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class LegacyFormHelper 13 | { 14 | private static $map = array( 15 | 'FOS\UserBundle\Form\Type\UsernameFormType' => 'fos_user_username', 16 | 'FOS\MessageBundle\FormType\RecipientsType' => 'recipients_selector', 17 | 'Symfony\Component\Form\Extension\Core\Type\EmailType' => 'email', 18 | 'Symfony\Component\Form\Extension\Core\Type\PasswordType' => 'password', 19 | 'Symfony\Component\Form\Extension\Core\Type\RepeatedType' => 'repeated', 20 | 'Symfony\Component\Form\Extension\Core\Type\TextType' => 'text', 21 | 'Symfony\Component\Form\Extension\Core\Type\TextareaType' => 'textarea', 22 | ); 23 | 24 | public static function getType($class) 25 | { 26 | if (!self::isLegacy()) { 27 | return $class; 28 | } 29 | 30 | if (!isset(self::$map[$class])) { 31 | throw new \InvalidArgumentException(sprintf('Form type with class "%s" can not be found. Please check for typos or add it to the map in LegacyFormHelper', $class)); 32 | } 33 | 34 | return self::$map[$class]; 35 | } 36 | 37 | public static function isLegacy() 38 | { 39 | return !method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix'); 40 | } 41 | 42 | private function __construct() 43 | { 44 | } 45 | 46 | private function __clone() 47 | { 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/Model/ThreadTest.php: -------------------------------------------------------------------------------- 1 | createParticipantMock('u1'); 13 | $u2 = $this->createParticipantMock('u2'); 14 | $u3 = $this->createParticipantMock('u3'); 15 | 16 | $thread = $this->getMockForAbstractClass('FOS\MessageBundle\Model\Thread'); 17 | $thread->expects($this->atLeastOnce()) 18 | ->method('getParticipants') 19 | ->will($this->returnValue(array($u1, $u2, $u3))); 20 | 21 | $toIds = function (array $participants) { 22 | return array_map(function (ParticipantInterface $participant) { 23 | return $participant->getId(); 24 | }, $participants); 25 | }; 26 | 27 | $this->assertSame($toIds(array($u2, $u3)), $toIds($thread->getOtherParticipants($u1))); 28 | $this->assertSame($toIds(array($u1, $u3)), $toIds($thread->getOtherParticipants($u2))); 29 | $this->assertSame($toIds(array($u1, $u2)), $toIds($thread->getOtherParticipants($u3))); 30 | } 31 | 32 | protected function createParticipantMock($id) 33 | { 34 | $participant = $this->getMockBuilder('FOS\MessageBundle\Model\ParticipantInterface') 35 | ->disableOriginalConstructor(true) 36 | ->getMock(); 37 | 38 | $participant->expects($this->any()) 39 | ->method('getId') 40 | ->will($this->returnValue($id)); 41 | 42 | return $participant; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /FormType/NewThreadMultipleMessageFormType.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class NewThreadMultipleMessageFormType extends AbstractType 15 | { 16 | public function buildForm(FormBuilderInterface $builder, array $options) 17 | { 18 | $builder 19 | ->add('recipients', LegacyFormHelper::getType('FOS\MessageBundle\FormType\RecipientsType'), array( 20 | 'label' => 'recipients', 21 | 'translation_domain' => 'FOSMessageBundle', 22 | )) 23 | ->add('subject', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\TextType'), array( 24 | 'label' => 'subject', 25 | 'translation_domain' => 'FOSMessageBundle', 26 | )) 27 | ->add('body', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\TextareaType'), array( 28 | 'label' => 'body', 29 | 'translation_domain' => 'FOSMessageBundle', 30 | )); 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function getBlockPrefix() 37 | { 38 | return 'fos_message_new_multiperson_thread'; 39 | } 40 | 41 | /** 42 | * @deprecated To remove when supporting only Symfony 3 43 | */ 44 | public function getName() 45 | { 46 | return $this->getBlockPrefix(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /FormType/ReplyMessageFormType.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class ReplyMessageFormType extends AbstractType 17 | { 18 | public function buildForm(FormBuilderInterface $builder, array $options) 19 | { 20 | $builder 21 | ->add('body', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\TextareaType'), array( 22 | 'label' => 'body', 23 | 'translation_domain' => 'FOSMessageBundle', 24 | )); 25 | } 26 | 27 | public function configureOptions(OptionsResolver $resolver) 28 | { 29 | $resolver->setDefaults(array( 30 | 'intention' => 'reply', 31 | )); 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function getBlockPrefix() 38 | { 39 | return 'fos_message_reply_message'; 40 | } 41 | 42 | /** 43 | * @deprecated To remove when supporting only Symfony 3 44 | */ 45 | public function setDefaultOptions(OptionsResolverInterface $resolver) 46 | { 47 | $this->configureOptions($resolver); 48 | } 49 | 50 | /** 51 | * @deprecated To remove when supporting only Symfony 3 52 | */ 53 | public function getName() 54 | { 55 | return $this->getBlockPrefix(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Resources/config/validator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Model/ThreadMetadata.php: -------------------------------------------------------------------------------- 1 | participant; 30 | } 31 | 32 | public function setParticipant(ParticipantInterface $participant) 33 | { 34 | $this->participant = $participant; 35 | } 36 | 37 | /** 38 | * @return bool 39 | */ 40 | public function getIsDeleted() 41 | { 42 | return $this->isDeleted; 43 | } 44 | 45 | /** 46 | * @param bool $isDeleted 47 | */ 48 | public function setIsDeleted($isDeleted) 49 | { 50 | $this->isDeleted = (bool) $isDeleted; 51 | } 52 | 53 | /** 54 | * @return \DateTime 55 | */ 56 | public function getLastParticipantMessageDate() 57 | { 58 | return $this->lastParticipantMessageDate; 59 | } 60 | 61 | public function setLastParticipantMessageDate(\DateTime $date) 62 | { 63 | $this->lastParticipantMessageDate = $date; 64 | } 65 | 66 | /** 67 | * @return \DateTime 68 | */ 69 | public function getLastMessageDate() 70 | { 71 | return $this->lastMessageDate; 72 | } 73 | 74 | public function setLastMessageDate(\DateTime $date) 75 | { 76 | $this->lastMessageDate = $date; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Composer/Composer.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class Composer implements ComposerInterface 17 | { 18 | /** 19 | * Message manager. 20 | * 21 | * @var MessageManagerInterface 22 | */ 23 | protected $messageManager; 24 | 25 | /** 26 | * Thread manager. 27 | * 28 | * @var ThreadManagerInterface 29 | */ 30 | protected $threadManager; 31 | 32 | public function __construct(MessageManagerInterface $messageManager, ThreadManagerInterface $threadManager) 33 | { 34 | $this->messageManager = $messageManager; 35 | $this->threadManager = $threadManager; 36 | } 37 | 38 | /** 39 | * Starts composing a message, starting a new thread. 40 | * 41 | * @return NewThreadMessageBuilder 42 | */ 43 | public function newThread() 44 | { 45 | $thread = $this->threadManager->createThread(); 46 | $message = $this->messageManager->createMessage(); 47 | 48 | return new NewThreadMessageBuilder($message, $thread); 49 | } 50 | 51 | /** 52 | * Starts composing a message in a reply to a thread. 53 | * 54 | * @return ReplyMessageBuilder 55 | */ 56 | public function reply(ThreadInterface $thread) 57 | { 58 | $message = $this->messageManager->createMessage(); 59 | 60 | return new ReplyMessageBuilder($message, $thread); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Search/Finder.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class Finder implements FinderInterface 15 | { 16 | /** 17 | * The participant provider instance. 18 | * 19 | * @var ParticipantProviderInterface 20 | */ 21 | protected $participantProvider; 22 | 23 | /** 24 | * The thread manager. 25 | * 26 | * @var ThreadManagerInterface 27 | */ 28 | protected $threadManager; 29 | 30 | public function __construct(ParticipantProviderInterface $participantProvider, ThreadManagerInterface $threadManager) 31 | { 32 | $this->participantProvider = $participantProvider; 33 | $this->threadManager = $threadManager; 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function find(Query $query) 40 | { 41 | return $this->threadManager->findParticipantThreadsBySearch($this->getAuthenticatedParticipant(), $query->getEscaped()); 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | public function getQueryBuilder(Query $query) 48 | { 49 | return $this->threadManager->getParticipantThreadsBySearchQueryBuilder($this->getAuthenticatedParticipant(), $query->getEscaped()); 50 | } 51 | 52 | /** 53 | * Gets the current authenticated user. 54 | * 55 | * @return ParticipantInterface 56 | */ 57 | protected function getAuthenticatedParticipant() 58 | { 59 | return $this->participantProvider->getAuthenticatedParticipant(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /FormModel/NewThreadMultipleMessage.php: -------------------------------------------------------------------------------- 1 | recipients = new ArrayCollection(); 30 | } 31 | 32 | /** 33 | * @return string 34 | */ 35 | public function getSubject() 36 | { 37 | return $this->subject; 38 | } 39 | 40 | /** 41 | * @param string $subject 42 | */ 43 | public function setSubject($subject) 44 | { 45 | $this->subject = $subject; 46 | } 47 | 48 | /** 49 | * @return ArrayCollection 50 | */ 51 | public function getRecipients() 52 | { 53 | return $this->recipients; 54 | } 55 | 56 | /** 57 | * Adds single recipient to collection. 58 | * 59 | * @param ParticipantInterface $recipient 60 | */ 61 | public function addRecipient(ParticipantInterface $recipient) 62 | { 63 | if (!$this->recipients->contains($recipient)) { 64 | $this->recipients->add($recipient); 65 | } 66 | } 67 | 68 | /** 69 | * Removes recipient from collection. 70 | * 71 | * @param ParticipantInterface $recipient 72 | */ 73 | public function removeRecipient(ParticipantInterface $recipient) 74 | { 75 | $this->recipients->removeElement($recipient); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /MessageBuilder/AbstractMessageBuilder.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | abstract class AbstractMessageBuilder 15 | { 16 | /** 17 | * The message we are building. 18 | * 19 | * @var MessageInterface 20 | */ 21 | protected $message; 22 | 23 | /** 24 | * The thread the message goes in. 25 | * 26 | * @var ThreadInterface 27 | */ 28 | protected $thread; 29 | 30 | public function __construct(MessageInterface $message, ThreadInterface $thread) 31 | { 32 | $this->message = $message; 33 | $this->thread = $thread; 34 | 35 | $this->message->setThread($thread); 36 | $thread->addMessage($message); 37 | } 38 | 39 | /** 40 | * Gets the created message. 41 | * 42 | * @return MessageInterface the message created 43 | */ 44 | public function getMessage() 45 | { 46 | return $this->message; 47 | } 48 | 49 | /** 50 | * @param string 51 | * 52 | * @return AbstractMessageBuilder (fluent interface) 53 | */ 54 | public function setBody($body) 55 | { 56 | $this->message->setBody($body); 57 | 58 | return $this; 59 | } 60 | 61 | /** 62 | * @param ParticipantInterface $sender 63 | * 64 | * @return AbstractMessageBuilder (fluent interface) 65 | */ 66 | public function setSender(ParticipantInterface $sender) 67 | { 68 | $this->message->setSender($sender); 69 | $this->thread->addParticipant($sender); 70 | 71 | return $this; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Resources/config/routing.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | FOS\MessageBundle\Controller\MessageController::inboxAction 9 | 10 | 11 | 12 | FOS\MessageBundle\Controller\MessageController::sentAction 13 | 14 | 15 | 16 | FOS\MessageBundle\Controller\MessageController::deletedAction 17 | 18 | 19 | 20 | FOS\MessageBundle\Controller\MessageController::searchAction 21 | 22 | 23 | 24 | FOS\MessageBundle\Controller\MessageController::newThreadAction 25 | 26 | 27 | 28 | FOS\MessageBundle\Controller\MessageController::deleteAction 29 | 30 | 31 | 32 | FOS\MessageBundle\Controller\MessageController::undeleteAction 33 | 34 | 35 | 36 | FOS\MessageBundle\Controller\MessageController::threadAction 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Security/ParticipantProvider.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class ParticipantProvider implements ParticipantProviderInterface 16 | { 17 | /** 18 | * @var SecurityContextInterface|TokenStorageInterface 19 | */ 20 | protected $securityContext; 21 | 22 | public function __construct($securityContext) 23 | { 24 | if (!$securityContext instanceof SecurityContextInterface && !$securityContext instanceof TokenStorageInterface) { 25 | throw new \InvalidArgumentException(sprintf( 26 | 'Argument 1 passed to ParticipantProvider::__construct is not valid (instance of %s or %s expected, %s given)', 27 | 'Symfony\Component\Security\Core\SecurityContextInterface', 28 | 'Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface', 29 | is_object($securityContext) ? get_class($securityContext) : gettype($securityContext) 30 | )); 31 | } 32 | 33 | $this->securityContext = $securityContext; 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function getAuthenticatedParticipant() 40 | { 41 | $participant = $this->securityContext->getToken()->getUser(); 42 | 43 | if (!$participant instanceof ParticipantInterface) { 44 | throw new AccessDeniedException('Must be logged in with a ParticipantInterface instance'); 45 | } 46 | 47 | return $participant; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Resources/doc/06-events.md: -------------------------------------------------------------------------------- 1 | FOSMessageBundle Events 2 | ======================= 3 | 4 | FOSMessageBundle will dispatch events inside the Symfony2 `EventDispatcher` which allows 5 | you to write listeners to modify or augment behaviour of the MessageBundle process. 6 | 7 | You can see definitions and explanations of each event as defined in `Event\FOSMessageEvents`: 8 | 9 | ```php 10 | isSpam = (bool) $isSpam; 31 | } 32 | 33 | /* 34 | * DENORMALIZATION 35 | * 36 | * All following methods are relative to denormalization 37 | */ 38 | 39 | /** 40 | * Performs denormalization tricks. 41 | */ 42 | public function denormalize() 43 | { 44 | $this->doSenderIsRead(); 45 | $this->doEnsureUnreadForParticipantsArray(); 46 | } 47 | 48 | /** 49 | * Ensures that the sender is considered to have read this message. 50 | */ 51 | protected function doSenderIsRead() 52 | { 53 | $this->setIsReadByParticipant($this->getSender(), true); 54 | } 55 | 56 | /** 57 | * Ensures that the unreadForParticipants array is updated. 58 | */ 59 | protected function doEnsureUnreadForParticipantsArray() 60 | { 61 | $this->unreadForParticipants = array(); 62 | 63 | if ($this->isSpam) { 64 | return; 65 | } 66 | 67 | foreach ($this->metadata as $metadata) { 68 | if (!$metadata->getIsRead()) { 69 | $this->unreadForParticipants[] = $metadata->getParticipant()->getId(); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FOSMessageBundle 2 | ================ 3 | 4 | This bundle provides messaging features for a Symfony application. Features available include: 5 | 6 | - Support for both the Doctrine ORM and ODM for message storage 7 | - Threaded conversations 8 | - Spam detection support 9 | - Soft deletion of threads 10 | - Permissions for messaging. 11 | 12 | [![Build Status](https://travis-ci.org/FriendsOfSymfony/FOSMessageBundle.png?branch=master)](https://travis-ci.org/FriendsOfSymfony/FOSMessageBundle) [![Total Downloads](https://poser.pugx.org/FriendsOfSymfony/message-bundle/downloads.png)](https://packagist.org/packages/FriendsOfSymfony/message-bundle) [![Latest Stable Version](https://poser.pugx.org/FriendsOfSymfony/message-bundle/v/stable.png)](https://packagist.org/packages/FriendsOfSymfony/message-bundle) 13 | 14 | Documentation 15 | ------------- 16 | 17 | Documentation for this bundle is stored under `Resources/doc` in this repository. 18 | 19 | [Read the documentation for the last stable][] 20 | 21 | Legacy (Symfony 2, or <=3.3) 22 | ------ 23 | 24 | Due to difficulties in CI testing, deprecation of the `Controller` class and the [deprecation coming November of this year (2019)][] support for anything older than Symfony3.4 (LTS) has been dropped in this bundle. 25 | 26 | For more info, see the pull request that ultimately made the decision for us - https://github.com/FriendsOfSymfony/FOSMessageBundle/pull/340 27 | 28 | If using versions older than Symfony3.4 (LTS), make sure to use version 1.3 of this bundle. 29 | 30 | https://github.com/FriendsOfSymfony/FOSMessageBundle/tree/v1.3.0 31 | 32 | License 33 | ------- 34 | 35 | This bundle is under the MIT license. See the complete license in the bundle: 36 | 37 | ``` 38 | Resources/meta/LICENSE 39 | ``` 40 | 41 | [Read the documentation for the last stable]: https://github.com/FriendsOfSymfony/FOSMessageBundle/blob/master/Resources/doc/00-index.md 42 | 43 | [deprecation coming November of this year (2019)]: https://symfony.com/roadmap/2.8 -------------------------------------------------------------------------------- /Resources/config/doctrine/Message.mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "friendsofsymfony/message-bundle", 3 | "type": "symfony-bundle", 4 | "description": "Provides user-to-user messaging features for your Symfony application.", 5 | "keywords": ["symfony", "message", "messaging"], 6 | "homepage": "https://github.com/FriendsOfSymfony/FOSMessageBundle", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Thibault Duplessis", 11 | "email": "thibault.duplessis@gmail.com", 12 | "homepage": "http://ornicar.github.com" 13 | }, 14 | { 15 | "name": "FriendsOfSymfony Community", 16 | "homepage": "https://github.com/friendsofsymfony/FOSMessageBundle/contributors" 17 | } 18 | ], 19 | "require": { 20 | "php": ">=5.5.9", 21 | "doctrine/collections": "^1.3", 22 | "symfony/form": "^3.4|^4.0", 23 | "symfony/framework-bundle": "^3.4|^4.0", 24 | "symfony/security": "^3.4|^4.0", 25 | "symfony/twig-bundle": "^3.4|^4.0" 26 | }, 27 | "require-dev": { 28 | "symfony/validator": "^3.4|^4.0", 29 | "symfony/translation": "^3.4|^4.0", 30 | "symfony/yaml": "^3.4|^4.0", 31 | "symfony/security-bundle": "^3.4|^4.0", 32 | "symfony/templating": "^3.4|^4.0", 33 | "symfony/browser-kit": "^3.4|^4.0", 34 | "symfony/phpunit-bridge": "^4.2.8", 35 | "doctrine/orm": "^2.0" 36 | }, 37 | "suggest": { 38 | "doctrine/doctrine-bundle": "dev-master", 39 | "doctrine/mongodb-odm-bundle": "dev-master" 40 | }, 41 | "autoload": { 42 | "psr-4": { 43 | "FOS\\MessageBundle\\": "" 44 | } 45 | }, 46 | "extra": { 47 | "branch-alias": { 48 | "dev-master": "2.0-dev" 49 | } 50 | }, 51 | "repositories": [ 52 | { 53 | "type": "composer", 54 | "url": "https://packagist.org" 55 | }, 56 | { "packagist": false } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /FormFactory/AbstractMessageFormFactory.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | abstract class AbstractMessageFormFactory 15 | { 16 | /** 17 | * The Symfony form factory. 18 | * 19 | * @var FormFactoryInterface 20 | */ 21 | protected $formFactory; 22 | 23 | /** 24 | * The message form type. 25 | * 26 | * @var AbstractType|string 27 | */ 28 | protected $formType; 29 | 30 | /** 31 | * The name of the form. 32 | * 33 | * @var string 34 | */ 35 | protected $formName; 36 | 37 | /** 38 | * The FQCN of the message model. 39 | * 40 | * @var string 41 | */ 42 | protected $messageClass; 43 | 44 | public function __construct(FormFactoryInterface $formFactory, $formType, $formName, $messageClass) 45 | { 46 | if (!is_string($formType) && !$formType instanceof AbstractType) { 47 | throw new \InvalidArgumentException(sprintf( 48 | 'Form type provided is not valid (class name or instance of %s expected, %s given)', 49 | 'Symfony\Component\Form\AbstractType', 50 | is_object($formType) ? get_class($formType) : gettype($formType) 51 | )); 52 | } 53 | 54 | $this->formFactory = $formFactory; 55 | $this->formType = $formType; 56 | $this->formName = $formName; 57 | $this->messageClass = $messageClass; 58 | } 59 | 60 | /** 61 | * Creates a new instance of the form model. 62 | * 63 | * @return AbstractMessage 64 | */ 65 | protected function createModelInstance() 66 | { 67 | $class = $this->messageClass; 68 | 69 | return new $class(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sender/Sender.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class Sender implements SenderInterface 18 | { 19 | /** 20 | * @var MessageManagerInterface 21 | */ 22 | protected $messageManager; 23 | 24 | /** 25 | * @var ThreadManagerInterface 26 | */ 27 | protected $threadManager; 28 | 29 | /** 30 | * @var EventDispatcherInterface 31 | */ 32 | protected $dispatcher; 33 | 34 | public function __construct(MessageManagerInterface $messageManager, ThreadManagerInterface $threadManager, EventDispatcherInterface $dispatcher) 35 | { 36 | $this->messageManager = $messageManager; 37 | $this->threadManager = $threadManager; 38 | $this->dispatcher = $dispatcher; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function send(MessageInterface $message) 45 | { 46 | $this->threadManager->saveThread($message->getThread(), false); 47 | $this->messageManager->saveMessage($message, false); 48 | 49 | /* Note: Thread::setIsDeleted() depends on metadata existing for all 50 | * thread and message participants, so both objects must be saved first. 51 | * We can avoid flushing the object manager, since we must save once 52 | * again after undeleting the thread. 53 | */ 54 | $message->getThread()->setIsDeleted(false); 55 | $this->messageManager->saveMessage($message); 56 | 57 | $this->dispatcher->dispatch(FOSMessageEvents::POST_SEND, new MessageEvent($message)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Search/QueryFactory.php: -------------------------------------------------------------------------------- 1 | request = $requestStack; 31 | $this->queryParameter = $queryParameter; 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function createFromRequest() 38 | { 39 | $original = $this->getCurrentRequest()->query->get($this->queryParameter); 40 | $original = trim($original); 41 | 42 | $escaped = $this->escapeTerm($original); 43 | 44 | return new Query($original, $escaped); 45 | } 46 | 47 | /** 48 | * Sets: the query parameter containing the search term. 49 | * 50 | * @param string $queryParameter 51 | */ 52 | public function setQueryParameter($queryParameter) 53 | { 54 | $this->queryParameter = $queryParameter; 55 | } 56 | 57 | protected function escapeTerm($term) 58 | { 59 | return $term; 60 | } 61 | 62 | /** 63 | * BC layer to retrieve the current request directly or from a stack. 64 | * 65 | * @return null|Request 66 | */ 67 | private function getCurrentRequest() 68 | { 69 | if ($this->request instanceof Request) { 70 | return $this->request; 71 | } 72 | 73 | return $this->request->getCurrentRequest(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /FormType/RecipientsType.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class RecipientsType extends AbstractType 18 | { 19 | /** 20 | * @var RecipientsDataTransformer 21 | */ 22 | private $recipientsTransformer; 23 | 24 | /** 25 | * @param RecipientsDataTransformer $transformer 26 | */ 27 | public function __construct(RecipientsDataTransformer $transformer) 28 | { 29 | $this->recipientsTransformer = $transformer; 30 | } 31 | 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function buildForm(FormBuilderInterface $builder, array $options) 36 | { 37 | $builder->addModelTransformer($this->recipientsTransformer); 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function configureOptions(OptionsResolver $resolver) 44 | { 45 | $resolver->setDefaults(array( 46 | 'invalid_message' => 'The selected recipient does not exist', 47 | )); 48 | } 49 | 50 | public function setDefaultOptions(OptionsResolverInterface $resolver) 51 | { 52 | $this->configureOptions($resolver); 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | public function getBlockPrefix() 59 | { 60 | return 'recipients_selector'; 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function getParent() 67 | { 68 | return LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\TextType'); 69 | } 70 | 71 | /** 72 | * @deprecated To remove when supporting only Symfony 3 73 | */ 74 | public function getName() 75 | { 76 | return $this->getBlockPrefix(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /FormType/NewThreadMessageFormType.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class NewThreadMessageFormType extends AbstractType 17 | { 18 | public function buildForm(FormBuilderInterface $builder, array $options) 19 | { 20 | $builder 21 | ->add('recipient', LegacyFormHelper::getType('FOS\UserBundle\Form\Type\UsernameFormType'), array( 22 | 'label' => 'recipient', 23 | 'translation_domain' => 'FOSMessageBundle', 24 | )) 25 | ->add('subject', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\TextType'), array( 26 | 'label' => 'subject', 27 | 'translation_domain' => 'FOSMessageBundle', 28 | )) 29 | ->add('body', LegacyFormHelper::getType('Symfony\Component\Form\Extension\Core\Type\TextareaType'), array( 30 | 'label' => 'body', 31 | 'translation_domain' => 'FOSMessageBundle', 32 | )); 33 | } 34 | 35 | public function configureOptions(OptionsResolver $resolver) 36 | { 37 | $resolver->setDefaults(array( 38 | 'intention' => 'message', 39 | )); 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function getBlockPrefix() 46 | { 47 | return 'fos_message_new_thread'; 48 | } 49 | 50 | /** 51 | * @deprecated To remove when supporting only Symfony 3 52 | */ 53 | public function setDefaultOptions(OptionsResolverInterface $resolver) 54 | { 55 | $this->configureOptions($resolver); 56 | } 57 | 58 | /** 59 | * @deprecated To remove when supporting only Symfony 3 60 | */ 61 | public function getName() 62 | { 63 | return $this->getBlockPrefix(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Tests/Functional/EntityManager/ThreadManager.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class Reader implements ReaderInterface 19 | { 20 | /** 21 | * The participantProvider instance. 22 | * 23 | * @var ParticipantProviderInterface 24 | */ 25 | protected $participantProvider; 26 | 27 | /** 28 | * The readable manager. 29 | * 30 | * @var ReadableManagerInterface 31 | */ 32 | protected $readableManager; 33 | 34 | /** 35 | * The event dispatcher. 36 | * 37 | * @var EventDispatcherInterface 38 | */ 39 | protected $dispatcher; 40 | 41 | public function __construct(ParticipantProviderInterface $participantProvider, ReadableManagerInterface $readableManager, EventDispatcherInterface $dispatcher) 42 | { 43 | $this->participantProvider = $participantProvider; 44 | $this->readableManager = $readableManager; 45 | $this->dispatcher = $dispatcher; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function markAsRead(ReadableInterface $readable) 52 | { 53 | $participant = $this->getAuthenticatedParticipant(); 54 | if ($readable->isReadByParticipant($participant)) { 55 | return; 56 | } 57 | $this->readableManager->markAsReadByParticipant($readable, $participant); 58 | 59 | $this->dispatcher->dispatch(FOSMessageEvents::POST_READ, new ReadableEvent($readable)); 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function markAsUnread(ReadableInterface $readable) 66 | { 67 | $participant = $this->getAuthenticatedParticipant(); 68 | if (!$readable->isReadByParticipant($participant)) { 69 | return; 70 | } 71 | $this->readableManager->markAsUnreadByParticipant($readable, $participant); 72 | 73 | $this->dispatcher->dispatch(FOSMessageEvents::POST_UNREAD, new ReadableEvent($readable)); 74 | } 75 | 76 | /** 77 | * Gets the current authenticated user. 78 | * 79 | * @return ParticipantInterface 80 | */ 81 | protected function getAuthenticatedParticipant() 82 | { 83 | return $this->participantProvider->getAuthenticatedParticipant(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /DataTransformer/RecipientsDataTransformer.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class RecipientsDataTransformer implements DataTransformerInterface 18 | { 19 | /** 20 | * @var DataTransformerInterface 21 | */ 22 | private $userToUsernameTransformer; 23 | 24 | public function __construct(DataTransformerInterface $userToUsernameTransformer) 25 | { 26 | $this->userToUsernameTransformer = $userToUsernameTransformer; 27 | } 28 | 29 | /** 30 | * Transforms a collection of recipients into a string. 31 | * 32 | * @param Collection $recipients 33 | * 34 | * @return string 35 | */ 36 | public function transform($recipients) 37 | { 38 | if (null === $recipients || 0 === $recipients->count()) { 39 | return ''; 40 | } 41 | 42 | $usernames = array(); 43 | 44 | foreach ($recipients as $recipient) { 45 | $usernames[] = $this->userToUsernameTransformer->transform($recipient); 46 | } 47 | 48 | return implode(', ', $usernames); 49 | } 50 | 51 | /** 52 | * Transforms a string (usernames) to a Collection of UserInterface. 53 | * 54 | * @param string $usernames 55 | * 56 | * @throws UnexpectedTypeException 57 | * @throws TransformationFailedException 58 | * 59 | * @return Collection $recipients 60 | */ 61 | public function reverseTransform($usernames) 62 | { 63 | if (null === $usernames || '' === $usernames) { 64 | return null; 65 | } 66 | 67 | if (!is_string($usernames)) { 68 | throw new UnexpectedTypeException($usernames, 'string'); 69 | } 70 | 71 | $recipients = new ArrayCollection(); 72 | $recipientsNames = array_filter(explode(',', $usernames)); 73 | 74 | foreach ($recipientsNames as $username) { 75 | $user = $this->userToUsernameTransformer->reverseTransform(trim($username)); 76 | 77 | if (!$user instanceof UserInterface) { 78 | throw new TransformationFailedException(sprintf('User "%s" does not exists', $username)); 79 | } 80 | 81 | $recipients->add($user); 82 | } 83 | 84 | return $recipients; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Deleter/Deleter.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Deleter implements DeleterInterface 20 | { 21 | /** 22 | * The authorizer instance. 23 | * 24 | * @var AuthorizerInterface 25 | */ 26 | protected $authorizer; 27 | 28 | /** 29 | * The participant provider instance. 30 | * 31 | * @var ParticipantProviderInterface 32 | */ 33 | protected $participantProvider; 34 | 35 | /** 36 | * The event dispatcher. 37 | * 38 | * @var EventDispatcherInterface 39 | */ 40 | protected $dispatcher; 41 | 42 | public function __construct(AuthorizerInterface $authorizer, ParticipantProviderInterface $participantProvider, EventDispatcherInterface $dispatcher) 43 | { 44 | $this->authorizer = $authorizer; 45 | $this->participantProvider = $participantProvider; 46 | $this->dispatcher = $dispatcher; 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function markAsDeleted(ThreadInterface $thread) 53 | { 54 | if (!$this->authorizer->canDeleteThread($thread)) { 55 | throw new AccessDeniedException('You are not allowed to delete this thread'); 56 | } 57 | $thread->setIsDeletedByParticipant($this->getAuthenticatedParticipant(), true); 58 | 59 | $this->dispatcher->dispatch(FOSMessageEvents::POST_DELETE, new ThreadEvent($thread)); 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function markAsUndeleted(ThreadInterface $thread) 66 | { 67 | if (!$this->authorizer->canDeleteThread($thread)) { 68 | throw new AccessDeniedException('You are not allowed to delete this thread'); 69 | } 70 | $thread->setIsDeletedByParticipant($this->getAuthenticatedParticipant(), false); 71 | 72 | $this->dispatcher->dispatch(FOSMessageEvents::POST_UNDELETE, new ThreadEvent($thread)); 73 | } 74 | 75 | /** 76 | * Gets the current authenticated user. 77 | * 78 | * @return ParticipantInterface 79 | */ 80 | protected function getAuthenticatedParticipant() 81 | { 82 | return $this->participantProvider->getAuthenticatedParticipant(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Resources/config/doctrine/Thread.mongodb.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Resources/doc/01-installation.md: -------------------------------------------------------------------------------- 1 | Setting up FOSMessageBundle 2 | =========================== 3 | 4 | ### Step 1 - Requirements and Installing the bundle 5 | 6 | > **Note** FOSMessageBundle by default has a requirement for FOSUserBundle for some of its 7 | > interfaces which can be swapped out if you're not using FOSUserBundle. See 8 | > [Using other UserBundles][] for more information. 9 | 10 | The first step is to tell composer that you want to download FOSMessageBundle which can 11 | be achieved by typing the following at the command prompt: 12 | 13 | #### Current 14 | 15 | ```bash 16 | composer require friendsofsymfony/message-bundle 17 | ``` 18 | 19 | #### v1.3 (Legacy: Symfony 2, or <=3.3) 20 | 21 | ```bash 22 | composer require friendsofsymfony/message-bundle:1.3.0 23 | ``` 24 | 25 | ### Step 2 - Setting up your user class 26 | 27 | FOSMessageBundle requires that your user class implement `ParticipantInterface`. This 28 | bundle does not have any direct dependencies to any particular UserBundle or 29 | implementation of a user, except that it must implement the above interface. 30 | 31 | Your user class may look something like the following: 32 | 33 | ```php 34 | 24 | */ 25 | class TestKernel extends Kernel 26 | { 27 | use MicroKernelTrait; 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function registerBundles() 33 | { 34 | $bundles = array( 35 | new FrameworkBundle(), 36 | new SecurityBundle(), 37 | new TwigBundle(), 38 | new FOSMessageBundle(), 39 | ); 40 | 41 | return $bundles; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | protected function configureRoutes(RouteCollectionBuilder $routes) 48 | { 49 | $routes->import('@FOSMessageBundle/Resources/config/routing.xml'); 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) 56 | { 57 | $c->loadFromExtension('framework', array( 58 | 'secret' => 'MySecretKey', 59 | 'test' => null, 60 | 'form' => null, 61 | 'templating' => array( 62 | 'engines' => array('twig'), 63 | ), 64 | )); 65 | 66 | $c->loadFromExtension('security', array( 67 | 'providers' => array('permissive' => array('id' => 'app.user_provider')), 68 | 'encoders' => array('FOS\MessageBundle\Tests\Functional\Entity\User' => 'plaintext'), 69 | 'firewalls' => array('main' => array('http_basic' => true)), 70 | )); 71 | 72 | $c->loadFromExtension('twig', array( 73 | 'strict_variables' => '%kernel.debug%', 74 | )); 75 | 76 | $c->loadFromExtension('fos_message', array( 77 | 'db_driver' => 'orm', 78 | 'thread_class' => Thread::class, 79 | 'message_class' => Message::class, 80 | )); 81 | 82 | $c->register('fos_user.user_to_username_transformer', UserToUsernameTransformer::class); 83 | $c->register('app.user_provider', UserProvider::class); 84 | $c->addCompilerPass(new RegisteringManagersPass()); 85 | } 86 | } 87 | 88 | class RegisteringManagersPass implements CompilerPassInterface { 89 | public function process(ContainerBuilder $container) 90 | { 91 | $container->register('fos_message.message_manager.default', MessageManager::class); 92 | $container->register('fos_message.thread_manager.default', ThreadManager::class); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Resources/views/Message/threads_list.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% if threads %} 17 | {% for thread in threads %} 18 | 19 | 28 | 31 | 34 | 37 | 49 | 63 | 64 | {% endfor %} 65 | {% else %} 66 | 67 | 70 | 71 | {% endif %} 72 | 73 | 74 | 75 |
{% trans from 'FOSMessageBundle' %}subject{% endtrans %}{% trans from 'FOSMessageBundle' %}starter{% endtrans %}{% trans from 'FOSMessageBundle' %}startdate{% endtrans %}{% trans from 'FOSMessageBundle' %}messages{% endtrans %}{% trans from 'FOSMessageBundle' %}last_message{% endtrans %}{% trans from 'FOSMessageBundle' %}actions{% endtrans %}
20 | 21 | {{ thread.subject }} 22 | 23 | 24 | {% if not fos_message_is_read(thread) %} 25 | ({% trans from 'FOSMessageBundle' %}new{% endtrans %}) 26 | {% endif %} 27 | 29 | {{ thread.createdBy }} 30 | 32 | {{ thread.createdAt|date }} 33 | 35 | {{ thread.messages|length }} 36 | 38 | {% if thread.lastMessage %} 39 | 40 | → 41 | 42 | {% trans with {'%date%': thread.lastMessage.createdAt|date} from 'FOSMessageBundle' %}on{% endtrans %} 43 |
44 | {% trans with {'%sender%': thread.lastMessage.sender|e } from 'FOSMessageBundle' %}by{% endtrans %} 45 | {% else %} 46 | ---- 47 | {% endif %} 48 |
50 | {% if fos_message_can_delete_thread(thread) %} 51 | {% if fos_message_deleted_by_participant(thread) %} 52 | {% set formAction %}{{ url('fos_message_thread_undelete', {'threadId': thread.id}) }}{% endset %} 53 | {% set submitValue %}{% trans from 'FOSMessageBundle' %}undelete{% endtrans %}{% endset %} 54 | {% else %} 55 | {% set formAction %}{{ url('fos_message_thread_delete', {'threadId': thread.id}) }}{% endset %} 56 | {% set submitValue %}{% trans from 'FOSMessageBundle' %}delete{% endtrans %}{% endset %} 57 | {% endif %} 58 |
59 | 60 |
61 | {% endif %} 62 |
68 | {% trans from 'FOSMessageBundle' %}no_thread{% endtrans %}. 69 |
76 | -------------------------------------------------------------------------------- /Resources/doc/02-basic-usage.md: -------------------------------------------------------------------------------- 1 | Basic Usage of FOSMessageBundle 2 | =============================== 3 | 4 | Basic operations involving FOSMessageBundle can be seen in the 5 | `Controller/MessageController` file. 6 | 7 | Get user threads 8 | ---------------- 9 | 10 | Get the threads in the inbox of the authenticated user:: 11 | 12 | ```php 13 | $provider = $container->get('fos_message.provider'); 14 | 15 | $threads = $provider->getInboxThreads(); 16 | ``` 17 | 18 | And the threads in the sentbox:: 19 | 20 | ```php 21 | $threads = $provider->getSentThreads(); 22 | ``` 23 | 24 | To get a single thread, check it belongs to the authenticated user and mark it as read:: 25 | 26 | ```php 27 | $thread = $provider->getThread($threadId); 28 | ``` 29 | 30 | Manipulate threads 31 | ------------------ 32 | 33 | See `FOS\\MessageBundle\\Model\\ThreadInterface` for the complete list of available methods:: 34 | 35 | ```php 36 | // Print the thread subject 37 | echo $thread->getSubject(); 38 | 39 | // Get the tread participants 40 | $participants = $thread->getParticipants(); 41 | 42 | // Know if this participant has read this thread 43 | if ($thread->isReadByParticipant($participant)) 44 | 45 | // Know if this participant has deleted this thread 46 | if ($thread->isDeletedByParticipant($participant)) 47 | ``` 48 | 49 | Be careful when manipulating thread participants: you may have to persist the thread to synchronize 50 | the thread metadata using `$container->get('fos_message.thread_manager')->saveThread($thread, false)` 51 | (the second argument as `false` meaning to not flush the modifications). 52 | 53 | Manipulate messages 54 | ------------------- 55 | 56 | See ``FOS\\MessageBundle\\Model\\MessageInterface`` for the complete list of available methods:: 57 | 58 | ```php 59 | // Print the message body 60 | echo $message->getBody(); 61 | 62 | // Get the message sender participant 63 | $sender = $message->getSender(); 64 | 65 | // Get the message thread 66 | $thread = $message->getThread(); 67 | 68 | // Know if this participant has read this message 69 | if ($message->isReadByParticipant($participant)) 70 | ``` 71 | 72 | Compose a message 73 | -------------- 74 | 75 | Create a new message thread:: 76 | 77 | ```php 78 | $composer = $container->get('fos_message.composer'); 79 | 80 | $message = $composer->newThread() 81 | ->setSender($jack) 82 | ->addRecipient($clyde) 83 | ->setSubject('Hi there') 84 | ->setBody('This is a test message') 85 | ->getMessage(); 86 | ``` 87 | 88 | And to reply to this thread:: 89 | 90 | ```php 91 | $message = $composer->reply($thread) 92 | ->setSender($clyde) 93 | ->setBody('This is the answer to the test message') 94 | ->getMessage(); 95 | ``` 96 | 97 | Note that when replying, we don't need to provide the subject nor the recipient. 98 | Because they are the attributes of the thread, which already exists. 99 | 100 | Send a message 101 | -------------- 102 | 103 | Nothing's easier than sending the message you've just composed:: 104 | 105 | ```php 106 | $sender = $container->get('fos_message.sender'); 107 | $sender->send($message); 108 | ``` 109 | 110 | Number of Unread Messages 111 | ------------------------- 112 | 113 | You can return the number of unread messages for the authenticated user with:: 114 | 115 | ```php 116 | $provider = $container->get('fos_message.provider'); 117 | $provider->getNbUnreadMessages(); 118 | ``` 119 | 120 | Will return an integer, the number of unread messages. 121 | 122 | [Return to the documentation index](00-index.md) 123 | -------------------------------------------------------------------------------- /Resources/config/form.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | %fos_message.new_thread_form.name% 19 | %fos_message.new_thread_form.model% 20 | 21 | 22 | 23 | 24 | 25 | %fos_message.reply_form.name% 26 | %fos_message.reply_form.model% 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Twig/Extension/MessageExtension.php: -------------------------------------------------------------------------------- 1 | participantProvider = $participantProvider; 25 | $this->provider = $provider; 26 | $this->authorizer = $authorizer; 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function getFunctions() 33 | { 34 | return array( 35 | new TwigFunction('fos_message_is_read', array($this, 'isRead')), 36 | new TwigFunction('fos_message_nb_unread', array($this, 'getNbUnread')), 37 | new TwigFunction('fos_message_can_delete_thread', array($this, 'canDeleteThread')), 38 | new TwigFunction('fos_message_deleted_by_participant', array($this, 'isThreadDeletedByParticipant')), 39 | ); 40 | } 41 | 42 | /** 43 | * Tells if this readable (thread or message) is read by the current user. 44 | * 45 | * @return bool 46 | */ 47 | public function isRead(ReadableInterface $readable) 48 | { 49 | return $readable->isReadByParticipant($this->getAuthenticatedParticipant()); 50 | } 51 | 52 | /** 53 | * Checks if the participant can mark a thread as deleted. 54 | * 55 | * @param ThreadInterface $thread 56 | * 57 | * @return bool true if participant can mark a thread as deleted, false otherwise 58 | */ 59 | public function canDeleteThread(ThreadInterface $thread) 60 | { 61 | return $this->authorizer->canDeleteThread($thread); 62 | } 63 | 64 | /** 65 | * Checks if the participant has marked the thread as deleted. 66 | * 67 | * @param ThreadInterface $thread 68 | * 69 | * @return bool true if participant has marked the thread as deleted, false otherwise 70 | */ 71 | public function isThreadDeletedByParticipant(ThreadInterface $thread) 72 | { 73 | return $thread->isDeletedByParticipant($this->getAuthenticatedParticipant()); 74 | } 75 | 76 | /** 77 | * Gets the number of unread messages for the current user. 78 | * 79 | * @return int 80 | */ 81 | public function getNbUnread() 82 | { 83 | if (null === $this->nbUnreadMessagesCache) { 84 | $this->nbUnreadMessagesCache = $this->provider->getNbUnreadMessages(); 85 | } 86 | 87 | return $this->nbUnreadMessagesCache; 88 | } 89 | 90 | /** 91 | * Gets the current authenticated user. 92 | * 93 | * @return ParticipantInterface 94 | */ 95 | protected function getAuthenticatedParticipant() 96 | { 97 | return $this->participantProvider->getAuthenticatedParticipant(); 98 | } 99 | 100 | /** 101 | * {@inheritdoc} 102 | */ 103 | public function getName() 104 | { 105 | return 'fos_message'; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Model/ThreadInterface.php: -------------------------------------------------------------------------------- 1 | getParticipantsCollection()->toArray(); 63 | } 64 | 65 | /** 66 | * Gets the users participating in this conversation. 67 | * 68 | * Since the ORM schema does not map the participants collection field, it 69 | * must be created on demand. 70 | * 71 | * @return ArrayCollection|ParticipantInterface[] 72 | */ 73 | protected function getParticipantsCollection() 74 | { 75 | if (null === $this->participants) { 76 | $this->participants = new ArrayCollection(); 77 | 78 | foreach ($this->metadata as $data) { 79 | $this->participants->add($data->getParticipant()); 80 | } 81 | } 82 | 83 | return $this->participants; 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function addParticipant(ParticipantInterface $participant) 90 | { 91 | if (!$this->isParticipant($participant)) { 92 | $this->getParticipantsCollection()->add($participant); 93 | } 94 | } 95 | 96 | /** 97 | * Adds many participants to the thread. 98 | * 99 | * @param array|\Traversable 100 | * 101 | * @throws \InvalidArgumentException 102 | * 103 | * @return Thread 104 | */ 105 | public function addParticipants($participants) 106 | { 107 | if (!is_array($participants) && !$participants instanceof \Traversable) { 108 | throw new \InvalidArgumentException('Participants must be an array or instance of Traversable'); 109 | } 110 | 111 | foreach ($participants as $participant) { 112 | $this->addParticipant($participant); 113 | } 114 | 115 | return $this; 116 | } 117 | 118 | /** 119 | * {@inheritdoc} 120 | */ 121 | public function isParticipant(ParticipantInterface $participant) 122 | { 123 | return $this->getParticipantsCollection()->contains($participant); 124 | } 125 | 126 | /** 127 | * Get the collection of ModelThreadMetadata. 128 | * 129 | * @return Collection 130 | */ 131 | public function getAllMetadata() 132 | { 133 | return $this->metadata; 134 | } 135 | 136 | /** 137 | * {@inheritdoc} 138 | */ 139 | public function addMetadata(ModelThreadMetadata $meta) 140 | { 141 | $meta->setThread($this); 142 | parent::addMetadata($meta); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Provider/Provider.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Provider implements ProviderInterface 20 | { 21 | /** 22 | * The thread manager. 23 | * 24 | * @var ThreadManagerInterface 25 | */ 26 | protected $threadManager; 27 | 28 | /** 29 | * The message manager. 30 | * 31 | * @var MessageManagerInterface 32 | */ 33 | protected $messageManager; 34 | 35 | /** 36 | * The reader used to mark threads as read. 37 | * 38 | * @var ReaderInterface 39 | */ 40 | protected $threadReader; 41 | 42 | /** 43 | * The authorizer manager. 44 | * 45 | * @var authorizerInterface 46 | */ 47 | protected $authorizer; 48 | 49 | /** 50 | * The participant provider instance. 51 | * 52 | * @var ParticipantProviderInterface 53 | */ 54 | protected $participantProvider; 55 | 56 | public function __construct(ThreadManagerInterface $threadManager, MessageManagerInterface $messageManager, ReaderInterface $threadReader, AuthorizerInterface $authorizer, ParticipantProviderInterface $participantProvider) 57 | { 58 | $this->threadManager = $threadManager; 59 | $this->messageManager = $messageManager; 60 | $this->threadReader = $threadReader; 61 | $this->authorizer = $authorizer; 62 | $this->participantProvider = $participantProvider; 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | public function getInboxThreads() 69 | { 70 | $participant = $this->getAuthenticatedParticipant(); 71 | 72 | return $this->threadManager->findParticipantInboxThreads($participant); 73 | } 74 | 75 | /** 76 | * {@inheritdoc} 77 | */ 78 | public function getSentThreads() 79 | { 80 | $participant = $this->getAuthenticatedParticipant(); 81 | 82 | return $this->threadManager->findParticipantSentThreads($participant); 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function getDeletedThreads() 89 | { 90 | $participant = $this->getAuthenticatedParticipant(); 91 | 92 | return $this->threadManager->findParticipantDeletedThreads($participant); 93 | } 94 | 95 | /** 96 | * {@inheritdoc} 97 | */ 98 | public function getThread($threadId) 99 | { 100 | $thread = $this->threadManager->findThreadById($threadId); 101 | if (!$thread) { 102 | throw new NotFoundHttpException('There is no such thread'); 103 | } 104 | if (!$this->authorizer->canSeeThread($thread)) { 105 | throw new AccessDeniedException('You are not allowed to see this thread'); 106 | } 107 | // Load the thread messages before marking them as read 108 | // because we want to see the unread messages 109 | $thread->getMessages(); 110 | $this->threadReader->markAsRead($thread); 111 | 112 | return $thread; 113 | } 114 | 115 | /** 116 | * {@inheritdoc} 117 | */ 118 | public function getNbUnreadMessages() 119 | { 120 | return $this->messageManager->getNbUnreadMessageByParticipant($this->getAuthenticatedParticipant()); 121 | } 122 | 123 | /** 124 | * Gets the current authenticated user. 125 | * 126 | * @return ParticipantInterface 127 | */ 128 | protected function getAuthenticatedParticipant() 129 | { 130 | return $this->participantProvider->getAuthenticatedParticipant(); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Resources/doc/01b-odm-models.md: -------------------------------------------------------------------------------- 1 | Concrete classes for Doctrine ODM 2 | ================================= 3 | 4 | This page lists some example implementations of FOSMessageBundle models for the Doctrine 5 | MongoDB ODM. 6 | 7 | Given the examples below with their namespaces and class names, you need to configure 8 | FOSMessageBundle to tell them about these classes. 9 | 10 | Add the following to your `app/config/config.yml` file. 11 | 12 | ```yaml 13 | # app/config/config.yml 14 | 15 | fos_message: 16 | db_driver: mongodb 17 | thread_class: AppBundle\Document\Thread 18 | message_class: AppBundle\Document\Message 19 | ``` 20 | 21 | You may have to include the MessageBundle in your Doctrine mapping configuration, 22 | along with the bundle containing your custom Thread and Message classes: 23 | 24 | ```yaml 25 | # app/config/config.yml 26 | 27 | doctrine_mongodb: 28 | document_managers: 29 | default: 30 | mappings: 31 | FOSMessageBundle: ~ 32 | ``` 33 | 34 | 35 | [Continue with the installation][] 36 | 37 | Message class 38 | ------------- 39 | 40 | ```php 41 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /Resources/config/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /Model/Message.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | abstract class Message implements MessageInterface 14 | { 15 | /** 16 | * Unique id of the message. 17 | * 18 | * @var mixed 19 | */ 20 | protected $id; 21 | 22 | /** 23 | * User who sent the message. 24 | * 25 | * @var ParticipantInterface 26 | */ 27 | protected $sender; 28 | 29 | /** 30 | * Text body of the message. 31 | * 32 | * @var string 33 | */ 34 | protected $body; 35 | 36 | /** 37 | * Date when the message was sent. 38 | * 39 | * @var \DateTime 40 | */ 41 | protected $createdAt; 42 | 43 | /** 44 | * Thread the message belongs to. 45 | * 46 | * @var ThreadInterface 47 | */ 48 | protected $thread; 49 | 50 | /** 51 | * Collection of MessageMetadata. 52 | * 53 | * @var Collection|MessageMetadata[] 54 | */ 55 | protected $metadata; 56 | 57 | /** 58 | * Constructor. 59 | */ 60 | public function __construct() 61 | { 62 | $this->createdAt = new \DateTime(); 63 | $this->metadata = new ArrayCollection(); 64 | } 65 | 66 | /** 67 | * {@inheritdoc} 68 | */ 69 | public function getId() 70 | { 71 | return $this->id; 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | public function getThread() 78 | { 79 | return $this->thread; 80 | } 81 | 82 | /** 83 | * {@inheritdoc} 84 | */ 85 | public function setThread(ThreadInterface $thread) 86 | { 87 | $this->thread = $thread; 88 | } 89 | 90 | /** 91 | * {@inheritdoc} 92 | */ 93 | public function getCreatedAt() 94 | { 95 | return $this->createdAt; 96 | } 97 | 98 | /** 99 | * {@inheritdoc} 100 | */ 101 | public function getBody() 102 | { 103 | return $this->body; 104 | } 105 | 106 | /** 107 | * {@inheritdoc} 108 | */ 109 | public function setBody($body) 110 | { 111 | $this->body = $body; 112 | } 113 | 114 | /** 115 | * {@inheritdoc} 116 | */ 117 | public function getSender() 118 | { 119 | return $this->sender; 120 | } 121 | 122 | /** 123 | * {@inheritdoc} 124 | */ 125 | public function setSender(ParticipantInterface $sender) 126 | { 127 | $this->sender = $sender; 128 | } 129 | 130 | /** 131 | * Gets the created at timestamp. 132 | * 133 | * @return int 134 | */ 135 | public function getTimestamp() 136 | { 137 | return $this->getCreatedAt()->getTimestamp(); 138 | } 139 | 140 | /** 141 | * Adds MessageMetadata to the metadata collection. 142 | * 143 | * @param MessageMetadata $meta 144 | */ 145 | public function addMetadata(MessageMetadata $meta) 146 | { 147 | $this->metadata->add($meta); 148 | } 149 | 150 | /** 151 | * Get the MessageMetadata for a participant. 152 | * 153 | * @param ParticipantInterface $participant 154 | * 155 | * @return MessageMetadata 156 | */ 157 | public function getMetadataForParticipant(ParticipantInterface $participant) 158 | { 159 | foreach ($this->metadata as $meta) { 160 | if ($meta->getParticipant()->getId() == $participant->getId()) { 161 | return $meta; 162 | } 163 | } 164 | 165 | return null; 166 | } 167 | 168 | /** 169 | * {@inheritdoc} 170 | */ 171 | public function isReadByParticipant(ParticipantInterface $participant) 172 | { 173 | if ($meta = $this->getMetadataForParticipant($participant)) { 174 | return $meta->getIsRead(); 175 | } 176 | 177 | return false; 178 | } 179 | 180 | /** 181 | * {@inheritdoc} 182 | */ 183 | public function setIsReadByParticipant(ParticipantInterface $participant, $isRead) 184 | { 185 | if (!$meta = $this->getMetadataForParticipant($participant)) { 186 | throw new \InvalidArgumentException(sprintf('No metadata exists for participant with id "%s"', $participant->getId())); 187 | } 188 | 189 | $meta->setIsRead($isRead); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /FormHandler/AbstractMessageFormHandler.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | abstract class AbstractMessageFormHandler 21 | { 22 | protected $request; 23 | protected $composer; 24 | protected $sender; 25 | protected $participantProvider; 26 | 27 | /** 28 | * @param Request|RequestStack $request 29 | * @param ComposerInterface $composer 30 | * @param SenderInterface $sender 31 | * @param ParticipantProviderInterface $participantProvider 32 | */ 33 | public function __construct($request, ComposerInterface $composer, SenderInterface $sender, ParticipantProviderInterface $participantProvider) 34 | { 35 | if ($request instanceof Request) { 36 | @trigger_error(sprintf('Using an instance of "%s" as first parameter of "%s" is deprecated since version 1.3 and won\'t be supported in 2.0. Use an instance of "Symfony\Component\HttpFoundation\RequestStack" instead.', get_class($request), __METHOD__), E_USER_DEPRECATED); 37 | } elseif (!$request instanceof RequestStack) { 38 | throw new \InvalidArgumentException(sprintf('AbstractMessageFormHandler expected a Request or RequestStack, %s given', is_object($request) ? get_class($request) : gettype($request))); 39 | } 40 | 41 | $this->request = $request; 42 | $this->composer = $composer; 43 | $this->sender = $sender; 44 | $this->participantProvider = $participantProvider; 45 | } 46 | 47 | /** 48 | * Processes the form with the request. 49 | * 50 | * @param Form $form 51 | * 52 | * @return MessageInterface|false the sent message if the form is bound and valid, false otherwise 53 | */ 54 | public function process(Form $form) 55 | { 56 | $request = $this->getCurrentRequest(); 57 | 58 | if ('POST' !== $request->getMethod()) { 59 | return false; 60 | } 61 | 62 | $form->handleRequest($request); 63 | 64 | if ($form->isValid()) { 65 | return $this->processValidForm($form); 66 | } 67 | 68 | return false; 69 | } 70 | 71 | /** 72 | * Processes the valid form, sends the message. 73 | * 74 | * @param Form $form 75 | * 76 | * @return MessageInterface the sent message 77 | */ 78 | public function processValidForm(Form $form) 79 | { 80 | $message = $this->composeMessage($form->getData()); 81 | $this->sender->send($message); 82 | 83 | return $message; 84 | } 85 | 86 | /** 87 | * Composes a message from the form data. 88 | * 89 | * @param AbstractMessage $message 90 | * 91 | * @return MessageInterface the composed message ready to be sent 92 | */ 93 | abstract protected function composeMessage(AbstractMessage $message); 94 | 95 | /** 96 | * Gets the current authenticated user. 97 | * 98 | * @return ParticipantInterface 99 | */ 100 | protected function getAuthenticatedParticipant() 101 | { 102 | return $this->participantProvider->getAuthenticatedParticipant(); 103 | } 104 | 105 | /** 106 | * BC layer to retrieve the current request directly or from a stack. 107 | * 108 | * @return Request 109 | */ 110 | private function getCurrentRequest() 111 | { 112 | if (!$this->request) { 113 | throw new \RuntimeException('Current request was not provided to the form handler.'); 114 | } 115 | 116 | if ($this->request instanceof Request) { 117 | return $this->request; 118 | } 119 | 120 | if (!$this->request->getCurrentRequest()) { 121 | throw new \RuntimeException('Request stack provided to the form handler did not contains a current request.'); 122 | } 123 | 124 | return $this->request->getCurrentRequest(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | getRootNode(); 25 | } else { 26 | $rootNode = $treeBuilder->root(self::ROOT_NAME); 27 | } 28 | 29 | $rootNode 30 | ->children() 31 | ->scalarNode('db_driver')->cannotBeOverwritten()->isRequired()->cannotBeEmpty()->end() 32 | ->scalarNode('thread_class')->isRequired()->cannotBeEmpty()->end() 33 | ->scalarNode('message_class')->isRequired()->cannotBeEmpty()->end() 34 | ->scalarNode('message_manager')->defaultValue('fos_message.message_manager.default')->cannotBeEmpty()->end() 35 | ->scalarNode('thread_manager')->defaultValue('fos_message.thread_manager.default')->cannotBeEmpty()->end() 36 | ->scalarNode('sender')->defaultValue('fos_message.sender.default')->cannotBeEmpty()->end() 37 | ->scalarNode('composer')->defaultValue('fos_message.composer.default')->cannotBeEmpty()->end() 38 | ->scalarNode('provider')->defaultValue('fos_message.provider.default')->cannotBeEmpty()->end() 39 | ->scalarNode('participant_provider')->defaultValue('fos_message.participant_provider.default')->cannotBeEmpty()->end() 40 | ->scalarNode('authorizer')->defaultValue('fos_message.authorizer.default')->cannotBeEmpty()->end() 41 | ->scalarNode('message_reader')->defaultValue('fos_message.message_reader.default')->cannotBeEmpty()->end() 42 | ->scalarNode('thread_reader')->defaultValue('fos_message.thread_reader.default')->cannotBeEmpty()->end() 43 | ->scalarNode('deleter')->defaultValue('fos_message.deleter.default')->cannotBeEmpty()->end() 44 | ->scalarNode('spam_detector')->defaultValue('fos_message.noop_spam_detector')->cannotBeEmpty()->end() 45 | ->scalarNode('twig_extension')->defaultValue('fos_message.twig_extension.default')->cannotBeEmpty()->end() 46 | ->scalarNode('user_transformer')->defaultValue('fos_user.user_to_username_transformer')->cannotBeEmpty()->end() 47 | ->arrayNode('search') 48 | ->addDefaultsIfNotSet() 49 | ->children() 50 | ->scalarNode('query_factory')->defaultValue('fos_message.search_query_factory.default')->cannotBeEmpty()->end() 51 | ->scalarNode('finder')->defaultValue('fos_message.search_finder.default')->cannotBeEmpty()->end() 52 | ->scalarNode('query_parameter')->defaultValue('q')->cannotBeEmpty()->end() 53 | ->end() 54 | ->end() 55 | ->arrayNode('new_thread_form') 56 | ->addDefaultsIfNotSet() 57 | ->children() 58 | ->scalarNode('factory')->defaultValue('fos_message.new_thread_form.factory.default')->cannotBeEmpty()->end() 59 | ->scalarNode('type')->defaultValue('FOS\MessageBundle\FormType\NewThreadMessageFormType')->cannotBeEmpty()->end() 60 | ->scalarNode('handler')->defaultValue('fos_message.new_thread_form.handler.default')->cannotBeEmpty()->end() 61 | ->scalarNode('name')->defaultValue('message')->cannotBeEmpty()->end() 62 | ->scalarNode('model')->defaultValue('FOS\MessageBundle\FormModel\NewThreadMessage')->end() 63 | ->end() 64 | ->end() 65 | ->arrayNode('reply_form') 66 | ->addDefaultsIfNotSet() 67 | ->children() 68 | ->scalarNode('factory')->defaultValue('fos_message.reply_form.factory.default')->cannotBeEmpty()->end() 69 | ->scalarNode('type')->defaultValue('FOS\MessageBundle\FormType\ReplyMessageFormType')->cannotBeEmpty()->end() 70 | ->scalarNode('handler')->defaultValue('fos_message.reply_form.handler.default')->cannotBeEmpty()->end() 71 | ->scalarNode('name')->defaultValue('message')->cannotBeEmpty()->end() 72 | ->scalarNode('model')->defaultValue('FOS\MessageBundle\FormModel\ReplyMessage')->end() 73 | ->end() 74 | ->end() 75 | ->end(); 76 | 77 | return $treeBuilder; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Resources/doc/01a-orm-models.md: -------------------------------------------------------------------------------- 1 | Concrete classes for Doctrine ORM 2 | ================================= 3 | 4 | This page lists some example implementations of FOSMessageBundle models for the Doctrine 5 | ORM. 6 | 7 | Given the examples below with their namespaces and class names, you need to configure 8 | FOSMessageBundle to tell them about these classes. 9 | 10 | Add the following to your `app/config/config.yml` file. 11 | 12 | ```yaml 13 | # app/config/config.yml 14 | 15 | fos_message: 16 | db_driver: orm 17 | thread_class: AppBundle\Entity\Thread 18 | message_class: AppBundle\Entity\Message 19 | ``` 20 | 21 | [Continue with the installation][] 22 | 23 | Message class 24 | ------------- 25 | 26 | ```php 27 |