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