controllers/MotionController.php
Rama main · réplica de lectura del repositorio original.
<?php
namespace app\controllers;
use app\components\RequestContext;
use app\components\UrlHelper;
use app\models\db\{Amendment,
ConsultationLog,
ConsultationSettingsMotionSection,
IProposal,
ISupporter,
Motion,
MotionSupporter,
SpeechQueue,
User,
UserNotification};
use app\models\api\imotion\MotionUpdateRequest;
use app\models\http\{HtmlErrorResponse, HtmlResponse, RedirectResponse, ResponseInterface};
use app\models\settings\{PrivilegeQueryContext, Privileges};
use app\models\exceptions\{ExceptionBase, FormError, Inconsistency, Internal, ResponseException};
use app\models\api\imotion\MotionCreateRequest;
use app\models\forms\MotionEditForm;
use app\models\sectionTypes\ISectionType;
use app\models\MotionSectionChanges;
use app\models\events\MotionEvent;
use yii\web\NotFoundHttpException;
class MotionController extends Base
{
use MotionActionsTrait;
use MotionMergingTrait;
use MotionExportTraits;
public const VIEW_ID_VIEW = 'view';
public const VIEW_ID_VIEW_CHANGES = 'view-changes';
public const VIEW_ID_VIEW_PDF = 'pdf';
public const VIEW_ID_MERGING_STATUS_AJAX = 'merge-amendments-status-ajax';
public const VIEW_ID_MERGING_PUBLIC_AJAX = 'merge-amendments-public-ajax';
public function actionView(string $motionSlug, int $commentId = 0, ?string $procedureToken = null, ?int $proposalVersion = null): HtmlResponse
{
$this->layout = 'column2';
$motion = $this->getMotionWithCheck($motionSlug);
$this->motion = $motion;
if ($this->consultation->havePrivilege(Privileges::PRIVILEGE_SCREENING, null)) {
$adminEdit = UrlHelper::createUrl(['/admin/motion/update', 'motionId' => $motion->id]);
} else {
$adminEdit = null;
}
if (!$motion->isReadable()) {
return new HtmlResponse($this->render('view_not_visible', ['motion' => $motion, 'adminEdit' => $adminEdit]));
}
$textSections = $motion->getActiveSections(ISectionType::TYPE_TEXT_SIMPLE);
if (count($textSections) === 0) {
$commentWholeMotions = true;
} else {
$commentWholeMotions = false;
foreach ($textSections as $section) {
if ($section->getSettings()->hasComments === ConsultationSettingsMotionSection::COMMENTS_MOTION) {
$commentWholeMotions = true;
}
}
}
$motionViewParams = [
'motion' => $motion,
'openedComments' => $this->getOpenedComments($motion, $commentId),
'adminEdit' => $adminEdit,
'commentForm' => null,
'commentWholeMotions' => $commentWholeMotions,
'activeProposal' => $this->getActiveProposal($motion, $procedureToken, $proposalVersion),
'procedureToken' => $procedureToken,
];
try {
$this->performShowActions($motion, intval($commentId), $motionViewParams);
} catch (ResponseException $e) {
throw $e;
} catch (\Throwable $e) {
$this->getHttpSession()->setFlash('error', $e->getMessage());
}
$motionViewParams['supportStatus'] = MotionSupporter::getCurrUserSupportStatus($motion);
return new HtmlResponse($this->render('view', $motionViewParams));
}
private function getOpenedComments(Motion $motion, int $commentId): array
{
$openedComments = [];
if ($commentId > 0) {
foreach ($motion->getActiveSections(ISectionType::TYPE_TEXT_SIMPLE) as $section) {
foreach ($section->getTextParagraphObjects(false, true, true) as $paragraph) {
foreach ($paragraph->comments as $comment) {
if ($comment->id == $commentId) {
if (!isset($openedComments[$section->sectionId])) {
$openedComments[$section->sectionId] = [];
}
$openedComments[$section->sectionId][] = $paragraph->paragraphNo;
}
}
}
}
}
return $openedComments;
}
private function getActiveProposal(Motion $motion, ?string $procedureToken, ?int $proposalVersion): IProposal
{
if ($procedureToken) {
return $motion->getProposalByToken($procedureToken) ?? $motion->getLatestProposal();
}
if ($proposalVersion) {
foreach ($motion->proposals as $proposal) {
if ($proposal->version === $proposalVersion && $proposal->canEditLimitedProposedProcedure()) {
return $proposal;
}
}
}
return $motion->getLatestProposal();
}
public function actionViewChanges(string $motionSlug): ResponseInterface
{
$this->layout = 'column2';
$motion = $this->getMotionWithCheck($motionSlug);
$parentMotion = $motion->replacedMotion;
if (!$motion->isReadable()) {
return new HtmlResponse($this->render('view_not_visible', ['motion' => $motion, 'adminEdit' => false]));
}
if (!$parentMotion || !$parentMotion->isReadable()) {
$this->getHttpSession()->setFlash('error', 'The diff-view is not available');
return new RedirectResponse(UrlHelper::createMotionUrl($motion));
}
try {
$changes = MotionSectionChanges::motionToSectionChanges($parentMotion, $motion);
} catch (Inconsistency $e) {
$changes = [];
$this->getHttpSession()->setFlash('error', $e->getMessage());
}
return new HtmlResponse($this->render('view_changes', [
'newMotion' => $motion,
'oldMotion' => $parentMotion,
'changes' => $changes,
]));
}
public function actionCreateSelectStatutes(?string $motionTypeId = null, ?string $agendaItemId = null): ResponseInterface
{
$agendaItem = null;
$motionType = null;
if ($motionTypeId) {
$motionType = $this->consultation->getMotionType(intval($motionTypeId));
} elseif ($agendaItemId) {
$agendaItem = $this->consultation->getAgendaItem(intval($agendaItemId));
if ($agendaItem && $agendaItem->getMyMotionType()) {
$motionType = $agendaItem->getMyMotionType();
}
}
if (!$motionType) {
return new HtmlErrorResponse(400, 'No motion type found');
}
return new HtmlResponse($this->render('create_select_statutes', ['motionType' => $motionType, 'agendaItem' => $agendaItem]));
}
public function actionCreatedone(string $motionSlug, string $fromMode): string
{
$motion = $this->consultation->getMotion($motionSlug);
if (!$motion) {
$this->getHttpSession()->setFlash('error', \Yii::t('motion', 'err_not_found'));
return $this->redirect(UrlHelper::createUrl('consultation/index'));
}
if ($motion->getMyMotionType()->amendmentsOnly) {
return $this->render('create_done_amendments_only', ['motion' => $motion, 'mode' => $fromMode]);
} else {
return $this->render('create_done', ['motion' => $motion, 'mode' => $fromMode]);
}
}
public function actionCreateconfirm(string $motionSlug, string $fromMode): ResponseInterface
{
$motion = $this->consultation->getMotion($motionSlug);
if (!$motion || $motion->status !== Motion::STATUS_DRAFT) {
$this->getHttpSession()->setFlash('error', \Yii::t('motion', 'err_not_found'));
return new RedirectResponse(UrlHelper::createUrl('consultation/index'));
}
if (!$motion->canEditText()) {
$this->getHttpSession()->setFlash('error', \Yii::t('motion', 'err_edit_permission'));
return new RedirectResponse(UrlHelper::createUrl('consultation/index'));
}
$policy = $motion->getMyMotionType()->getMotionPolicy();
if (!$policy->checkCurrUserMotion()) {
// Could be past the deadline of submission by now, or an admin changing the permissions
return new HtmlErrorResponse(403, \Yii::t('motion', 'err_create_permission'));
}
if ($this->isPostSet('modify')) {
return new RedirectResponse(UrlHelper::createMotionUrl($motion, 'edit'));
}
if ($this->isPostSet('confirm')) {
$motion->trigger(Motion::EVENT_CREATED, new MotionEvent($motion));
if ($motion->status === Motion::STATUS_SUBMITTED_SCREENED) {
$motion->trigger(Motion::EVENT_PUBLISHED, new MotionEvent($motion));
}
if (User::getCurrentUser()) {
UserNotification::addNotification(
User::getCurrentUser(),
$this->consultation,
UserNotification::NOTIFICATION_AMENDMENT_MY_MOTION
);
}
return new RedirectResponse(UrlHelper::createMotionUrl($motion, 'createdone', ['fromMode' => $fromMode]));
} else {
$params = ['motion' => $motion, 'mode' => $fromMode];
$params['deleteDraftId'] = $this->getRequestValue('draftId');
return new HtmlResponse($this->render('create_confirm', $params));
}
}
public function actionEdit(string $motionSlug): ResponseInterface
{
$motion = $this->consultation->getMotion($motionSlug);
if (!$motion) {
$this->getHttpSession()->setFlash('error', \Yii::t('motion', 'err_not_found'));
return new RedirectResponse(UrlHelper::homeUrl());
}
if (!$motion->canEditText()) {
$this->getHttpSession()->setFlash('error', \Yii::t('motion', 'err_edit_permission'));
return new RedirectResponse(UrlHelper::homeUrl());
}
$form = MotionEditForm::createForUserEdit($motion);
$fromMode = ($motion->status === Motion::STATUS_DRAFT ? 'create' : 'edit');
if ($this->isPostSet('save')) {
$motion->flushCache(true);
try {
$updateDto = MotionUpdateRequest::fromWebRequest(RequestContext::getAllPostVars(), $_FILES, $motion->getMyMotionType());
$form->saveMotion($motion, $updateDto, []);
if ($motion->isVisible()) {
ConsultationLog::logCurrUser($this->consultation, ConsultationLog::MOTION_CHANGE, $motion->id);
}
if ($motion->status == Motion::STATUS_DRAFT) {
$nextUrl = UrlHelper::createMotionUrl($motion, 'createconfirm', ['fromMode' => $fromMode]);
return new RedirectResponse($nextUrl);
} else {
return new HtmlResponse($this->render('edit_done', ['motion' => $motion]));
}
} catch (FormError $e) {
$this->getHttpSession()->setFlash('error', $e->getMessage());
}
}
return new HtmlResponse($this->render(
'edit_form',
[
'mode' => $fromMode,
'form' => $form,
'consultation' => $this->consultation,
]
));
}
public function actionCreate(?int $motionTypeId = null, ?int $agendaItemId = null, ?int $cloneFrom = null): ResponseInterface
{
try {
$ret = MotionEditForm::getMotionTypeForCreate($this->consultation, $motionTypeId, $agendaItemId, $cloneFrom);
list($motionType, $agendaItem) = $ret;
} catch (ExceptionBase $e) {
$this->getHttpSession()->setFlash('error', $e->getMessage());
return new RedirectResponse(UrlHelper::homeUrl());
}
$policy = $motionType->getMotionPolicy();
if (!$policy->checkCurrUserMotion()) {
if ($policy->checkCurrUserMotion(true, true)) {
$loginUrl = UrlHelper::createLoginUrl([
'motion/create',
'motionTypeId' => $motionTypeId,
'agendaItemId' => $agendaItemId
]);
return new RedirectResponse($loginUrl);
} else {
return new HtmlErrorResponse(403, \Yii::t('motion', 'err_create_permission'));
}
}
$form = MotionEditForm::createForCreating($this->consultation, $motionType, $agendaItem);
$supportType = $motionType->getMotionSupportTypeClass();
$iAmAdmin = $this->consultation->havePrivilege(Privileges::PRIVILEGE_SCREENING, null);
if ($this->isPostSet('save')) {
try {
$createDto = MotionCreateRequest::fromWebRequest(RequestContext::getAllPostVars(), $_FILES, $motionType);
$motion = $form->createMotion($createDto, true);
// Supporting members are not collected in the form, but need to be copied a well
if ($supportType->collectSupportersBeforePublication() && $cloneFrom && $iAmAdmin) {
$adoptMotion = $this->consultation->getMotion($cloneFrom);
foreach ($adoptMotion->getSupporters(true) as $supp) {
$suppNew = new MotionSupporter();
$suppNew->setAttributes($supp->getAttributes());
$suppNew->id = null;
$suppNew->motionId = $motion->id;
$suppNew->extraData = $supp->extraData;
$suppNew->dateCreation = date('Y-m-d H:i:s');
if ($supp->isNonPublic()) {
$suppNew->setExtraDataEntry(ISupporter::EXTRA_DATA_FIELD_NON_PUBLIC, true);
}
$suppNew->save();
}
}
return new RedirectResponse(UrlHelper::createMotionUrl($motion, 'createconfirm', [
'fromMode' => 'create',
'draftId' => $this->getRequestValue('draftId'),
]));
} catch (FormError $e) {
$this->getHttpSession()->setFlash('error', $e->getMessage());
}
}
if ($cloneFrom > 0) {
// Using an existing motion as template is an admin function, and also exposes unpublished motions and contact data
$motion = $this->consultation->getMotion($cloneFrom);
if ($motion && (
$this->consultation->havePrivilege(Privileges::PRIVILEGE_SCREENING, PrivilegeQueryContext::motion($motion)) ||
$this->consultation->havePrivilege(Privileges::PRIVILEGE_MOTION_STATUS_EDIT, PrivilegeQueryContext::motion($motion))
)) {
$form->cloneSupporters($motion);
$form->cloneMotionText($motion);
}
}
if (count($form->supporters) === 0) {
$form->supporters[] = MotionSupporter::createInitiator($this->consultation, $supportType, $iAmAdmin);
}
return new HtmlResponse($this->render(
'edit_form',
[
'mode' => 'create',
'form' => $form,
'consultation' => $this->consultation,
]
));
}
public function actionWithdraw(string $motionSlug): ResponseInterface
{
$motion = $this->consultation->getMotion($motionSlug);
if (!$motion) {
$this->getHttpSession()->setFlash('error', \Yii::t('motion', 'err_not_found'));
return new RedirectResponse(UrlHelper::createUrl('consultation/index'));
}
if (!$motion->canWithdraw()) {
$this->getHttpSession()->setFlash('error', \Yii::t('motion', 'err_withdraw_permission'));
return new RedirectResponse(UrlHelper::createUrl('consultation/index'));
}
if ($this->isPostSet('cancel')) {
return new RedirectResponse(UrlHelper::createMotionUrl($motion));
}
if ($this->isPostSet('withdraw')) {
$motion->withdraw();
$this->getHttpSession()->setFlash('success', \Yii::t('motion', 'withdraw_done'));
return new RedirectResponse(UrlHelper::createMotionUrl($motion));
}
return new HtmlResponse($this->render('withdraw', ['motion' => $motion]));
}
public function actionAdminSpeech(string $motionSlug): ResponseInterface
{
$this->layout = 'column2';
$motion = $this->consultation->getMotion($motionSlug);
if (!$motion) {
$this->getHttpSession()->setFlash('error', \Yii::t('motion', 'err_not_found'));
return new RedirectResponse(UrlHelper::createUrl('consultation/index'));
}
$user = User::getCurrentUser();
if (!$user->hasPrivilege($this->consultation, Privileges::PRIVILEGE_SPEECH_QUEUES, null)) {
$this->getHttpSession()->setFlash('error', \Yii::t('motion', 'err_edit_permission'));
return new RedirectResponse(UrlHelper::createMotionUrl($motion));
}
if (count($motion->speechQueues) === 0) {
$speechQueue = SpeechQueue::createWithSubqueues($this->consultation, false);
$speechQueue->motionId = $motion->id;
$speechQueue->save();
} else {
$speechQueue = $motion->speechQueues[0];
}
return new HtmlResponse($this->render('@app/views/speech/admin-singlepage', ['queue' => $speechQueue]));
}
protected function guessRedirectByPrefix(string $prefix): ?string
{
$motion = Motion::findOne([
'consultationId' => $this->consultation->id,
'titlePrefix' => $prefix
]);
if ($motion && $motion->isReadable()) {
return $motion->getLink();
}
/** @var Amendment|null $amendment */
$amendment = Amendment::find()->joinWith('motionJoin')->where([
'motion.consultationId' => $this->consultation->id,
'amendment.titlePrefix' => $prefix,
])->one();
if ($amendment && $amendment->isReadable()) {
return $amendment->getLink();
}
return null;
}
/**
* URL: /[consultationPrefix]/[motionPrefix]
*
* @throws NotFoundHttpException
*/
public function actionGotoPrefix(string $prefix): RedirectResponse
{
$redirect = $this->guessRedirectByPrefix($prefix);
if ($redirect) {
return new RedirectResponse($redirect);
}
throw new NotFoundHttpException();
}
}