diff --git a/cool-jconon-backend/src/main/java/it/cnr/si/cool/jconon/rest/Call.java b/cool-jconon-backend/src/main/java/it/cnr/si/cool/jconon/rest/Call.java index 50006b886c..74d9326ef7 100644 --- a/cool-jconon-backend/src/main/java/it/cnr/si/cool/jconon/rest/Call.java +++ b/cool-jconon-backend/src/main/java/it/cnr/si/cool/jconon/rest/Call.java @@ -323,14 +323,23 @@ public Response firmaConvocazioni(@Context HttpServletRequest req, @FormParam("q @Path("invia-convocazioni") @Produces(MediaType.APPLICATION_JSON) public Response inviaConvocazioni(@Context HttpServletRequest req, @FormParam("query") String query, @FormParam("callId") String callId, - @FormParam("userNamePEC") String userName, @FormParam("passwordPEC") String password, @FormParam("addressFromApplication") AddressType addressFromApplication) throws IOException { + @FormParam("PEC") Boolean pec, @FormParam("userNamePEC") String userName, @FormParam("passwordPEC") String password, @FormParam("addressFromApplication") AddressType addressFromApplication) throws IOException { LOGGER.debug("Invia convocazioni from query:" + query); ResponseBuilder rb; Session session = cmisService.getCurrentCMISSession(req); try { - Long numConvocazioni = callService.inviaConvocazioni(session, cmisService.getCurrentBindingSession(req), query, Utility.getContextURL(req), + Long numConvocazioni = callService.inviaConvocazioni( + session, + cmisService.getCurrentBindingSession(req), + query, + Utility.getContextURL(req), cmisService.getCMISUserFromSession(req).getId(), - callId, userName, password, addressFromApplication); + callId, + userName, + password, + addressFromApplication, + pec + ); rb = Response.ok(Collections.singletonMap("numConvocazioni", numConvocazioni)); } catch (IOException e) { LOGGER.error(e.getMessage(), e); @@ -362,14 +371,14 @@ public Response firmaEsclusioni(@Context HttpServletRequest req, @FormParam("que @Path("invia-esclusioni") @Produces(MediaType.APPLICATION_JSON) public Response inviaEsclusioni(@Context HttpServletRequest req, @FormParam("query") String query, @FormParam("callId") String callId, - @FormParam("userNamePEC") String userName, @FormParam("passwordPEC") String password, @FormParam("addressFromApplication") AddressType addressFromApplication) throws IOException { + @FormParam("PEC") Boolean pec, @FormParam("userNamePEC") String userName, @FormParam("passwordPEC") String password, @FormParam("addressFromApplication") AddressType addressFromApplication) throws IOException { LOGGER.debug("Invia convocazioni from query:" + query); ResponseBuilder rb; Session session = cmisService.getCurrentCMISSession(req); try { Long numEsclusioni = callService.inviaEsclusioni(session, cmisService.getCurrentBindingSession(req), query, Utility.getContextURL(req), cmisService.getCMISUserFromSession(req).getId(), - callId, userName, password, addressFromApplication); + callId, userName, password, addressFromApplication, pec); rb = Response.ok(Collections.singletonMap("numEsclusioni", numEsclusioni)); } catch (IOException e) { LOGGER.error(e.getMessage(), e); @@ -420,6 +429,7 @@ public Response inviaAllegato(@Context HttpServletRequest req, @FormParam("objec @Path("invia-comunicazioni") @Produces(MediaType.APPLICATION_JSON) public Response inviaComunicazioni(@Context HttpServletRequest req, @FormParam("query") String query, @FormParam("callId") String callId, + @FormParam("PEC") Boolean pec, @FormParam("userNamePEC") String userName, @FormParam("passwordPEC") String password, @FormParam("addressFromApplication") AddressType addressFromApplication) throws IOException { @@ -429,7 +439,7 @@ public Response inviaComunicazioni(@Context HttpServletRequest req, @FormParam(" try { Long numComunicazioni = callService.inviaComunicazioni(session, cmisService.getCurrentBindingSession(req), query, Utility.getContextURL(req), cmisService.getCMISUserFromSession(req).getId(), - callId, userName, password, addressFromApplication); + callId, userName, password, addressFromApplication, pec); rb = Response.ok(Collections.singletonMap("numComunicazioni", numComunicazioni)); } catch (IOException e) { LOGGER.error(e.getMessage(), e); diff --git a/cool-jconon-backend/src/main/java/it/cnr/si/cool/jconon/service/call/CallService.java b/cool-jconon-backend/src/main/java/it/cnr/si/cool/jconon/service/call/CallService.java index 13232347a4..5f2ea50676 100644 --- a/cool-jconon-backend/src/main/java/it/cnr/si/cool/jconon/service/call/CallService.java +++ b/cool-jconon-backend/src/main/java/it/cnr/si/cool/jconon/service/call/CallService.java @@ -79,6 +79,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.mail.EmailAttachment; import org.apache.commons.mail.EmailException; +import org.apache.commons.mail.MultiPartEmail; import org.apache.commons.text.StrSubstitutor; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; @@ -1553,7 +1554,7 @@ public void verifyPEC(VerificaPECTask verificaPECTask) { } public Long inviaConvocazioni(Session session, BindingSession bindingSession, String query, String contexURL, String userId, - String callId, String userName, String password, AddressType addressFromApplication) throws IOException { + String callId, String userName, String password, AddressType addressFromApplication, Boolean pec) throws IOException { Folder call = (Folder) session.getObject(callId); ItemIterable convocazioni = session.query(query, false); long index = 0; @@ -1566,6 +1567,9 @@ public Long inviaConvocazioni(Session session, BindingSession bindingSession, St convocazioneObject.getProperty(CoolPropertyIds.ALFCMIS_NODEREF.value()).getValueAsString(), true); String contentURL = contexURL + "/rest/application/convocazione?nodeRef=" + convocazioneObject.getId(); + if (!pec) { + addressFromApplication = AddressType.EMAIL; + } String address = obtainAddress(convocazioneObject, "jconon_convocazione:email_pec", "jconon_convocazione:email", @@ -1581,16 +1585,29 @@ public Long inviaConvocazioni(Session session, BindingSession bindingSession, St .filter(Document.class::isInstance) .map(Document.class::cast) .collect(Collectors.toList()); - - SimplePECMail simplePECMail = new SimplePECMail(userName, password); - simplePECMail.setHostName(pecConfiguration.getHostSmtp()); - simplePECMail.setSubject(subject + " $$ " + convocazioneObject.getId()); - String content = "Con riferimento alla Sua domanda di partecipazione al concorso indicato in oggetto, si invia in allegato la relativa convocazione.
" + - "Per i candidati che non hanno indicato in domanda un indirizzo PEC o che non lo hanno comunicato in seguito, e' richiesta conferma di ricezione della presente cliccando sul seguente link ,
qualora non dovesse funzionare copi questo [" + contentURL + "] nella barra degli indirizzi del browser.
"; + String content = "Con riferimento alla Sua domanda di partecipazione al concorso indicato in oggetto, si invia in allegato la relativa convocazione.
"; + if (pec) { + content += "Per i candidati che non hanno indicato in domanda un indirizzo PEC o che non lo hanno comunicato in seguito, "; + } + content += "é richiesta conferma di ricezione della presente cliccando sul seguente link,
qualora non dovesse funzionare copi questo [\"" + contentURL + "\"] nella barra degli indirizzi del browser.

"; content += "Distinti saluti.



"; content += "Questo messaggio e' stato generato da un sistema automatico. Si prega di non rispondere.

"; + + MultiPartEmail simplePECMail = pec ? new SimplePECMail(userName, password):new MultiPartEmail(); try { - simplePECMail.setFrom(userName); + simplePECMail.setSubject(subject + " $$ " + convocazioneObject.getId()); + if (pec) { + simplePECMail.setHostName(pecConfiguration.getHostSmtp()); + simplePECMail.setFrom(userName); + } else { + final Optional smtpuser = Optional.ofNullable(env.getProperty("mail.smtp.user")).filter(s -> !s.isEmpty()); + final Optional smtppassword = Optional.ofNullable(env.getProperty("mail.smtp.password")).filter(s -> !s.isEmpty()); + if (smtpuser.isPresent() && smtppassword.isPresent()) { + simplePECMail.setAuthentication(smtpuser.get(),smtppassword.get()); + } + simplePECMail.setHostName(env.getProperty("mail.smtp.host")); + simplePECMail.setFrom(env.getProperty("mail.from.default")); + } simplePECMail.setReplyTo(Collections.singleton(new InternetAddress("undisclosed-recipients"))); simplePECMail.setTo(Collections.singleton(new InternetAddress(address))); simplePECMail.attach(new ByteArrayDataSource(new ByteArrayInputStream(content.getBytes()), @@ -1613,6 +1630,7 @@ public Long inviaConvocazioni(Session session, BindingSession bindingSession, St } } simplePECMail.send(); + Map properties = new HashMap(); properties.put(JCONON_CONVOCAZIONE_STATO, StatoComunicazione.SPEDITO.name()); convocazioneObject.updateProperties(properties); @@ -1661,13 +1679,15 @@ public Long inviaConvocazioni(Session session, BindingSession bindingSession, St } } } - callRepository.removeVerificaPECTask(subject); - callRepository.verificaPECTask(userName, password, subject, JCONON_CONVOCAZIONE_STATO); + if (pec) { + callRepository.removeVerificaPECTask(subject); + callRepository.verificaPECTask(userName, password, subject, JCONON_CONVOCAZIONE_STATO); + } return index; } public Long inviaEsclusioni(Session session, BindingSession bindingSession, String query, String contexURL, - String userId, String callId, String userName, String password, AddressType addressFromApplication) throws IOException { + String userId, String callId, String userName, String password, AddressType addressFromApplication, Boolean pec) throws IOException { Folder call = (Folder) session.getObject(callId); ItemIterable esclusioni = session.query(query, false); String subject = i18NService.getLabel("subject-info", Locale.ITALIAN) + @@ -1707,20 +1727,32 @@ public Long inviaEsclusioni(Session session, BindingSession bindingSession, Stri .filter(Document.class::isInstance) .map(Document.class::cast) .collect(Collectors.toList()); - + if (!pec) { + addressFromApplication = AddressType.EMAIL; + } String address = obtainAddress(esclusioneObject, "jconon_esclusione:email_pec", "jconon_esclusione:email", addressFromApplication); - - SimplePECMail simplePECMail = new SimplePECMail(userName, password); - simplePECMail.setHostName(pecConfiguration.getHostSmtp()); - simplePECMail.setSubject(subject + " $$ " + esclusioneObject.getId()); String content = "Con riferimento alla Sua domanda di partecipazione al concorso indicato in oggetto, si invia in allegato la relativa esclusione.
"; content += "Distinti saluti.



"; content += "Questo messaggio e' stato generato da un sistema automatico. Si prega di non rispondere.

"; + + MultiPartEmail simplePECMail = pec ? new SimplePECMail(userName, password):new MultiPartEmail(); try { - simplePECMail.setFrom(userName); + simplePECMail.setSubject(subject + " $$ " + esclusioneObject.getId()); + if (pec) { + simplePECMail.setHostName(pecConfiguration.getHostSmtp()); + simplePECMail.setFrom(userName); + } else { + final Optional smtpuser = Optional.ofNullable(env.getProperty("mail.smtp.user")).filter(s -> !s.isEmpty()); + final Optional smtppassword = Optional.ofNullable(env.getProperty("mail.smtp.password")).filter(s -> !s.isEmpty()); + if (smtpuser.isPresent() && smtppassword.isPresent()) { + simplePECMail.setAuthentication(smtpuser.get(),smtppassword.get()); + } + simplePECMail.setHostName(env.getProperty("mail.smtp.host")); + simplePECMail.setFrom(env.getProperty("mail.from.default")); + } simplePECMail.setReplyTo(Collections.singleton(new InternetAddress("undisclosed-recipients"))); simplePECMail.setTo(Collections.singleton(new InternetAddress(address))); simplePECMail.attach(new ByteArrayDataSource(new ByteArrayInputStream(content.getBytes()), @@ -1791,8 +1823,10 @@ public Long inviaEsclusioni(Session session, BindingSession bindingSession, Stri } } } - callRepository.removeVerificaPECTask(subject); - callRepository.verificaPECTask(userName, password, subject, JCONON_ESCLUSIONE_STATO); + if (pec) { + callRepository.removeVerificaPECTask(subject); + callRepository.verificaPECTask(userName, password, subject, JCONON_ESCLUSIONE_STATO); + } return index; } @@ -1866,7 +1900,7 @@ private String obtainAddress(Document document, String propertyEmailPec, String } public Long inviaComunicazioni(Session session, BindingSession bindingSession, String query, String contexURL, String userId, - String callId, String userName, String password, AddressType addressFromApplication) throws IOException { + String callId, String userName, String password, AddressType addressFromApplication, Boolean pec) throws IOException { Folder call = (Folder) session.getObject(callId); String subject = i18NService.getLabel("subject-info", Locale.ITALIAN) + i18NService.getLabel("subject-confirm-comunicazione", @@ -1879,6 +1913,9 @@ public Long inviaComunicazioni(Session session, BindingSession bindingSession, S aclService.setInheritedPermission(bindingSession, comunicazioneObject.getProperty(CoolPropertyIds.ALFCMIS_NODEREF.value()).getValueAsString(), true); + if (!pec) { + addressFromApplication = AddressType.EMAIL; + } String address = obtainAddress(comunicazioneObject, "jconon_comunicazione:email_pec", "jconon_comunicazione:email", @@ -1894,15 +1931,25 @@ public Long inviaComunicazioni(Session session, BindingSession bindingSession, S .filter(Document.class::isInstance) .map(Document.class::cast) .collect(Collectors.toList()); - - SimplePECMail simplePECMail = new SimplePECMail(userName, password); - simplePECMail.setHostName(pecConfiguration.getHostSmtp()); - simplePECMail.setSubject(subject + " $$ " + comunicazioneObject.getId()); String content = "Con riferimento alla Sua domanda di partecipazione al concorso indicato in oggetto, si invia in allegato la relativa comunicazione.
"; content += "Distinti saluti.



"; content += "Questo messaggio e' stato generato da un sistema automatico. Si prega di non rispondere.

"; + + MultiPartEmail simplePECMail = pec ? new SimplePECMail(userName, password):new MultiPartEmail(); try { - simplePECMail.setFrom(userName); + simplePECMail.setSubject(subject + " $$ " + comunicazioneObject.getId()); + if (pec) { + simplePECMail.setHostName(pecConfiguration.getHostSmtp()); + simplePECMail.setFrom(userName); + } else { + final Optional smtpuser = Optional.ofNullable(env.getProperty("mail.smtp.user")).filter(s -> !s.isEmpty()); + final Optional smtppassword = Optional.ofNullable(env.getProperty("mail.smtp.password")).filter(s -> !s.isEmpty()); + if (smtpuser.isPresent() && smtppassword.isPresent()) { + simplePECMail.setAuthentication(smtpuser.get(),smtppassword.get()); + } + simplePECMail.setHostName(env.getProperty("mail.smtp.host")); + simplePECMail.setFrom(env.getProperty("mail.from.default")); + } simplePECMail.setReplyTo(Collections.singleton(new InternetAddress("undisclosed-recipients"))); simplePECMail.setTo(Collections.singleton(new InternetAddress(address))); simplePECMail.attach(new ByteArrayDataSource(new ByteArrayInputStream(content.getBytes()), @@ -1974,10 +2021,11 @@ public Long inviaComunicazioni(Session session, BindingSession bindingSession, S } } } - } - callRepository.removeVerificaPECTask(subject); - callRepository.verificaPECTask(userName, password, subject, JCONON_COMUNICAZIONE_STATO); + if (pec) { + callRepository.removeVerificaPECTask(subject); + callRepository.verificaPECTask(userName, password, subject, JCONON_COMUNICAZIONE_STATO); + } return index; } diff --git a/cool-jconon-webapp-resources/src/main/resources/META-INF/js/ws/call/show-comunicazione.get.js b/cool-jconon-webapp-resources/src/main/resources/META-INF/js/ws/call/show-comunicazione.get.js index ba17a1f879..47e2936351 100644 --- a/cool-jconon-webapp-resources/src/main/resources/META-INF/js/ws/call/show-comunicazione.get.js +++ b/cool-jconon-webapp-resources/src/main/resources/META-INF/js/ws/call/show-comunicazione.get.js @@ -213,6 +213,11 @@ define(['jquery', 'header', 'json!common', 'json!cache', 'cnr/cnr.bulkinfo', 'cn id: 'callId', name: 'callId', value: params.callId + }, + { + id: 'PEC', + name: 'PEC', + value: true } ); jconon.Data.call.comunicazione.invia({ @@ -232,6 +237,39 @@ define(['jquery', 'header', 'json!common', 'json!cache', 'cnr/cnr.bulkinfo', 'cn } myModal = UI.modal('Invia comunicazioni tramite PEC', content, callback); }); + $('#inviaEmail').off('click').on('click', function () { + UI.confirm(i18n.prop('message.confirm.send.email', i18n.prop('actions.comunicazioni')), function () { + var close = UI.progress(), d = []; + d.push( + { + id: 'query', + name: 'query', + value: getUrlParams(page).q + }, + { + id: 'callId', + name: 'callId', + value: params.callId + }, + { + id: 'PEC', + name: 'PEC', + value: false + } + ); + jconon.Data.call.comunicazione.invia({ + type: 'POST', + data: d, + success: function (data) { + UI.info("Sono state inviate " + data.numComunicazioni + " comunicazioni.", function () { + $('#stato').find("[data-value='SPEDITO']").click(); + }); + }, + complete: close, + error: URL.errorFn + }); + }); + }); return deferred; } }); diff --git a/cool-jconon-webapp-resources/src/main/resources/META-INF/js/ws/call/show-convocazione.get.js b/cool-jconon-webapp-resources/src/main/resources/META-INF/js/ws/call/show-convocazione.get.js index d8c1df5a49..28d5aadd08 100644 --- a/cool-jconon-webapp-resources/src/main/resources/META-INF/js/ws/call/show-convocazione.get.js +++ b/cool-jconon-webapp-resources/src/main/resources/META-INF/js/ws/call/show-convocazione.get.js @@ -235,6 +235,11 @@ define(['jquery', 'header', 'json!common', 'json!cache', 'cnr/cnr.bulkinfo', 'cn id: 'callId', name: 'callId', value: params.callId + }, + { + id: 'PEC', + name: 'PEC', + value: true } ); jconon.Data.call.convocazione.invia({ @@ -254,6 +259,39 @@ define(['jquery', 'header', 'json!common', 'json!cache', 'cnr/cnr.bulkinfo', 'cn } myModal = UI.modal('Invia convocazioni tramite PEC', content, callback); }); + $('#inviaEmail').off('click').on('click', function () { + UI.confirm(i18n.prop('message.confirm.send.email', i18n.prop('actions.convocazioni')), function () { + var close = UI.progress(), d = []; + d.push( + { + id: 'query', + name: 'query', + value: getUrlParams(page).q + }, + { + id: 'callId', + name: 'callId', + value: params.callId + }, + { + id: 'PEC', + name: 'PEC', + value: false + } + ); + jconon.Data.call.convocazione.invia({ + type: 'POST', + data: d, + success: function (data) { + UI.info("Sono state inviate " + data.numConvocazioni + " convocazioni.", function () { + $('#stato').find("[data-value='SPEDITO']").click(); + }); + }, + complete: close, + error: URL.errorFn + }); + }); + }); return deferred; } }); diff --git a/cool-jconon-webapp-resources/src/main/resources/META-INF/js/ws/call/show-esclusione.get.js b/cool-jconon-webapp-resources/src/main/resources/META-INF/js/ws/call/show-esclusione.get.js index 79f7177210..eadef0acf2 100644 --- a/cool-jconon-webapp-resources/src/main/resources/META-INF/js/ws/call/show-esclusione.get.js +++ b/cool-jconon-webapp-resources/src/main/resources/META-INF/js/ws/call/show-esclusione.get.js @@ -213,6 +213,11 @@ define(['jquery', 'header', 'json!common', 'json!cache', 'cnr/cnr.bulkinfo', 'cn id: 'callId', name: 'callId', value: params.callId + }, + { + id: 'PEC', + name: 'PEC', + value: true } ); jconon.Data.call.esclusione.invia({ @@ -232,6 +237,39 @@ define(['jquery', 'header', 'json!common', 'json!cache', 'cnr/cnr.bulkinfo', 'cn } myModal = UI.modal('Invia esclusioni tramite PEC', content, callback); }); + $('#inviaEmail').off('click').on('click', function () { + UI.confirm(i18n.prop('message.confirm.send.email', i18n.prop('actions.esclusioni')), function () { + var close = UI.progress(), d = []; + d.push( + { + id: 'query', + name: 'query', + value: getUrlParams(page).q + }, + { + id: 'callId', + name: 'callId', + value: params.callId + }, + { + id: 'PEC', + name: 'PEC', + value: false + } + ); + jconon.Data.call.esclusione.invia({ + type: 'POST', + data: d, + success: function (data) { + UI.info("Sono state inviate " + data.numEsclusioni + " esclusioni.", function () { + $('#stato').find("[data-value='SPEDITO']").click(); + }); + }, + complete: close, + error: URL.errorFn + }); + }); + }); return deferred; } }); diff --git a/cool-jconon-webapp-resources/src/main/resources/i18n/cool-jconon_en.properties b/cool-jconon-webapp-resources/src/main/resources/i18n/cool-jconon_en.properties index 918df7c289..b9645670f7 100644 --- a/cool-jconon-webapp-resources/src/main/resources/i18n/cool-jconon_en.properties +++ b/cool-jconon-webapp-resources/src/main/resources/i18n/cool-jconon_en.properties @@ -57,6 +57,7 @@ page.applications-user=Applications text.loading.spinner=Loading .... placeholder.search=Search... message.confirm=Confirmation +message.confirm.send.email=Do you confirm sending the {0} by email? message.delete.CMIS_FOLDER=Are you sure you want to delete ({0}) and all its contents? message.delete.CMIS_DOCUMENT=Are you sure you want to delete ({0})? message.importo.valido=Enter a valid amount! diff --git a/cool-jconon-webapp-resources/src/main/resources/i18n/cool-jconon_it.properties b/cool-jconon-webapp-resources/src/main/resources/i18n/cool-jconon_it.properties index 5224d16479..bdbf1fa8d9 100644 --- a/cool-jconon-webapp-resources/src/main/resources/i18n/cool-jconon_it.properties +++ b/cool-jconon-webapp-resources/src/main/resources/i18n/cool-jconon_it.properties @@ -68,6 +68,7 @@ message.confirm=Conferma message.delete.CMIS_FOLDER=Sei sicuro di voler eliminare ({0}) e tutto il suo contenuto? message.delete.CMIS_DOCUMENT=Sei sicuro di voler eliminare ({0})? message.importo.valido=Inserire un importo valido! +message.confirm.send.email=Confermi l\'invio delle {0} via email? hint.export-xls=Esporta dati in Excel hint.export-xml=Esporta dati in XML diff --git a/cool-jconon-webapp-resources/src/main/resources/pages/call/show-comunicazione.get.html.ftl b/cool-jconon-webapp-resources/src/main/resources/pages/call/show-comunicazione.get.html.ftl index 05f834c67c..04e3d5f8f9 100644 --- a/cool-jconon-webapp-resources/src/main/resources/pages/call/show-comunicazione.get.html.ftl +++ b/cool-jconon-webapp-resources/src/main/resources/pages/call/show-comunicazione.get.html.ftl @@ -17,8 +17,12 @@
- -
+ + +
+ +
+
diff --git a/cool-jconon-webapp-resources/src/main/resources/pages/call/show-convocazione.get.html.ftl b/cool-jconon-webapp-resources/src/main/resources/pages/call/show-convocazione.get.html.ftl index 4a3708f89b..69bf3aee3c 100644 --- a/cool-jconon-webapp-resources/src/main/resources/pages/call/show-convocazione.get.html.ftl +++ b/cool-jconon-webapp-resources/src/main/resources/pages/call/show-convocazione.get.html.ftl @@ -17,8 +17,11 @@
- +
+
+ +
diff --git a/cool-jconon-webapp-resources/src/main/resources/pages/call/show-esclusione.get.html.ftl b/cool-jconon-webapp-resources/src/main/resources/pages/call/show-esclusione.get.html.ftl index 1dd8138c86..fccf9a53d5 100644 --- a/cool-jconon-webapp-resources/src/main/resources/pages/call/show-esclusione.get.html.ftl +++ b/cool-jconon-webapp-resources/src/main/resources/pages/call/show-esclusione.get.html.ftl @@ -17,8 +17,12 @@
- -
+ + +
+ +
+