diff --git a/components/Posting/Posting.php b/components/Posting/Posting.php index b24cd4cd55..656334f9ed 100644 --- a/components/Posting/Posting.php +++ b/components/Posting/Posting.php @@ -88,7 +88,7 @@ class Posting extends Module * $actor_id, possibly as a reply to note $reply_to and with flag * $is_local. Sanitizes $content and $attachments */ - public static function storeNote(int $actor_id, string $content, array $attachments, bool $is_local, ?int $reply_to = null, ?int $repeat_of = null) + public static function storeNote(int $actor_id, ?string $content, array $attachments, bool $is_local, ?int $reply_to = null, ?int $repeat_of = null) { $note = Note::create([ 'gsactor_id' => $actor_id, diff --git a/plugins/Poll/Controller/NewPoll.php b/plugins/Poll/Controller/NewPoll.php index 8aee7ce8fd..87143e046f 100644 --- a/plugins/Poll/Controller/NewPoll.php +++ b/plugins/Poll/Controller/NewPoll.php @@ -22,12 +22,19 @@ namespace Plugin\Poll\Controller; use App\Core\DB\DB; +use App\Core\Form; +use function App\Core\I18n\_m; use App\Core\Security; +use App\Entity\Note; use App\Entity\Poll; use App\Util\Common; use App\Util\Exception\InvalidFormException; use App\Util\Exception\RedirectException; use Plugin\Poll\Forms\NewPollForm; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\HttpFoundation\Request; /** @@ -61,19 +68,43 @@ class NewPoll { $user = Common::ensureLoggedIn(); $numOptions = Common::clamp($num,MIN_OPTS,MAX_OPTS); - $form = NewPollForm::make($numOptions); + //$form = NewPollForm::make($numOptions); + /* + $opts = + ['visibility', ChoiceType::class, ['label' => _m('Visibility:'), 'expanded' => true, 'choices' => [_m('Public') => 'public', _m('Instance') => 'instance', _m('Private') => 'private']]], + // ['to', ChoiceType::class, ['label' => _m('To:'), 'multiple' => true, 'expanded' => true, 'choices' => $to_tags]], + ['post', SubmitType::class, ['label' => _m('Post')]], + ]; + */ + $opts[] = ['visibility', ChoiceType::class, ['label' => _m('Visibility:'), 'expanded' => true, 'choices' => [_m('Public') => 'public', _m('Instance') => 'instance', _m('Private') => 'private']]]; + $opts[] = ['Question', TextType::class, ['label' => _m(('Question'))]]; + + for ($i = 1; $i <= $numOptions; ++$i) { + //['Option_i', TextType::class, ['label' => _m('Option i')]], + $opts[] = ['Option_' . $i, TextType::class, ['label' => _m(('Option ' . $i))]]; + } + //$subForm = Form::create($subOpts); + //$opts[] = ['options',FormType::class,[$subForm]]; + $opts[] = ['post_poll', SubmitType::class, ['label' => _m('Post')]]; + + $form = Form::create($opts); + $form->handleRequest($request); $opt = []; if ($form->isSubmitted()) { if ($form->isValid()) { $data = $form->getData(); + + $note = Note::create(['gsactor_id' => $user->getId(), $is_local = true]); + DB::persist($note); + Security::sanitize($question = $data['Question']); for ($i = 1; $i <= $numOptions; ++$i) { Security::sanitize($opt[$i - 1] = $data['Option_' . $i]); } $options = implode("\n",$opt); - $poll = Poll::create(['gsactor_id' => $user->getId(), 'question' => $question, 'options' => $options]); + $poll = Poll::create(['gsactor_id' => $user->getId(), 'question' => $question, 'options' => $options, 'note_id' => $note->getId()]); DB::persist($poll); DB::flush(); throw new RedirectException('showpoll', ['id' => $poll->getId()]); diff --git a/plugins/Poll/Controller/ShowPoll.php b/plugins/Poll/Controller/ShowPoll.php index 9d717faa55..253a8cab5a 100644 --- a/plugins/Poll/Controller/ShowPoll.php +++ b/plugins/Poll/Controller/ShowPoll.php @@ -21,8 +21,10 @@ namespace Plugin\Poll\Controller; +use App\Entity\Note; use App\Entity\Poll; use App\Util\Common; +use App\Util\Exception\NoSuchNoteException; use App\Util\Exception\NotFoundException; use Symfony\Component\HttpFoundation\Request; @@ -55,6 +57,12 @@ class ShowPoll $poll = Poll::getFromId((int) $id); + $note = Note::getFromId($poll->getNoteId()); + + if (!$note->isVisibleTo($user)) { + throw new NoSuchNoteException(); + } + if ($poll == null) { throw new NotFoundException('Poll does not exist'); } diff --git a/plugins/Poll/Forms/PollResponseForm.php b/plugins/Poll/Forms/PollResponseForm.php index 9c0ca4e61a..a95b64d82c 100644 --- a/plugins/Poll/Forms/PollResponseForm.php +++ b/plugins/Poll/Forms/PollResponseForm.php @@ -27,7 +27,6 @@ use App\Entity\Poll; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\HiddenType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; -use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Form as SymfForm; /** @@ -53,20 +52,18 @@ class PollResponseForm extends Form public static function make(Poll $poll,int $noteId): SymfForm { $opts = $poll->getOptionsArr(); - $question = $poll->getQuestion(); $formOptions = []; $options = []; for ($i = 1; $i <= count($opts); ++$i) { $options[$opts[$i - 1]] = $i; } $formOptions = [ - ['Question', TextType::class, ['data' => $question, 'label' => _m(('Question')), 'disabled' => true]], - ['Options:', ChoiceType::class, [ + ['Options' . $poll->getId(), ChoiceType::class, [ 'choices' => $options, 'expanded' => true, ]], ['note_id', HiddenType::class, ['data' => $noteId]], - ['pollresponse', SubmitType::class, ['label' => _m('Submit')]], + ['pollresponse', SubmitType::class, ['label' => _m('Vote')]], ]; return parent::create($formOptions); } diff --git a/plugins/Poll/Poll.php b/plugins/Poll/Poll.php index 2ff13e6287..0752bcf09b 100644 --- a/plugins/Poll/Poll.php +++ b/plugins/Poll/Poll.php @@ -22,20 +22,17 @@ namespace Plugin\Poll; use App\Core\DB\DB; use App\Core\Event; -use App\Core\Form; -use function App\Core\I18n\_m; use App\Core\Module; use App\Core\Router\RouteLoader; use App\Entity\Note; -use App\Entity\Poll as PollEntity; use App\Entity\PollResponse; use App\Util\Common; use App\Util\Exception\InvalidFormException; +use App\Util\Exception\NotFoundException; +use App\Util\Exception\RedirectException; use App\Util\Exception\ServerException; use Plugin\Poll\Forms\PollResponseForm; use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; -use Symfony\Component\Form\Extension\Core\Type\NumberType; -use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\HttpFoundation\Request; /** @@ -87,19 +84,41 @@ class Poll extends Module return Event::next; } + public function onStartShowStyles(array &$styles): bool + { + $styles[] = 'poll/poll.css'; + return Event::next; + } + /** * Display a poll in the timeline */ - public function onShowNoteContent(Request $request, Note $note, array &$actions) + public function onShowNoteContent(Request $request, Note $note, array &$test) { - $user = Common::ensureLoggedIn(); - $poll = PollEntity::getFromId(21); + $responses = null; + $formView = null; + try { + $poll = DB::findOneBy('poll', ['note_id' => $note->getId()]); + } catch (NotFoundException $e) { + return Event::next; + } - if (!PollResponse::exits($poll->getId(), $user->getId())) { - $form = PollResponseForm::make($poll, $note->getId()); - $ret = self::noteActionHandle($request, $form, $note, 'pollresponse', function ($note, $data) { + if (Common::isLoggedIn() && !PollResponse::exits($poll->getId(), Common::ensureLoggedIn()->getId())) { + $form = PollResponseForm::make($poll, $note->getId()); + $formView = $form->createView(); + $ret = self::noteActionHandle($request, $form, $note, 'pollresponse', /** TODO Documentation */ function ($note, $data) { $user = Common::ensureLoggedIn(); - $poll = PollEntity::getFromId(21); //substituir por get from note + + try { + $poll = DB::findOneBy('poll', ['note_id' => $note->getId()]); + } catch (NotFoundException $e) { + return Event::next; + } + + if (PollResponse::exits($poll->getId(), $user->getId())) { + return Event::next; + } + $selection = array_values($data)[1]; if (!$poll->isValidSelection($selection)) { throw new InvalidFormException(); @@ -110,20 +129,17 @@ class Poll extends Module $pollResponse = PollResponse::create(['poll_id' => $poll->getId(), 'gsactor_id' => $user->getId(), 'selection' => $selection]); DB::persist($pollResponse); DB::flush(); - return Event::stop; + + throw new RedirectException(); }); - } else { - $options[] = ['Question', TextType::class, ['data' => $poll->getQuestion(), 'label' => _m(('Question')), 'disabled' => true]]; - $responses = $poll->countResponses(); - $i = 0; - foreach ($responses as $option => $num) { - //['Option_i', TextType::class, ['label' => _m('Option i')]], - $options[] = ['Option_' . $i, NumberType::class, ['data' => $num, 'label' => $option, 'disabled' => true]]; - ++$i; + if ($ret != null) { + return $ret; } - $form = Form::create($options); + } else { + $responses = $poll->countResponses(); } - $actions[] = $form->createView(); + //$test[] = $form->createView(); + $test[] = ['name' => 'Poll', 'vars' => ['question' => $poll->getQuestion(), 'responses' => $responses, 'form' => $formView]]; return Event::next; } } diff --git a/public/assets/css/network/public.css b/public/assets/css/network/public.css index f0aeec3782..b833d1492a 100644 --- a/public/assets/css/network/public.css +++ b/public/assets/css/network/public.css @@ -284,10 +284,27 @@ margin-left: 0.2em; } + +/* top right */ +.create-top-right{ + display: flex; + justify-content: space-between; + color: var(--fg); + background-color: var(--bg1); + border-radius: 0 var(--unit-size) 0 0; + width: 100%; + box-sizing: border-box; +} + +/* tabs */ + +.tabs{ + padding: var(--unit-size); +} + + /* scope options */ .scope { - order: 1; - width: 100%; display: flex; flex-wrap: wrap; color: var(--fg); diff --git a/public/assets/css/network/public_mid.css b/public/assets/css/network/public_mid.css index 05898c352b..846104ae6b 100644 --- a/public/assets/css/network/public_mid.css +++ b/public/assets/css/network/public_mid.css @@ -284,16 +284,32 @@ margin-left: 0.2em; } +.create-top-right{ + display: flex; + justify-content: space-between; + color: var(--fg); + background-color: var(--bg1); + border-radius: 0 var(--unit-size) 0 0; + width: 100%; + box-sizing: border-box; +} + +/* tabs */ + +.tabs{ + padding: var(--unit-size); +} + + /* scope options */ .scope { - order: 1; - width: 100%; display: flex; flex-wrap: wrap; color: var(--fg); background-color: var(--bg1); border-radius: 0 var(--unit-size) 0 0; } + .scope > div { display: inline; vertical-align: middle; diff --git a/public/assets/css/network/public_small.css b/public/assets/css/network/public_small.css index 02ddb9f573..88d9fdcfc8 100644 --- a/public/assets/css/network/public_small.css +++ b/public/assets/css/network/public_small.css @@ -284,10 +284,25 @@ margin-left: 0.2em; } +.create-top-right{ + display: flex; + justify-content: space-between; + color: var(--fg); + background-color: var(--bg1); + border-radius: 0 var(--unit-size) 0 0; + width: 100%; + box-sizing: border-box; +} + +/* tabs */ + +.tabs{ + padding: var(--unit-size); +} + + /* scope options */ .scope { - order: 1; - width: 100%; display: flex; flex-wrap: wrap; color: var(--fg); diff --git a/public/assets/css/poll/poll.css b/public/assets/css/poll/poll.css new file mode 100644 index 0000000000..1d7c473b20 --- /dev/null +++ b/public/assets/css/poll/poll.css @@ -0,0 +1,83 @@ +.poll .poll-question{ + font-weight: 700; +} + +.poll #pollresponse_Options{ + display:flex; + flex-direction: column; +} + +#pollresponse > * > label{ + vertical-align:top; + display: none; +} + + +.poll .form-single , +.create-poll-notice .form-single{ + padding: 0 var(--unit-size); + height: 100%; + width: 100%; + border-radius: var(--small-size); +} + + +/* FORMS ------------------------------*/ +.poll label { + display: inline-block; + font-family: 'Montserrat', sans-serif; + width: 100%; + padding: 0.5em 0.1em; +} + +.poll input[type=text], +.create-poll-notice input[type=text]{ + margin-top: calc(var(--unit-size) * 0.5); + width: calc(100% - var(--unit-size)); + background-color: var(--bg2); + box-shadow: var(--shadow); + border: solid 2px var(--accent-low); + padding: calc(var(--unit-size) * 0.5) calc(var(--unit-size) * 0.5); + color: var(--fg); + border-radius: calc(var(--unit-size) * 0.5); + font-size: var(--medium-size); + box-sizing: border-box; +} + + +.poll button[type=submit] , +.create-poll-notice button[type=submit]{ + background: var(--bg1); + padding: calc(var(--unit-size) * 0.8) calc(var(--unit-size) * 2); + color: var(--fg); + border-style: solid; + border-color: var(--accent); + border-radius: var(--unit-size); + border-width: 2px; + font-family: 'Montserrat', sans-serif; + font-size: var(--small-size); + font-weight: 700; + margin-top: 1em; +} + +#post_poll_visibility { + font-size: var(--medium-size); + color: var(--fg); +} + +.create-poll-notice{ + padding: var(--unit-size); +} + +#post_poll > *{ + padding: calc(var(--unit-size) * 0.2); +} + +#post_poll_visibility > *{ + padding-right: calc(var(--unit-size) * 1); +} + +.create-poll-notice { + width: 100%; +} + diff --git a/src/Entity/Poll.php b/src/Entity/Poll.php index 18f8895ab1..62bfb2053e 100644 --- a/src/Entity/Poll.php +++ b/src/Entity/Poll.php @@ -42,6 +42,7 @@ class Poll extends Entity private int $id; private ?string $uri; private ?int $gsactor_id; + private int $note_id; private ?string $question; private ?string $options; private DateTimeInterface $created; @@ -80,6 +81,17 @@ class Poll extends Entity return $this->gsactor_id; } + public function setNoteId(int $note_id): self + { + $this->note_id = $note_id; + return $this; + } + + public function getNoteId(): int + { + return $this->note_id; + } + public function setQuestion(?string $question): self { $this->question = $question; @@ -137,19 +149,19 @@ class Poll extends Entity 'name' => 'poll', 'description' => 'Per-notice poll data for Poll plugin', 'fields' => [ - 'id' => ['type' => 'serial', 'not null' => true], - 'uri' => ['type' => 'varchar', 'length' => 191], - // 'uri' => ['type' => 'varchar', 'length' => 191, 'not null' => true], + 'id' => ['type' => 'serial', 'not null' => true], + 'uri' => ['type' => 'varchar', 'length' => 191], 'gsactor_id' => ['type' => 'int'], + 'note_id' => ['type' => 'int', 'not null' => true], 'question' => ['type' => 'text'], 'options' => ['type' => 'text'], 'created' => ['type' => 'datetime', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was created'], 'modified' => ['type' => 'timestamp', 'not null' => true, 'default' => 'CURRENT_TIMESTAMP', 'description' => 'date this record was modified'], ], 'primary key' => ['id'], - // 'unique keys' => [ - // 'poll_uri_key' => ['uri'], - // ], + 'unique keys' => [ + 'poll_note_id' => ['note_id'], + ], ]; } diff --git a/src/Twig/Extension.php b/src/Twig/Extension.php index 7ecb008a69..032ac10b3c 100644 --- a/src/Twig/Extension.php +++ b/src/Twig/Extension.php @@ -50,6 +50,7 @@ class Extension extends AbstractExtension new TwigFunction('is_route', [Runtime::class, 'isCurrentRoute']), new TwigFunction('get_note_actions', [Runtime::class, 'getNoteActions']), new TwigFunction('get_note_test', [Runtime::class, 'getNoteTest']), + new TwigFunction('get_show_styles', [Runtime::class, 'getShowStyles']), new TwigFunction('config', [Runtime::class, 'getConfig']), new TwigFunction('icon', [Runtime::class, 'embedSvgIcon'], ['needs_environment' => true]), ]; diff --git a/src/Twig/Runtime.php b/src/Twig/Runtime.php index ce034d179f..4127856935 100644 --- a/src/Twig/Runtime.php +++ b/src/Twig/Runtime.php @@ -31,15 +31,11 @@ namespace App\Twig; use App\Core\Event; -use App\Core\Form; -use function App\Core\I18n\_m; use App\Entity\Note; use App\Util\Common; use App\Util\Formatting; use Functional as F; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\Form\Extension\Core\Type\NumberType; -use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -79,25 +75,7 @@ class Runtime implements RuntimeExtensionInterface, EventSubscriberInterface { $test = []; Event::handle('show_note_content', [$this->request, $note, &$test]); - /* - $options = []; - $options[0] = ['Question', TextType::class, ['data'=>'fav color?','label' => _m(('Question')),'disabled'=>true]]; - - $options[1] = ['Option_1', NumberType::class, ['data'=>3,'label' => 'blue','disabled'=>true]]; - */ - /* - $options[0] = ['Question', TextType::class, ['data'=>'fav color?','label' => _m(('Question')),'disabled'=>true]]; - $options[1] = ['Options:', ChoiceType::class, [ - 'choices' => ['blue','green'], - 'expanded' => true, - ]]; - $options[2] = ['save', SubmitType::class, ['label' => _m('Submit')]]; - */ - /* - $form = Form::create($options); - $test[0] = $form->createView(); - */ return $test; } @@ -106,6 +84,13 @@ class Runtime implements RuntimeExtensionInterface, EventSubscriberInterface return Common::config(...$args); } + public function getShowStyles() + { + $styles = []; + Event::handle('start_show_styles',[&$styles]); + return $styles; + } + // ---------------------------------------------------------- // Request is not a service, can't find a better way to get it diff --git a/src/Util/Common.php b/src/Util/Common.php index d8ce3a5768..cdf04b30d1 100644 --- a/src/Util/Common.php +++ b/src/Util/Common.php @@ -108,6 +108,11 @@ abstract class Common } } + public static function isLoggedIn(): bool + { + return self::user() != null; + } + /** * Is the given string identical to a system path or route? * This could probably be put in some other class, but at diff --git a/templates/Poll/newpoll.html.twig b/templates/Poll/newpoll.html.twig index d7c1bbbaf9..2b1d0289bc 100644 --- a/templates/Poll/newpoll.html.twig +++ b/templates/Poll/newpoll.html.twig @@ -2,32 +2,42 @@ {% extends 'left/left.html.twig' %} {% block meta %} - {{ parent() }} + {{ parent() }} {% endblock %} -{% block title %}New Poll{% endblock %} +{% block title %}Welcome!{% endblock %} {% block stylesheets %} - {{ parent() }} - - - + {{ parent() }} + + + + {% endblock %} {% block header %} - {{ parent() }} + {{ parent() }} {% endblock %} {% block left %} - {{ parent() }} + {{ parent() }} {% endblock %} {% block body %} -
- {{ form(form) }} + {{ parent() }} +
+
+
+ {% block form %} +
+ {{ form(form) }} +
+ {% endblock form %} +
{% endblock body %} diff --git a/templates/Poll/view.html.twig b/templates/Poll/view.html.twig new file mode 100644 index 0000000000..0e0ce31ed6 --- /dev/null +++ b/templates/Poll/view.html.twig @@ -0,0 +1,17 @@ + +
+

{{ vars.question }}

+ {% if vars.form is not null %} + {% block form %} +
+ {{ form(vars.form) }} +
+ {% endblock form %} + {% else %} +
    + {% for question, votes in vars.responses %} +
  • {{ question }}: {{ votes }}
  • + {% endfor %} +
+ {% endif %} +
\ No newline at end of file diff --git a/templates/base.html.twig b/templates/base.html.twig index aa1f8110a3..020aba4a4c 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -14,9 +14,13 @@ + {% for stylesheet in get_show_styles() %} + + {% endfor %} {% endblock %} +
{% block header %}
-
- {{ form_row(post_form.visibility) }} - - - {% for tab in tabs %} - {{ tab['title'] }} - {% endfor %} +
+
+ {{ form_row(post_form.visibility) }} +
+
+ {% for tab in tabs %} + {{ tab['title'] }} + {% endfor %} +
diff --git a/templates/note/view.html.twig b/templates/note/view.html.twig index 431d09c3d5..ad98e4adba 100644 --- a/templates/note/view.html.twig +++ b/templates/note/view.html.twig @@ -12,14 +12,11 @@
{{ note.getContent() }} - - - {% if have_user %} +
{% for test in get_note_test(note) %} - {{ form(test) }} + {% include '/'~ test.name ~ '/view.html.twig' with {'vars': test.vars} only %} {% endfor %} - {% endif %} - +
{% for attachment in note.getAttachments() %} {% if attachment.mimetype starts with 'image/' %}