001/**
002 *
003 * Copyright 2015-2024 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.muc;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.List;
022
023import org.jivesoftware.smack.SmackException.NoResponseException;
024import org.jivesoftware.smack.SmackException.NotConnectedException;
025import org.jivesoftware.smack.XMPPException.XMPPErrorException;
026
027import org.jivesoftware.smackx.muc.MultiUserChatException.MucConfigurationNotSupportedException;
028import org.jivesoftware.smackx.xdata.BooleanFormField;
029import org.jivesoftware.smackx.xdata.FormField;
030import org.jivesoftware.smackx.xdata.form.FillableForm;
031import org.jivesoftware.smackx.xdata.form.FilledForm;
032import org.jivesoftware.smackx.xdata.form.Form;
033
034import org.jxmpp.jid.Jid;
035import org.jxmpp.jid.util.JidUtil;
036
037/**
038 * Multi-User Chat configuration form manager is used to fill out and submit a {@link FilledForm} used to
039 * configure rooms.
040 * <p>
041 * Room configuration needs either be done right after the room is created and still locked. Or at
042 * any later point (see <a href="http://xmpp.org/extensions/xep-0045.html#roomconfig">XEP-45 § 10.2
043 * Subsequent Room Configuration</a>). When done with the configuration, call
044 * {@link #submitConfigurationForm()}.
045 * </p>
046 * <p>
047 * The manager may not provide all possible configuration options. If you want direct access to the
048 * configuration form, use {@link MultiUserChat#getConfigurationForm()} and
049 * {@link MultiUserChat#sendConfigurationForm(FillableForm)}.
050 * </p>
051 */
052public class MucConfigFormManager {
053
054    private static final String HASH_ROOMCONFIG = "#roomconfig";
055
056    public static final String FORM_TYPE = MultiUserChatConstants.NAMESPACE + HASH_ROOMCONFIG;
057
058                    /**
059     * The constant String {@value}.
060     *
061     * @see <a href="http://xmpp.org/extensions/xep-0045.html#owner">XEP-0045 § 10. Owner Use Cases</a>
062     */
063    public static final String MUC_ROOMCONFIG_ROOMOWNERS = "muc#roomconfig_roomowners";
064
065    /**
066     * The constant String {@value}.
067     */
068    public static final String MUC_ROOMCONFIG_MEMBERSONLY = "muc#roomconfig_membersonly";
069
070    /**
071     * The constant String {@value}.
072     *
073     * @see <a href="http://xmpp.org/extensions/xep-0045.html#enter-pw">XEP-0045 § 7.2.6 Password-Protected Rooms</a>
074     */
075    public static final String MUC_ROOMCONFIG_PASSWORDPROTECTEDROOM = "muc#roomconfig_passwordprotectedroom";
076
077    /**
078     * The constant String {@value}.
079     */
080    public static final String MUC_ROOMCONFIG_ROOMSECRET = "muc#roomconfig_roomsecret";
081
082    /**
083     * The constant String {@value}.
084     */
085    public static final String MUC_ROOMCONFIG_MODERATEDROOM = "muc#roomconfig_moderatedroom";
086
087    /**
088     * The constant String {@value}.
089     */
090    public static final String MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM = "muc#roomconfig_publicroom";
091
092    /**
093     * The constant String {@value}.
094     */
095    public static final String MUC_ROOMCONFIG_ROOMNAME = "muc#roomconfig_roomname";
096
097    /**
098     * The constant String {@value}.
099     */
100    public static final String MUC_ROOMCONFIG_ENABLE_PUBLIC_LOGGING = "muc#roomconfig_enablelogging";
101
102    /**
103     * The constant String {@value}.
104     */
105    public static final String MUC_ROOMCONFIG_CHANGE_SUBJECT = "muc#roomconfig_changesubject";
106
107    private final MultiUserChat multiUserChat;
108    private final FillableForm answerForm;
109    private final List<Jid> owners;
110
111    /**
112     * Create a new MUC config form manager.
113     * <p>
114     * Note that the answerForm needs to be filled out with the defaults.
115     * </p>
116     *
117     * @param multiUserChat the MUC for this configuration form.
118     * @throws InterruptedException if the calling thread was interrupted.
119     * @throws NotConnectedException if the XMPP connection is not connected.
120     * @throws XMPPErrorException if there was an XMPP error returned.
121     * @throws NoResponseException if there was no response from the remote entity.
122     */
123    MucConfigFormManager(MultiUserChat multiUserChat) throws NoResponseException,
124                    XMPPErrorException, NotConnectedException, InterruptedException {
125        this.multiUserChat = multiUserChat;
126
127        // Set the answer form
128        Form configForm = multiUserChat.getConfigurationForm();
129        this.answerForm = configForm.getFillableForm();
130
131        // Set the local variables according to the fields found in the answer form
132        FormField roomOwnersFormField = answerForm.getDataForm().getField(MUC_ROOMCONFIG_ROOMOWNERS);
133        if (roomOwnersFormField != null) {
134            // Set 'owners' to the currently configured owners
135            List<? extends CharSequence> ownerStrings = roomOwnersFormField.getValues();
136            owners = new ArrayList<>(ownerStrings.size());
137            JidUtil.jidsFrom(ownerStrings, owners, null);
138        }
139        else {
140            // roomowners not supported, this should barely be the case
141            owners = null;
142        }
143    }
144
145    /**
146     * Check if the room supports room owners.
147     * @return <code>true</code> if supported, <code>false</code> if not.
148     * @see #MUC_ROOMCONFIG_ROOMOWNERS
149     */
150    public boolean supportsRoomOwners() {
151        return owners != null;
152    }
153
154    /**
155     * Set the owners of the room.
156     *
157     * @param newOwners a collection of JIDs to become the new owners of the room.
158     * @return a reference to this object.
159     * @throws MucConfigurationNotSupportedException if the MUC service does not support this option.
160     * @see #MUC_ROOMCONFIG_ROOMOWNERS
161     */
162    public MucConfigFormManager setRoomOwners(Collection<? extends Jid> newOwners) throws MucConfigurationNotSupportedException {
163        if (!supportsRoomOwners()) {
164            throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_ROOMOWNERS);
165        }
166        owners.clear();
167        owners.addAll(newOwners);
168        return this;
169    }
170
171    /**
172     * Check if the room supports a members only configuration.
173     *
174     * @return <code>true</code> if supported, <code>false</code> if not.
175     */
176    public boolean supportsMembersOnly() {
177        return answerForm.hasField(MUC_ROOMCONFIG_MEMBERSONLY);
178    }
179
180    /**
181     * Check if the room supports being moderated in the configuration.
182     *
183     * @return <code>true</code> if supported, <code>false</code> if not.
184     */
185    public boolean supportsModeration() {
186        return answerForm.hasField(MUC_ROOMCONFIG_MODERATEDROOM);
187    }
188
189    /**
190     * Make the room for members only.
191     *
192     * @return a reference to this object.
193     * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
194     */
195    public MucConfigFormManager makeMembersOnly() throws MucConfigurationNotSupportedException {
196        return setMembersOnly(true);
197    }
198
199    /**
200     * Set if the room is members only. Rooms are not members only per default.
201     *
202     * @param isMembersOnly if the room should be members only.
203     * @return a reference to this object.
204     * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
205     */
206    public MucConfigFormManager setMembersOnly(boolean isMembersOnly) throws MucConfigurationNotSupportedException {
207        if (!supportsMembersOnly()) {
208            throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_MEMBERSONLY);
209        }
210        answerForm.setAnswer(MUC_ROOMCONFIG_MEMBERSONLY, isMembersOnly);
211        return this;
212    }
213
214
215    /**
216     * Make the room moderated.
217     *
218     * @return a reference to this object.
219     * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
220     */
221    public MucConfigFormManager makeModerated() throws MucConfigurationNotSupportedException {
222        return setModerated(true);
223    }
224
225    /**
226     * Set if the room is members only. Rooms are not members only per default.
227     *
228     * @param isModerated if the room should be moderated.
229     * @return a reference to this object.
230     * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
231     */
232    public MucConfigFormManager setModerated(boolean isModerated) throws MucConfigurationNotSupportedException {
233        if (!supportsModeration()) {
234            throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_MODERATEDROOM);
235        }
236        answerForm.setAnswer(MUC_ROOMCONFIG_MODERATEDROOM, isModerated);
237        return this;
238    }
239
240
241    /**
242     * Check if the room supports its visibility being controlled vioa configuration.
243     *
244     * @return <code>true</code> if supported, <code>false</code> if not.
245     */
246    public boolean supportsPublicRoom() {
247        return answerForm.hasField(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM);
248    }
249
250    /**
251     * Make the room publicly searchable.
252     *
253     * @return a reference to this object.
254     * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
255     */
256    public MucConfigFormManager makePublic() throws MucConfigurationNotSupportedException {
257        return setPublic(true);
258    }
259
260    /**
261     * Make the room hidden (not publicly searchable).
262     *
263     * @return a reference to this object.
264     * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
265     */
266    public MucConfigFormManager makeHidden() throws MucConfigurationNotSupportedException {
267        return setPublic(false);
268    }
269
270    /**
271     * Set if the room is publicly searchable (i.e. visible via discovery requests to the MUC service).
272     *
273     * @param isPublic if the room should be publicly searchable.
274     * @return a reference to this object.
275     * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
276     */
277    public MucConfigFormManager setPublic(boolean isPublic) throws MucConfigurationNotSupportedException {
278        if (!supportsPublicRoom()) {
279            throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM);
280        }
281        answerForm.setAnswer(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM, isPublic);
282        return this;
283    }
284
285    public boolean supportsRoomname() {
286        return answerForm.hasField(MUC_ROOMCONFIG_ROOMNAME);
287    }
288
289    public MucConfigFormManager setRoomName(String roomName) throws MucConfigurationNotSupportedException {
290        if (!supportsRoomname()) {
291            throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_ROOMNAME);
292        }
293        answerForm.setAnswer(MUC_ROOMCONFIG_ROOMNAME, roomName);
294        return this;
295    }
296
297    /**
298     * Check if the room supports password protection.
299     *
300     * @return <code>true</code> if supported, <code>false</code> if not.
301     */
302    public boolean supportsPasswordProtected() {
303        return answerForm.hasField(MUC_ROOMCONFIG_PASSWORDPROTECTEDROOM);
304    }
305
306    /**
307     * Set a password and make the room password protected. Users will need to supply the password
308     * to join the room.
309     *
310     * @param password the password to set.
311     * @return a reference to this object.
312     * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
313     */
314    public MucConfigFormManager setAndEnablePassword(String password)
315                    throws MucConfigurationNotSupportedException {
316        return setIsPasswordProtected(true).setRoomSecret(password);
317    }
318
319    /**
320     * Make the room password protected.
321     *
322     * @return a reference to this object.
323     * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
324     */
325    public MucConfigFormManager makePasswordProtected() throws MucConfigurationNotSupportedException {
326        return setIsPasswordProtected(true);
327    }
328
329    /**
330     * Set if this room is password protected. Rooms are by default not password protected.
331     *
332     * @param isPasswordProtected TODO javadoc me please
333     * @return a reference to this object.
334     * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
335     */
336    public MucConfigFormManager setIsPasswordProtected(boolean isPasswordProtected)
337                    throws MucConfigurationNotSupportedException {
338        if (!supportsPasswordProtected()) {
339            throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_PASSWORDPROTECTEDROOM);
340        }
341        answerForm.setAnswer(MUC_ROOMCONFIG_PASSWORDPROTECTEDROOM, isPasswordProtected);
342        return this;
343    }
344
345    public boolean supportsPublicLogging() {
346        return answerForm.hasField(MUC_ROOMCONFIG_ENABLE_PUBLIC_LOGGING);
347    }
348
349    public MucConfigFormManager setPublicLogging(boolean enabled) throws MucConfigurationNotSupportedException {
350        if (!supportsPublicLogging()) {
351            throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_ENABLE_PUBLIC_LOGGING);
352        }
353        answerForm.setAnswer(MUC_ROOMCONFIG_ENABLE_PUBLIC_LOGGING, enabled);
354        return this;
355    }
356
357    public MucConfigFormManager enablePublicLogging() throws MucConfigurationNotSupportedException {
358        return setPublicLogging(true);
359    }
360
361    public MucConfigFormManager disablPublicLogging() throws MucConfigurationNotSupportedException {
362        return setPublicLogging(false);
363    }
364
365    /**
366     * Set the room secret, aka the room password. If set and enabled, the password is required to
367     * join the room. Note that this does only set it by does not enable password protection. Use
368     * {@link #setAndEnablePassword(String)} to set a password and make the room protected.
369     *
370     * @param secret the secret/password.
371     * @return a reference to this object.
372     * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service.
373     */
374    public MucConfigFormManager setRoomSecret(String secret)
375                    throws MucConfigurationNotSupportedException {
376        if (!answerForm.hasField(MUC_ROOMCONFIG_ROOMSECRET)) {
377            throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_ROOMSECRET);
378        }
379        answerForm.setAnswer(MUC_ROOMCONFIG_ROOMSECRET, secret);
380        return this;
381    }
382
383    public boolean supportsChangeSubjectByOccupant() {
384        return answerForm.hasField(MUC_ROOMCONFIG_CHANGE_SUBJECT);
385    }
386
387    public boolean occupantsAreAllowedToChangeSubject() throws MucConfigurationNotSupportedException {
388        if (!supportsChangeSubjectByOccupant()) {
389            throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_CHANGE_SUBJECT);
390        }
391        return answerForm.getField(MUC_ROOMCONFIG_CHANGE_SUBJECT).ifPossibleAsOrThrow(BooleanFormField.class).getValueAsBoolean();
392    }
393
394    public MucConfigFormManager setChangeSubjectByOccupant(boolean enabled) throws MucConfigurationNotSupportedException {
395        if (!supportsChangeSubjectByOccupant()) {
396            throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_CHANGE_SUBJECT);
397        }
398        answerForm.setAnswer(MUC_ROOMCONFIG_CHANGE_SUBJECT, enabled);
399        return this;
400    }
401
402    public MucConfigFormManager allowOccupantsToChangeSubject() throws MucConfigurationNotSupportedException {
403        return setChangeSubjectByOccupant(true);
404    }
405
406    public MucConfigFormManager disallowOccupantsToChangeSubject() throws MucConfigurationNotSupportedException {
407        return setChangeSubjectByOccupant(false);
408    }
409
410    /**
411     * Submit the configuration as {@link FilledForm} to the room.
412     *
413     * @throws NoResponseException if there was no response from the room.
414     * @throws XMPPErrorException if there was an XMPP error returned.
415     * @throws NotConnectedException if the XMPP connection is not connected.
416     * @throws InterruptedException if the calling thread was interrupted.
417     */
418    public void submitConfigurationForm() throws NoResponseException, XMPPErrorException, NotConnectedException,
419                    InterruptedException {
420        if (owners != null) {
421            answerForm.setAnswer(MUC_ROOMCONFIG_ROOMOWNERS, JidUtil.toStringList(owners));
422        }
423        multiUserChat.sendConfigurationForm(answerForm);
424    }
425}