Merge remote-tracking branch 'origin/master' into develop

v1.18.x
Julian Lam 7 years ago
commit d1ccd78ac2

@ -59,7 +59,7 @@
"morgan": "^1.9.0",
"mousetrap": "^1.6.1",
"nconf": "^0.9.1",
"nodebb-plugin-composer-default": "6.0.7",
"nodebb-plugin-composer-default": "6.0.8",
"nodebb-plugin-dbsearch": "2.0.9",
"nodebb-plugin-emoji": "2.0.9",
"nodebb-plugin-emoji-android": "2.0.0",
@ -69,7 +69,7 @@
"nodebb-plugin-spam-be-gone": "0.5.1",
"nodebb-rewards-essentials": "0.0.9",
"nodebb-theme-lavender": "5.0.0",
"nodebb-theme-persona": "7.2.8",
"nodebb-theme-persona": "7.2.9",
"nodebb-theme-slick": "1.1.2",
"nodebb-theme-vanilla": "8.1.4",
"nodebb-widget-essentials": "4.0.1",

@ -3,12 +3,12 @@
"custom-css.description": "Zadejte vlastní deklarace CSS, které budou použity na všechny ostatních styly.",
"custom-css.enable": "Povolit uživatelské CSS",
"custom-js": "Custom Javascript",
"custom-js.description": "Enter your own javascript here. It will be executed after the page is loaded completely.",
"custom-js.enable": "Enable Custom Javascript",
"custom-js": "Uživatelský Javascript",
"custom-js.description": "Zadejte zde váš javascriptový kód. Bude spuštěn, jakmile se stránka plně načte.",
"custom-js.enable": "Povolit uživatelský Javascript",
"custom-header": "Uživatelská hlavička",
"custom-header.description": "Enter custom HTML here (ex. Meta Tags, etc.), which will be appended to the <code>&lt;head&gt;</code> section of your forum's markup. Script tags are allowed, but are discouraged, as the <a href=\"#custom-header\" data-toggle=\"tab\">Custom Javascript</a> tab is available.",
"custom-header.description": "Zde zadejte uživatelské HTML (mimo Meta Tags, atd.), které bude připojeno k části značek <code>&lt;head&gt;</code> vašeho fóra.. Značky pro „script” jsou povoleny, ale nedoporučujeme je, neboť je dostupný <a href=\"#custom-header\" data-toggle=\"tab\">Uživatelský Javascript</a> .",
"custom-header.enable": "Povolit uživatelskou hlavičku",
"custom-css.livereload": "Povolit aktuální znovu načtení",

@ -7,5 +7,5 @@
"content": "Obsah",
"posted": "Přidáno",
"reply-to": "Odpovědět na \"%1\"",
"content-editable": "You can click on individual content to edit before posting."
"content-editable": "Kvůli úpravám a před odesláním příspěvku můžete klikat na obsah."
}

@ -39,7 +39,7 @@
"section-appearance": "Vzhled",
"appearance/themes": "Motivy",
"appearance/skins": "Vzhledy",
"appearance/customise": "Custom Content (HTML/JS/CSS)",
"appearance/customise": "Uživatelský obsah (HTML/JS/CSS)",
"section-extend": "Rozšířit",
"extend/plugins": "Rozšíření",
@ -65,7 +65,7 @@
"logout": "Odhlásit",
"view-forum": "Zobrazit fórum",
"search.placeholder": "Search for settings",
"search.placeholder": "Hledat nastavení",
"search.no-results": "Žádné výsledky…",
"search.search-forum": "Prohledat fórum pro <strong></strong>",
"search.keep-typing": "Pište dále pro zobrazení výsledků…",

@ -2,5 +2,5 @@
"notifications": "Oznámení",
"welcome-notification": "Uvítání",
"welcome-notification-link": "Odkaz na uvítání",
"welcome-notification-uid": "Welcome Notification User (UID)"
"welcome-notification-uid": "Uvítání uživatele (UID)"
}

@ -3,9 +3,9 @@
"enable": "Stránkovat témata a příspěvky namísto nekonečného posouvání",
"topics": "Stránkování témat",
"posts-per-page": "Příspěvků na stránku",
"max-posts-per-page": "Maximum posts per page",
"max-posts-per-page": "Maximální množství příspěvků na stránku",
"categories": "Stránkování kategorii",
"topics-per-page": "Témat na stránku",
"max-topics-per-page": "Maximum topics per page",
"max-topics-per-page": "Maximální množství témat na stránku",
"initial-num-load": "Počáteční počet témat pro načtení u nepřečtených, posledních a polulárních"
}

@ -3,8 +3,8 @@
"sorting.post-default": "Výchozí třídění příspěvků",
"sorting.oldest-to-newest": "Od nejstarších po nejnovější",
"sorting.newest-to-oldest": "Od nejnovějších po nejstarší",
"sorting.most-votes": "Dle hlasování",
"sorting.most-posts": "Most Posts",
"sorting.most-votes": "Dle počtu hlasů",
"sorting.most-posts": "Dle počtu příspěvků",
"sorting.topic-default": "Výchozí třídění tématu",
"restrictions": "Omezení příspěvků",
"restrictions.post-queue": "Povolit frontu pro příspěvky",

@ -19,8 +19,8 @@
"themes": "Motivy",
"disable-user-skins": "Zabránit uživateli ve výběru vlastního vzhledu",
"account-protection": "Ochrana účtu",
"admin-relogin-duration": "Admin relogin duration (minutes)",
"admin-relogin-duration-help": "After a set amount of time accessing the admin section will require re-login, set to 0 to disable",
"admin-relogin-duration": "Doba pro opětovné přihlášení správce (minuty)",
"admin-relogin-duration-help": "Po nastavení počtu přístupu do správcovské části, bude vyžadováno opětovné přihlášení. Pro zakázání, nastavte na 0.",
"login-attempts": "Počet pokusů o přihlášení za hodinu",
"login-attempts-help": "Překročí-li pokusy o přihlášení uživatele/ů tuto hranici, účet bude uzamknut na určený čas",
"lockout-duration": "Délka blokování účtu (v minutách)",

@ -30,7 +30,7 @@
"notif.chat.unsub.info": "Toto upozornění na chat vám bylo odesláno na základě vašeho nastavení odběru.",
"notif.post.cta": "Klikněte zde pro přečtené celého tématu",
"notif.post.unsub.info": "Toto upozornění na příspěvek vám bylo odesláno na základě vašeho nastavení odběru.",
"notif.cta": "Click here to go to forum",
"notif.cta": "Pro přejití na fórum, klikněte zde",
"test.text1": "Tento testovací e-mail slouží k ověření, že je e-mailer správně nastaven pro práci s NodeBB.",
"unsub.cta": "Chcete-li změnit tyto nastavení, klikněte zde.",
"banned.subject": "Byl jste zablokován od %1",

@ -11,7 +11,7 @@
"invalid-uid": "Neplatné ID uživatele",
"invalid-username": "Neplatné uživatelské jméno",
"invalid-email": "Neplatný e-mail",
"invalid-title": "Invalid title",
"invalid-title": "Neplatný název",
"invalid-user-data": "Neplatná uživatelská data",
"invalid-password": "Neplatné heslo",
"invalid-login-credentials": "Neplatné přihlašovací údaje",
@ -81,7 +81,7 @@
"cant-ban-other-admins": "Nemůžete zablokovat jiné správce.",
"cant-remove-last-admin": "Jste jediným správcem. Před vlastním odebráním oprávnění správce nejdříve přidejte jiného uživatele jako správce",
"cant-delete-admin": "Před odstraněním účtu mu nejprve odeberte oprávnění správce.",
"invalid-image": "Invalid image",
"invalid-image": "Neplatný obrázek",
"invalid-image-type": "Neplatný typ obrázku. Povolené typy jsou: %1",
"invalid-image-extension": "Neplatná přípona obrázku",
"invalid-file-type": "Neplatný typ souboru. Povolené typy jsou: %1",
@ -119,13 +119,13 @@
"not-enough-reputation-to-downvote": "Nemáte dostatečnou reputaci pro vyjádření nesouhlasu u tohoto příspěvku",
"not-enough-reputation-to-flag": "Pro označení tohoto příspěvku nemáte dostatečnou reputaci",
"already-flagged": "Tento příspěvek jste již označil",
"self-vote": "You cannot vote on your own post",
"self-vote": "U svého vlastního příspěvku nemůžete hlasovat",
"reload-failed": "Vyskytla se chyba v NodeBB při znovu načtení: \"%1\". NodeBB bude pokračovat v běhu na straně klienta, nicméně byste měl/a přenastavit zpět to, co jste udělal/a před opětovným načtením.",
"registration-error": "Chyba při registraci",
"parse-error": "Při analýze odpovědi serveru nastala chyba",
"wrong-login-type-email": "Pro přihlášení použijte vaši e-mailovou adresu",
"wrong-login-type-username": "Pro přihlášení použijte vaše uživatelské jméno",
"sso-registration-disabled": "Registration has been disabled for %1 accounts, please register with an email address first",
"sso-registration-disabled": "Registrace byla zakázána pro účty - %1. Nejprve si zaregistrujte e-mailovou adresu",
"invite-maximum-met": "Již jste pozval/a maximálně možný počet lidí (%1 z %2).",
"no-session-found": "Nebyla nalezena relace s přihlášením.",
"not-in-room": "Uživatel není přítomen v místnosti",
@ -135,5 +135,5 @@
"invalid-home-page-route": "Neplatná cesta k domovské stránkce",
"invalid-session": "Nesoulad v relacích",
"invalid-session-text": "Zdá se, že vše relace s přihlášením již není aktivní nebo již neodpovídá s relací na serveru. Obnovte prosím tuto stránku.",
"no-topics-selected": "No topics selected!"
"no-topics-selected": "Žádná vybraná témata."
}

@ -54,11 +54,11 @@
"modal-body": "Zadejte váš důvod k označení %1 %2 pro kontrolu. Nebo použijte tlačítko je-li dostupné.",
"modal-reason-spam": "Spam",
"modal-reason-offensive": "Urážlivé",
"modal-reason-other": "Other (specify below)",
"modal-reason-other": "Jiné (popište níže)",
"modal-reason-custom": "Důvod ohlášení tohoto obsahu…",
"modal-submit": "Předat hlášení",
"modal-submit-success": "Obsah byl označen pro moderaci.",
"modal-submit-confirm": "Confirm Submission",
"modal-submit-confirm-text": "You have a custom reason specified already. Are you sure you wish to submit via quick-report?",
"modal-submit-confirm-text-help": "Submitting a quick report will overwrite any custom reasons defined."
"modal-submit-confirm": "Potvrdit hlášení",
"modal-submit-confirm-text": "Již jste zadal/a nějaký důvod. Jste si jist/a, že chcete nahlásit pomocí rychlé zprávy?",
"modal-submit-confirm-text-help": "Zaslání rychlé zprávy přepíše jiné zadané důvody."
}

@ -8,8 +8,8 @@
"outgoing_link_message": "Opouštíte %1",
"continue_to": "Pokračovat na %1",
"return_to": "Vrátit se na %1",
"new_notification": "Nové upozornění",
"new_notification_from": "You have a new Notification from %1",
"new_notification": "Nové oznámení",
"new_notification_from": "Máte nové upozornění od %1",
"you_have_unread_notifications": "Máte nepřečtená upozornění.",
"all": "Vše",
"topics": "Témata",
@ -47,18 +47,18 @@
"email-confirmed-message": "Děkujeme za ověření vaší e-mailové adresy. Váš účet je nyní aktivní.",
"email-confirm-error-message": "Nastal problém s ověřením vaší e-mailové adresy. Kód je pravděpodobně neplatný nebo jeho platnost vypršela.",
"email-confirm-sent": "Ověřovací e-mail odeslán.",
"none": "None",
"notification_only": "Notification Only",
"email_only": "Email Only",
"notification_and_email": "Notification & Email",
"notificationType_upvote": "When someone upvotes your post",
"notificationType_new-topic": "When someone you follow posts a topic",
"notificationType_new-reply": "When a new reply is posted in a topic you are watching",
"notificationType_follow": "When someone starts following you",
"notificationType_new-chat": "When you receive a chat message",
"notificationType_group-invite": "When you receive a group invite",
"notificationType_new-register": "When someone gets added to registration queue",
"notificationType_post-queue": "When a new post is queued",
"notificationType_new-post-flag": "When a post is flagged",
"notificationType_new-user-flag": "When a user is flagged"
"none": "Nic",
"notification_only": "Jen oznámení",
"email_only": "Jen e-mail",
"notification_and_email": "Oznámení a e-mail",
"notificationType_upvote": "Vyjádří-li někdo souhlas s vaším příspěvkem",
"notificationType_new-topic": "Začne-li někdo sledovat příspěvky a téma",
"notificationType_new-reply": "Bude-li přidán nový příspěvek v tématu, které sledujete",
"notificationType_follow": "Začne-li vás někdo sledovat",
"notificationType_new-chat": "Obdržíte-li novou konverzační zprávu",
"notificationType_group-invite": "Obdržíte-li pozvání ke skupině",
"notificationType_new-register": "Bude-li někdo přidán do registrační fronty",
"notificationType_post-queue": "Bude-li přidán nový příspěvek do fronty",
"notificationType_new-post-flag": "Bude-li příspěvek označen",
"notificationType_new-user-flag": "Bude-li uživatel označen"
}

@ -44,7 +44,7 @@
"account/bookmarks": "%1's zazáložkované příspěvky",
"account/settings": "Uživatelské nastavení",
"account/watched": "Témata sledovaná uživatelem %1",
"account/ignored": "Topics ignored by %1",
"account/ignored": "Témata ignorovaná uživatelem %1",
"account/upvoted": "Souhlasí s příspěvkem %1",
"account/downvoted": "Nesouhlasí s příspěvkem %1",
"account/best": "Nejlepší příspěvky od %1",

@ -68,8 +68,8 @@
"thread_tools.restore_confirm": "Jste si jist/a, že chcete toto téma obnovit?",
"thread_tools.purge": "Vyčistit téma",
"thread_tools.purge_confirm": "Jste si jist/a, že chcete vyčistit toto téma?",
"thread_tools.merge_topics": "Merge Topics",
"thread_tools.merge": "Merge",
"thread_tools.merge_topics": "Sloučit témata",
"thread_tools.merge": "Sloučit",
"topic_move_success": "Toto téma bylo úspěšně přesunuto do %1",
"post_delete_confirm": "Jste si jist/a, že chcete odstranit tento příspěvek?",
"post_restore_confirm": "Jste si jist/a, že chcete obnovit tento příspěvek?",
@ -91,7 +91,7 @@
"fork_pid_count": "Vybráno %1 příspěvek/ů",
"fork_success": "Téma úspěšně rozděleno. Pro přejití na rozdělené téma, zde klikněte.",
"delete_posts_instruction": "Klikněte na příspěvek, který chcete odstranit/vyčistit",
"merge_topics_instruction": "Click the topics you want to merge",
"merge_topics_instruction": "Pro sloučení témat, klikněte na ně",
"composer.title_placeholder": "Zadejte název tématu…",
"composer.handle_placeholder": "Jméno",
"composer.discard": "Zrušit",

@ -10,6 +10,6 @@
"all-topics": "Všechna témata",
"new-topics": "Nová témata",
"watched-topics": "Sledovaná témata",
"unreplied-topics": "Unreplied Topics",
"multiple-categories-selected": "Multiple Selected"
"unreplied-topics": "Neodpovězené témata",
"multiple-categories-selected": "Vícenásobný výběr"
}

@ -25,7 +25,7 @@
"reputation": "Reputace",
"bookmarks": "Záložky",
"watched": "Sledován",
"ignored": "Ignored",
"ignored": "Ignorován",
"followers": "Sledují ho",
"following": "Sleduje",
"aboutme": "O mně",
@ -85,7 +85,7 @@
"has_no_posts": "Tento uživatel ještě nic nenapsal.",
"has_no_topics": "Tento uživatel ještě nezaložil žádné téma.",
"has_no_watched_topics": "Tento uživatel zatím nesleduje žádná témata.",
"has_no_ignored_topics": "This user hasn't ignored any topics yet.",
"has_no_ignored_topics": "Tento uživatel ještě neignoruje žádné témata.",
"has_no_upvoted_posts": "Tento uživatel zatím nevyjádřil souhlas u žádného příspěvku.",
"has_no_downvoted_posts": "Tento uživatel zatím nevyjádřil nesouhlas u žádného příspěvku.",
"has_no_voted_posts": "Tento uživatel nemá žádné hlasovací příspěvky",
@ -101,11 +101,11 @@
"outgoing-message-sound": "Zvuk odchozí zprávy",
"notification-sound": "Zvuk oznámení",
"no-sound": "Bez zvuku",
"upvote-notif-freq": "Upvote Notification Frequency",
"upvote-notif-freq.all": "All Upvotes",
"upvote-notif-freq.everyTen": "Every Ten Upvotes",
"upvote-notif-freq.logarithmic": "On 10, 100, 1000...",
"upvote-notif-freq.disabled": "Disabled",
"upvote-notif-freq": "Frekvence upozornění na souhlasy",
"upvote-notif-freq.all": "Všechny souhlasy",
"upvote-notif-freq.everyTen": "Každý desátý souhlas",
"upvote-notif-freq.logarithmic": "Dle 10, 100, 1000...",
"upvote-notif-freq.disabled": "Zakázáno",
"browsing": "Nastavení prohlížení",
"open_links_in_new_tab": "Otevřít odchozí odkaz v nové záložce",
"enable_topic_searching": "Povolit vyhledávání v tématu",
@ -126,9 +126,9 @@
"sso.title": "Služby jednotného přihlášení",
"sso.associated": "Přiřazeno k",
"sso.not-associated": "Zde klikněte pro přiřazení k",
"sso.dissociate": "Dissociate",
"sso.dissociate-confirm-title": "Confirm Dissociation",
"sso.dissociate-confirm": "Are you sure you wish to dissociate your account from %1?",
"sso.dissociate": "Odloučit",
"sso.dissociate-confirm-title": "Potvrdit odloučení",
"sso.dissociate-confirm": "Jste si jist/a, že chcete odloučit váš účet z %1?",
"info.latest-flags": "Poslední označené",
"info.no-flags": "Nebyly nalezeny žádné označené příspěvky",
"info.ban-history": "Poslední historie blokovaných",

@ -0,0 +1,10 @@
{
"administrators": "Administrators",
"global-moderators": "Global Moderators",
"no-global-moderators": "No Global Moderators",
"moderators-of-category": "%1 Moderators",
"no-moderators": "No Moderators",
"add-administrator": "Add Administrator",
"add-global-moderator": "Add Global Moderator",
"add-moderator": "Add Moderator"
}

@ -0,0 +1,4 @@
{
"global": "Global",
"global.no-users": "No user-specific global privileges."
}

@ -71,9 +71,15 @@
"alerts.lockout-reset-success": "Lockout(s) reset!",
"alerts.flag-reset-success": "Flags(s) reset!",
"alerts.no-remove-yourself-admin": "You can't remove yourself as Administrator!",
"alerts.make-admin-success": "User(s) are now administrators.",
"alerts.confirm-remove-admin": "Do you really want to remove admins?",
"alerts.remove-admin-success": "User(s) are no longer administrators.",
"alerts.make-admin-success": "User is now administrator.",
"alerts.confirm-remove-admin": "Do you really want to remove this administrator?",
"alerts.remove-admin-success": "User is no longer administrator.",
"alerts.make-global-mod-success": "User is now global moderator.",
"alerts.confirm-remove-global-mod": "Do you really want to remove this global moderator?",
"alerts.remove-global-mod-success": "User is no longer global noderator",
"alerts.make-moderator-success": "User is now moderator.",
"alerts.confirm-remove-moderator": "Do you really want to remove this moderator?",
"alerts.remove-moderator-success": "User is no longer moderator.",
"alerts.confirm-validate-email": "Do you want to validate email(s) of these user(s)?",
"alerts.validate-email-success": "Emails validated",
"alerts.password-reset-confirm": "Do you want to send password reset email(s) to these user(s)?",

@ -9,8 +9,10 @@
"section-manage": "Manage",
"manage/categories": "Categories",
"manage/privileges": "Privileges",
"manage/tags": "Tags",
"manage/users": "Users",
"manage/admins-mods": "Admins & Mods",
"manage/registration": "Registration Queue",
"manage/post-queue": "Post Queue",
"manage/groups": "Groups",

@ -3,9 +3,9 @@
"custom-css.description": "Entrez vos propres déclarations de CSS ici, elles seront appliquées après tous les autres styles.",
"custom-css.enable": "Activer les CSS personnalisés",
"custom-js": "Custom Javascript",
"custom-js.description": "Enter your own javascript here. It will be executed after the page is loaded completely.",
"custom-js.enable": "Enable Custom Javascript",
"custom-js": "Javascript personnalisé",
"custom-js.description": "Entrez votre Javascript ici. Celui-ci sera exécute après le chargement complet de la page.",
"custom-js.enable": "Activer le Javascript personnalisé",
"custom-header": "En-tête personnalisé",
"custom-header.description": "Enter custom HTML here (ex. Meta Tags, etc.), which will be appended to the <code>&lt;head&gt;</code> section of your forum's markup. Script tags are allowed, but are discouraged, as the <a href=\"#custom-header\" data-toggle=\"tab\">Custom Javascript</a> tab is available.",

@ -13,7 +13,7 @@
"reset.text1": "Abbiamo ricevuto una richiesta di reset della tua password, probabilmente perché l'hai dimenticata. Se non è così si prega di ignorare questa email.",
"reset.text2": "Per confermare il reset della password per favore clicca il seguente link:",
"reset.cta": "Clicca qui per resettare la tua password",
"reset.notify.subject": "Possword modificata con successo.",
"reset.notify.subject": "Password modificata con successo.",
"reset.notify.text1": "Ti informiamo che in data %1, la password è stata cambiata con successo.",
"reset.notify.text2": "Se non hai autorizzato questo, per favore notifica immediatamente un amministratore.",
"digest.notifications": "Hai una notifica non letta da %1:",
@ -33,9 +33,9 @@
"notif.cta": "Vai alla discussione",
"test.text1": "Questa è una email di test per verificare che il servizio di invio email è configurato correttamente sul tuo NodeBB.",
"unsub.cta": "Clicca qui per modificare queste impostazioni",
"banned.subject": "You have been banned from %1",
"banned.text1": "The user %1 has been banned from %2.",
"banned.text2": "This ban will last until %1.",
"banned.text3": "This is the reason why you have been banned:",
"banned.subject": "Sei stato bannato da %1",
"banned.text1": "%1 è stato bannato da %2",
"banned.text2": "Questo ban durerà fino a %1.",
"banned.text3": "Il motivo del ban è:",
"closing": "Grazie!"
}

@ -11,10 +11,10 @@
"invalid-uid": "ID Utente non valido",
"invalid-username": "Nome utente non valido",
"invalid-email": "Email non valida",
"invalid-title": "Invalid title",
"invalid-title": "Titolo non Valido",
"invalid-user-data": "Dati Utente non validi",
"invalid-password": "Password non valida",
"invalid-login-credentials": "Invalid login credentials",
"invalid-login-credentials": "Credenziali non Valide",
"invalid-username-or-password": "Si prega di specificare sia un nome utente che una password",
"invalid-search-term": "Termine di ricerca non valido",
"csrf-invalid": "Non siamo riusciti a farti connettere, probabilmente perché la sessione è scaduta. Per favore riprova.",
@ -66,7 +66,7 @@
"content-too-long": "Inserisci un post più breve. I post non possono essere più lunghi di %1 caratteri.",
"title-too-short": "Inserisci un titolo più lungo. I titoli devono contenere almeno %1 caratteri.",
"title-too-long": "Inserisci un titolo più corto. I titoli non possono essere più lunghi di %1 caratteri.",
"category-not-selected": "Category not selected.",
"category-not-selected": "Categoria non selezionata.",
"too-many-posts": "È possibile inserire un Post ogni %1 secondi - si prega di attendere prima di postare di nuovo",
"too-many-posts-newbie": "Come nuovo utente puoi postare solamente una volta ogni %1 secondi finché non hai raggiunto un livello di reputazione %2 - per favore attendi prima di scrivere ancora",
"tag-too-short": "Inserisci un tag più lungo. I tag devono contenere almeno %1 caratteri.",
@ -76,12 +76,12 @@
"still-uploading": "Per favore attendere il completamento degli uploads.",
"file-too-big": "La dimensione massima consentita è di %1 kB - si prega di caricare un file più piccolo",
"guest-upload-disabled": "Il caricamento da ospite è stato disattivato",
"already-bookmarked": "You have already bookmarked this post",
"already-unbookmarked": "You have already unbookmarked this post",
"already-bookmarked": "Hai già aggiunto questa discussione ai preferiti.",
"already-unbookmarked": "Hai già rimosso questa discussione dai preferiti",
"cant-ban-other-admins": "Non puoi bannare altri amministratori!",
"cant-remove-last-admin": "Sei l'unico Amministratore. Aggiungi un altro amministratore prima di rimuovere il tuo ruolo",
"cant-delete-admin": "Togli i privilegi amministrativi da questo account prima di provare ad eliminarlo.",
"invalid-image": "Invalid image",
"invalid-image": "Immagine non Valida",
"invalid-image-type": "Tipo dell'immagine non valido. I tipi permessi sono: %1",
"invalid-image-extension": "Estensione immagine non valida",
"invalid-file-type": "Tipo di file non valido. I formati consentiti sono: %1",
@ -109,7 +109,7 @@
"chat-disabled": "Il sistema di chat è stato disabilitato",
"too-many-messages": "Hai inviato troppi messaggi, aspetta un attimo.",
"invalid-chat-message": "Messaggio chat non valido",
"chat-message-too-long": "Chat messages can not be longer than %1 characters.",
"chat-message-too-long": "I messaggi in chat non possono superare i %1 caratteri.",
"cant-edit-chat-message": "Non ti è permesso di modificare questo messaggio",
"cant-remove-last-user": "Non puoi rimuovere l'ultimo utente",
"cant-delete-chat-message": "Non ti è permesso di eliminare questo messaggio",
@ -119,7 +119,7 @@
"not-enough-reputation-to-downvote": "Non hai i privilegi per votare negativamente questo post",
"not-enough-reputation-to-flag": "Tu non hai abbastanza reputazione per segnalare questo Post",
"already-flagged": "Hai già messo marcato questo post",
"self-vote": "You cannot vote on your own post",
"self-vote": "Non puoi votare la tua stessa discussione",
"reload-failed": "NodeBB ha incontrato un problema durante il ricaricamento: \"%1\". NodeBB continuerà a servire gli assets esistenti lato client, così puoi annullare quello che hai fatto prima di ricaricare.",
"registration-error": "Errore nella registrazione",
"parse-error": "Qualcosa è andato storto durante l'analisi della risposta proveniente dal server",
@ -135,5 +135,5 @@
"invalid-home-page-route": "Percorso della pagina iniziale non valido",
"invalid-session": "Discrepanza della sessione",
"invalid-session-text": "Sembra che la tua sessione non sia più attiva, oppure non corrisponde con il server. Per favore ricarica la pagina.",
"no-topics-selected": "No topics selected!"
"no-topics-selected": "Nessuna discussione selezionata!"
}

@ -27,12 +27,12 @@
"quick-links": "Quick Links",
"flagged-user": "Flagged User",
"view-profile": "View Profile",
"view-profile": "Vedi Profilo",
"start-new-chat": "Start New Chat",
"go-to-target": "View Flag Target",
"user-view": "View Profile",
"user-edit": "Edit Profile",
"user-view": "Vedi Profilo",
"user-edit": "Modifica Profilo",
"notes": "Flag Notes",
"add-note": "Add Note",
@ -44,21 +44,21 @@
"state-all": "All states",
"state-open": "New/Open",
"state-wip": "Work in Progress",
"state-resolved": "Resolved",
"state-wip": "Lavori in Corso",
"state-resolved": "Risolto",
"state-rejected": "Rejected",
"no-assignee": "Not Assigned",
"no-assignee": "Non Assegnato",
"note-added": "Note Added",
"modal-title": "Report Inappropriate Content",
"modal-title": "Segnala Contenuto Inappropriato",
"modal-body": "Please specify your reason for flagging %1 %2 for review. Alternatively, use one of the quick report buttons if applicable.",
"modal-reason-spam": "Spam",
"modal-reason-offensive": "Offensive",
"modal-reason-other": "Other (specify below)",
"modal-reason-custom": "Reason for reporting this content...",
"modal-submit": "Submit Report",
"modal-submit-success": "Content has been flagged for moderation.",
"modal-submit-confirm": "Confirm Submission",
"modal-reason-offensive": "Offensivo",
"modal-reason-other": "Altro (Specificare di seguito)",
"modal-reason-custom": "Motivazione della segnalazione...",
"modal-submit": "Invia la Segnalazione",
"modal-submit-success": "Il contenuto è stato segnalato.",
"modal-submit-confirm": "Conferma la Segnalazione",
"modal-submit-confirm-text": "You have a custom reason specified already. Are you sure you wish to submit via quick-report?",
"modal-submit-confirm-text-help": "Submitting a quick report will overwrite any custom reasons defined."
}

@ -104,6 +104,6 @@
"cookies.accept": "Ho capito!",
"cookies.learn_more": "Scopri di più",
"edited": "Modificato",
"disabled": "Disabled",
"select": "Select"
"disabled": "Disabilitato",
"select": "Seleziona"
}

@ -27,7 +27,7 @@
"details.disableJoinRequests": "Disabilita le richieste d'adesione",
"details.grant": "Concedi / Rimuovi la Proprietà",
"details.kick": "Espelli",
"details.kick_confirm": "Are you sure you want to remove this member from the group?",
"details.kick_confirm": "Sei sicuro di voler rimuovere questo membro dal gruppo?",
"details.owner_options": "Amministratore del Grupo",
"details.group_name": "Nome Gruppo",
"details.member_count": "Totale Membri",
@ -53,6 +53,6 @@
"new-group.group_name": "Nome Gruppo:",
"upload-group-cover": "Carica copertina del gruppo",
"bulk-invite-instructions": "Inserisci una lista di nomi utente da invitare in questo gruppo separati da virgole",
"bulk-invite": "Bulk Invite",
"bulk-invite": "Invita in Massa",
"remove_group_cover_confirm": "Sei sicuro di voler rimuovere l'immagine copertina?"
}

@ -20,7 +20,7 @@
"chat.three_months": "3 Mesi",
"chat.delete_message_confirm": "Sei sicuro di voler eliminare questo messaggio?",
"chat.add-users-to-room": "Aggiungi utenti alla stanza",
"chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?",
"chat.confirm-chat-with-dnd-user": "Questo utente ha impostato il suo stato su Non Disturbare. Sei sicuro di voler iniziare una conversazione?",
"composer.compose": "Componi",
"composer.show_preview": "Visualizza Anteprima",
"composer.hide_preview": "Nascondi Anteprima",

@ -29,9 +29,9 @@
"user_flagged_post_in": "<strong>%1</strong> ha segnalato un post in <strong>%2</strong>",
"user_flagged_post_in_dual": "<strong>%1</strong> e <strong>%2</strong> hanno segnalato un post in <strong>%3</strong>",
"user_flagged_post_in_multiple": "<strong>%1</strong> ed altri %2 hanno segnalato un post in <strong>%3</strong>",
"user_flagged_user": "<strong>%1</strong> flagged a user profile (%2)",
"user_flagged_user_dual": "<strong>%1</strong> and <strong>%2</strong> flagged a user profile (%3)",
"user_flagged_user_multiple": "<strong>%1</strong> and %2 others flagged a user profile (%3)",
"user_flagged_user": "<strong>%1</strong> ha segnalato un utente (%2)",
"user_flagged_user_dual": "<strong>%1</strong> e <strong>%2</strong> hanno segnalato un utente (%3)",
"user_flagged_user_multiple": "<strong>%1</strong> e altri %2 hanno segnalato un utente (%3)",
"user_posted_to": "<strong>%1</strong> ha postato una risposta a: <strong>%2</strong>",
"user_posted_to_dual": "<strong>%1</strong> e <strong>%2</strong> hanno postato una risposta su: <strong>%3</strong>",
"user_posted_to_multiple": "<strong>%1</strong> ed altri %2 hanno postato una risposta su: <strong>%3</strong>",
@ -42,23 +42,23 @@
"new_register": "<strong>%1</strong> ha inviato una richiesta di registrazione.",
"new_register_multiple": "Ci sono <strong>%1</strong> richieste di registrazione che attendono di essere esaminate.",
"flag_assigned_to_you": "<strong>Flag %1</strong> has been assigned to you",
"post_awaiting_review": "Post awaiting review",
"post_awaiting_review": "Post in attesa di revisione",
"email-confirmed": "Email Confermata",
"email-confirmed-message": "Grazie per aver validato la tua email. Il tuo account è ora completamente attivato.",
"email-confirm-error-message": "C'è stato un problema nella validazione del tuo indirizzo email. Potrebbe essere il codice non valido o scaduto.",
"email-confirm-sent": "Email di conferma inviata.",
"none": "None",
"notification_only": "Notification Only",
"email_only": "Email Only",
"notification_and_email": "Notification & Email",
"notificationType_upvote": "When someone upvotes your post",
"none": "Nessuna Notifica",
"notification_only": "Solo Notifiche",
"email_only": "Solo Email",
"notification_and_email": "Email e Notifica",
"notificationType_upvote": "Quando il tuo post riceve un Mi Piace",
"notificationType_new-topic": "When someone you follow posts a topic",
"notificationType_new-reply": "When a new reply is posted in a topic you are watching",
"notificationType_follow": "When someone starts following you",
"notificationType_new-chat": "When you receive a chat message",
"notificationType_group-invite": "When you receive a group invite",
"notificationType_new-register": "When someone gets added to registration queue",
"notificationType_post-queue": "When a new post is queued",
"notificationType_new-post-flag": "When a post is flagged",
"notificationType_new-user-flag": "When a user is flagged"
"notificationType_post-queue": "Quando un nuovo post è in attesa di revisione",
"notificationType_new-post-flag": "Quando un post viene segnalato",
"notificationType_new-user-flag": "Quando un utente viene segnalato"
}

@ -7,7 +7,7 @@
"popular-alltime": "Discussioni più popolari di sempre",
"recent": "Discussioni Recenti",
"moderator-tools": "Moderator Tools",
"flagged-content": "Flagged Content",
"flagged-content": "Contenuti Segnalati",
"ip-blacklist": "Lista nera degli IP",
"post-queue": "Post Queue",
"users/online": "Utenti Online",
@ -44,7 +44,7 @@
"account/bookmarks": "%1 Post tra i favoriti",
"account/settings": "Impostazioni Utente",
"account/watched": "Discussioni osservate da %1",
"account/ignored": "Topics ignored by %1",
"account/ignored": "Discussioni ignorate da %1",
"account/upvoted": "Post apprezzati da %1",
"account/downvoted": "Post votati negativamente da %1",
"account/best": "I migliori post di %1",

@ -1,5 +1,5 @@
{
"register": "Registrazione",
"register": "Registrati",
"cancel_registration": "Cancella Registrazione",
"help.email": "Come opzione predefinita, il tuo indirizzo email non verrà reso pubblico.",
"help.username_restrictions": "Un nome utente unico, di almeno %1 caratteri e al massimo di %2. Gli altri utenti ti possono menzionare usando @<span id='yourUsername'>username</span>.",

@ -12,7 +12,7 @@
"reply-count": "Numero Risposte",
"at-least": "Almeno",
"at-most": "Al massimo",
"relevance": "Relevance",
"relevance": "Rilevanza",
"post-time": "Ora invio",
"newer-than": "Più nuovi di",
"older-than": "Più vecchi di",

@ -1,7 +1,7 @@
{
"success": "Riuscito",
"topic-post": "Hai postato correttamente.",
"post-queued": "Your post is queued for approval.",
"post-queued": "La tua discussione è in attesa di approvazione.",
"authentication-successful": "Autenticazione Riuscita",
"settings-saved": "Impostazioni salvate!"
}

@ -13,9 +13,9 @@
"notify_me": "Ricevi notifiche di nuove risposte in questa discussione",
"quote": "Cita",
"reply": "Rispondi",
"replies_to_this_post": "%1 Replies",
"one_reply_to_this_post": "1 Reply",
"last_reply_time": "Last reply",
"replies_to_this_post": "%1 Risposte",
"one_reply_to_this_post": "1 Risposta",
"last_reply_time": "Ultima Risposta",
"reply-as-topic": "Topic risposta",
"guest-login-reply": "Effettua il Log in per rispondere",
"edit": "Modifica",
@ -59,7 +59,7 @@
"thread_tools.unlock": "Sblocca Discussione",
"thread_tools.move": "Sposta Discussione",
"thread_tools.move_all": "Sposta Tutto",
"thread_tools.select_category": "Select Category",
"thread_tools.select_category": "Seleziona Categoria",
"thread_tools.fork": "Dividi Discussione",
"thread_tools.delete": "Elimina Discussione",
"thread_tools.delete-posts": "Cancella post",
@ -68,8 +68,8 @@
"thread_tools.restore_confirm": "Sei sicuro di voler ripristinare questa discussione?",
"thread_tools.purge": "Svuota Discussione",
"thread_tools.purge_confirm": "Sei sicuro di voler svuotare questa discussione?",
"thread_tools.merge_topics": "Merge Topics",
"thread_tools.merge": "Merge",
"thread_tools.merge_topics": "Unisci le Discussioni",
"thread_tools.merge": "Unisci",
"topic_move_success": "Questa discussione è stata correttamente spostata in %1",
"post_delete_confirm": "Sei sicuro di voler cancellare questo post?",
"post_restore_confirm": "Sei sicuro di voler ripristinare questo post?",
@ -91,7 +91,7 @@
"fork_pid_count": "%1 post selezionati",
"fork_success": "Topic Diviso con successo ! Clicca qui per andare al Topic Diviso.",
"delete_posts_instruction": "Clicca sui post che vuoi cancellare/eliminare",
"merge_topics_instruction": "Click the topics you want to merge",
"merge_topics_instruction": "Clicca sulle discussioni che vuoi unire",
"composer.title_placeholder": "Inserisci qui il titolo della discussione...",
"composer.handle_placeholder": "Nome",
"composer.discard": "Annulla",

@ -10,6 +10,6 @@
"all-topics": "Tutte le Discussioni",
"new-topics": "Nuova Discussione",
"watched-topics": "Discussioni seguite",
"unreplied-topics": "Nessuna Risposta",
"multiple-categories-selected": "Multiple Selected"
"unreplied-topics": "Discussioni Senza Risposta",
"multiple-categories-selected": "Più Categorie"
}

@ -19,13 +19,13 @@
"location": "Località",
"age": "Età",
"joined": "Iscrizione",
"lastonline": "Ultima volta in linea",
"lastonline": "Ultimo Accesso",
"profile": "Profilo",
"profile_views": "Visite al profilo",
"reputation": "Reputazione",
"bookmarks": "Favoriti",
"bookmarks": "Preferiti",
"watched": "Osservati",
"ignored": "Ignored",
"ignored": "Ignorati",
"followers": "Da chi è seguito",
"following": "Chi segue",
"aboutme": "Su di me",
@ -61,7 +61,7 @@
"username_taken_workaround": "Il nome utente che hai richiesto era già stato utilizzato, quindi lo abbiamo modificato leggermente. Ora il tuo è <strong>%1</strong>",
"password_same_as_username": "La tua password è uguale al tuo username, per piacere scegli un'altra password",
"password_same_as_email": "La tua password sembra coincidere con la tua email, per favore fornisci un'altra password.",
"weak_password": "Weak password.",
"weak_password": "Password debole.",
"upload_picture": "Carica foto",
"upload_a_picture": "Carica una foto",
"remove_uploaded_picture": "Elimina foto caricata",
@ -85,7 +85,7 @@
"has_no_posts": "Questo utente non ha ancora scritto niente.",
"has_no_topics": "Questo utente non ha ancora avviato discussioni.",
"has_no_watched_topics": "Questo utente non sta osservando discussioni.",
"has_no_ignored_topics": "This user hasn't ignored any topics yet.",
"has_no_ignored_topics": "Questo utente non sta ignorando discussioni.",
"has_no_upvoted_posts": "Questo utente non ha ancora apprezzato nessun post.",
"has_no_downvoted_posts": "Questo utente non ha ancora votato negativamente alcun post",
"has_no_voted_posts": "Questo utente non ha post votati",
@ -94,18 +94,18 @@
"paginate_description": "Non utilizzare lo scroll infinito per discussioni e messaggi",
"topics_per_page": "Discussioni per Pagina",
"posts_per_page": "Post per Pagina",
"max_items_per_page": "Maximum %1",
"max_items_per_page": "Massimo %1",
"notification_sounds": "Riproduci un suono quando si riceve una notifica",
"notifications_and_sounds": "Notifiche e Suoni",
"incoming-message-sound": "Suono messaggio in entrata",
"outgoing-message-sound": "Suono messaggio in uscita",
"notification-sound": "Suono di notifica",
"no-sound": "Nessun suono",
"upvote-notif-freq": "Upvote Notification Frequency",
"upvote-notif-freq.all": "All Upvotes",
"upvote-notif-freq.everyTen": "Every Ten Upvotes",
"upvote-notif-freq.logarithmic": "On 10, 100, 1000...",
"upvote-notif-freq.disabled": "Disabled",
"upvote-notif-freq": "Frequenza Notifiche dei Mi Piace ",
"upvote-notif-freq.all": "Tutti i Mi Piace",
"upvote-notif-freq.everyTen": "Ogni Dieci Mi Piace",
"upvote-notif-freq.logarithmic": "Ogni 10, 100, 1000...",
"upvote-notif-freq.disabled": "Disabilitate",
"browsing": "Impostazioni di Navigazione",
"open_links_in_new_tab": "Apri i link web in una nuova pagina",
"enable_topic_searching": "Abilita la ricerca negli argomenti",

@ -18,7 +18,7 @@
"reset.notify.text2": "Neem onmiddellijk contact met een beheerder op wanneer je hiervoor geen toestemming hebt gegeven.",
"digest.notifications": "Er zijn ongelezen notificaties van %1:",
"digest.latest_topics": "De meest recente onderwerpen van %1",
"digest.cta": "Klik hier om deze website te bezoeken %1 ",
"digest.cta": "Klik hier om %1 te bezoeken ",
"digest.unsub.info": "Deze samenvatting hebben we naar je verzonden omdat je dat hebt ingesteld.",
"digest.no_topics": "In de afgelopen %1 zijn er geen actieve onderwerpen geweest.",
"digest.day": "dag",
@ -30,7 +30,7 @@
"notif.chat.unsub.info": "Deze notificatie is verzonden vanwege de gebruikersinstellingen voor abonnementen.",
"notif.post.cta": "Klik hier om het volledige bericht te lezen",
"notif.post.unsub.info": "Deze notificatie is door ons verzonden vanwege gebruikersinstellingen voor abonnementen en berichten.",
"notif.cta": "Click here to go to forum",
"notif.cta": "Klik hier om naar het forum te gaan",
"test.text1": "Dit is een testbericht om te verifiëren dat NodeBB de e-mailberichtservice correct heeft opgezet.",
"unsub.cta": "Klik hier om deze instellingen te wijzigen",
"banned.subject": "U bent verbannen van %1",

@ -119,13 +119,13 @@
"not-enough-reputation-to-downvote": "Je hebt onvoldoende reputatie om een negatieve stem uit te mogen brengen",
"not-enough-reputation-to-flag": "Je hebt onvoldoende reputatie om dit bericht aan de beheerders te mogen melden",
"already-flagged": "Je hebt deze post al gerapporteerd",
"self-vote": "You cannot vote on your own post",
"self-vote": "Het is niet mogelijk om op je eigen bericht te stemmen",
"reload-failed": "Tijdens het herladen van \"%1\" is NodeBB een fout of probleem tegengekomen. NodeBB blijft operationeel. Echter het is verstandig om de oorzaak te onderzoeken en wellicht de vorige actie, voor het herladen, ongedaan te maken.",
"registration-error": "Fout tijdens registratie",
"parse-error": "Tijdens het verwerken van het antwoord van de server is er iets misgegaan.",
"wrong-login-type-email": "Gebruik je e-mailadres om in te loggen",
"wrong-login-type-username": "Gebruik je gebruikersnaam om in te loggen",
"sso-registration-disabled": "Registration has been disabled for %1 accounts, please register with an email address first",
"sso-registration-disabled": "Registratie is uitgeschakeld voor %1 accounts, registreer eerst met een e-mailadres",
"invite-maximum-met": "Je heb het maximum aantal mensen uitgenodigd (%1 van de %2).",
"no-session-found": "Geen login sessie gevonden!",
"not-in-room": "Gebruiker niet in de chat",
@ -135,5 +135,5 @@
"invalid-home-page-route": "Onbekende homepage route",
"invalid-session": "Verkeerde sessie combinatie",
"invalid-session-text": "Het lijkt erop dat je login sessie niet meer actief is of niet langer synchroon is met de server. Ververs de pagina.",
"no-topics-selected": "No topics selected!"
"no-topics-selected": "Geen onderwerpen geselecteerd!"
}

@ -9,7 +9,7 @@
"continue_to": "Door naar %1",
"return_to": "Terug naar %1",
"new_notification": "Nieuwe notificatie",
"new_notification_from": "You have a new Notification from %1",
"new_notification_from": "Je hebt een nieuwe notificatie van %1",
"you_have_unread_notifications": "Je hebt nieuwe notificaties.",
"all": "Alles",
"topics": "Onderwerpen",
@ -47,18 +47,18 @@
"email-confirmed-message": "Bedankt voor het bevestigen van je e-mailadres. Je account is nu volledig geactiveerd.",
"email-confirm-error-message": "Er was een probleem met het bevestigen van dit e-mailadres. Misschien is de code niet goed ingevoerd of was de beschikbare tijd inmiddels verstreken.",
"email-confirm-sent": "Bevestigingsmail verstuurd.",
"none": "None",
"notification_only": "Notification Only",
"email_only": "Email Only",
"notification_and_email": "Notification & Email",
"notificationType_upvote": "When someone upvotes your post",
"notificationType_new-topic": "When someone you follow posts a topic",
"notificationType_new-reply": "When a new reply is posted in a topic you are watching",
"notificationType_follow": "When someone starts following you",
"notificationType_new-chat": "When you receive a chat message",
"notificationType_group-invite": "When you receive a group invite",
"notificationType_new-register": "When someone gets added to registration queue",
"notificationType_post-queue": "When a new post is queued",
"notificationType_new-post-flag": "When a post is flagged",
"notificationType_new-user-flag": "When a user is flagged"
"none": "Geen",
"notification_only": "Alleen notificatie",
"email_only": "Alleen e-mail",
"notification_and_email": "Notificatie & e-mail",
"notificationType_upvote": "Als iemand positief stemt voor je bericht",
"notificationType_new-topic": "Wanneer iemand die jij volgt een onderwerp post",
"notificationType_new-reply": "Als een nieuwe reactie komt op een onderwerp dat je volgt",
"notificationType_follow": "Als iemand begint met jou te volgen",
"notificationType_new-chat": "Als je een chat-bericht ontvangt",
"notificationType_group-invite": "Als je een uitnodiging voor een groep ontvangt",
"notificationType_new-register": "Als iemand wordt toegevoegd aan een registratiewachtrij",
"notificationType_post-queue": "Als een bericht aan de wachtrij wordt toegevoegd",
"notificationType_new-post-flag": "Als een bericht wordt gevlagd",
"notificationType_new-user-flag": "Als een gebruiker wordt gevlagd"
}

@ -68,8 +68,8 @@
"thread_tools.restore_confirm": "Zeker weten dit onderwerp te herstellen?",
"thread_tools.purge": "Wis onderwerp ",
"thread_tools.purge_confirm": "Weet je zeker dat je dit onderwerp wil verwijderen?",
"thread_tools.merge_topics": "Merge Topics",
"thread_tools.merge": "Merge",
"thread_tools.merge_topics": "Onderwerpen samenvoegen",
"thread_tools.merge": "Samenvoegen",
"topic_move_success": "Verplaatsen van onderwerp naar %1 succesvol",
"post_delete_confirm": "Is het absoluut de bedoeling dit bericht te verwijderen?",
"post_restore_confirm": "Is het de bedoeling dit bericht te herstellen?",
@ -91,7 +91,7 @@
"fork_pid_count": "%1 bericht(en) geselecteerd",
"fork_success": "Onderwerp is succesvol afgesplitst. Klik hier om het nieuwe onderwerp te zien.",
"delete_posts_instruction": "Klik op de berichten die verwijderd moeten worden",
"merge_topics_instruction": "Click the topics you want to merge",
"merge_topics_instruction": "Klik op de onderwerpen die samengevoegd moeten worden",
"composer.title_placeholder": "Voer hier de titel van het onderwerp in...",
"composer.handle_placeholder": "Naam",
"composer.discard": "Annuleren",

@ -101,11 +101,11 @@
"outgoing-message-sound": "Uitgaand bericht geluid",
"notification-sound": "Notificatie geluid",
"no-sound": "Geen geluid",
"upvote-notif-freq": "Upvote Notification Frequency",
"upvote-notif-freq.all": "All Upvotes",
"upvote-notif-freq.everyTen": "Every Ten Upvotes",
"upvote-notif-freq.logarithmic": "On 10, 100, 1000...",
"upvote-notif-freq.disabled": "Disabled",
"upvote-notif-freq": "Notificatie frequentie voor Upvotes",
"upvote-notif-freq.all": "Alle Upvotes",
"upvote-notif-freq.everyTen": "Elke tien Upvotes",
"upvote-notif-freq.logarithmic": "Bij 10, 100, 1000...",
"upvote-notif-freq.disabled": "Uitgeschakeld",
"browsing": "Instellingen voor bladeren",
"open_links_in_new_tab": "Open uitgaande links naar een externe site in een nieuw tabblad",
"enable_topic_searching": "Inschakelen mogelijkheid op onderwerp te kunnen zoeken",

@ -14,6 +14,7 @@
@import "./manage/groups";
@import "./manage/registration";
@import "./manage/users";
@import "./manage/admins-mods";
@import "./appearance/customise";
@import "./appearance/themes";
@import "./extend/plugins";

@ -0,0 +1,27 @@
.admins-mods {
.user-card {
margin-right: 10px;
padding: 2px;
}
.remove-user-icon {
margin-right: 5px;
margin-left: 5px;
}
.category-depth-1 {
margin-left: 30px;
}
.category-depth-2 {
margin-left: 60px;
}
.category-depth-3 {
margin-left: 90px;
}
.category-depth-4 {
margin-left: 120px;
}
.category-depth-5 {
margin-left: 150px;
}
}

@ -0,0 +1,142 @@
'use strict';
define('admin/manage/admins-mods', ['translator', 'benchpress', 'autocomplete'], function (translator, Benchpress, autocomplete) {
var AdminsMods = {};
AdminsMods.init = function () {
autocomplete.user($('#admin-search'), function (ev, ui) {
socket.emit('admin.user.makeAdmins', [ui.item.user.uid], function (err) {
if (err) {
return app.alertError(err.message);
}
app.alertSuccess('[[admin/manage/users:alerts.make-admin-success]]');
$('#admin-search').val('');
if ($('.administrator-area [data-uid="' + ui.item.user.uid + '"]').length) {
return;
}
app.parseAndTranslate('admin/manage/admins-mods', 'admins.members', { admins: { members: [ui.item.user] } }, function (html) {
$('.administrator-area').prepend(html);
});
});
});
$('.administrator-area').on('click', '.remove-user-icon', function () {
var userCard = $(this).parents('[data-uid]');
var uid = userCard.attr('data-uid');
if (parseInt(uid, 10) === parseInt(app.user.uid, 10)) {
return app.alertError('[[admin/manage/users:alerts.no-remove-yourself-admin]]');
}
bootbox.confirm('[[admin/manage/users:alerts.confirm-remove-admin]]', function (confirm) {
if (confirm) {
socket.emit('admin.user.removeAdmins', [uid], function (err) {
if (err) {
return app.alertError(err.message);
}
app.alertSuccess('[[admin/manage/users:alerts.remove-admin-success]]');
userCard.remove();
});
}
});
});
autocomplete.user($('#global-mod-search'), function (ev, ui) {
socket.emit('admin.groups.join', {
groupName: 'Global Moderators',
uid: ui.item.user.uid,
}, function (err) {
if (err) {
return app.alertError(err.message);
}
app.alertSuccess('[[admin/manage/users:alerts.make-global-mod-success]]');
$('#global-mod-search').val('');
if ($('.global-moderator-area [data-uid="' + ui.item.user.uid + '"]').length) {
return;
}
app.parseAndTranslate('admin/manage/admins-mods', 'globalMods.members', { globalMods: { members: [ui.item.user] } }, function (html) {
$('.global-moderator-area').prepend(html);
$('#no-global-mods-warning').addClass('hidden');
});
});
});
$('.global-moderator-area').on('click', '.remove-user-icon', function () {
var userCard = $(this).parents('[data-uid]');
var uid = userCard.attr('data-uid');
bootbox.confirm('[[admin/manage/users:alerts.confirm-remove-global-mod]]', function (confirm) {
if (confirm) {
socket.emit('admin.groups.leave', { uid: uid, groupName: 'Global Moderators' }, function (err) {
if (err) {
return app.alertError(err.message);
}
app.alertSuccess('[[admin/manage/users:alerts.remove-global-mod-success]]');
userCard.remove();
if (!$('.global-moderator-area').children().length) {
$('#no-global-mods-warning').removeClass('hidden');
}
});
}
});
});
autocomplete.user($('.moderator-search'), function (ev, ui) {
var input = $(ev.target);
var cid = $(ev.target).attr('data-cid');
socket.emit('admin.categories.setPrivilege', {
cid: cid,
privilege: ['moderate'],
set: true,
member: ui.item.user.uid,
}, function (err) {
if (err) {
return app.alertError(err.message);
}
app.alertSuccess('[[admin/manage/users:alerts.make-moderator-success]]');
input.val('');
if ($('.moderator-area[data-cid="' + cid + '"] [data-uid="' + ui.item.user.uid + '"]').length) {
return;
}
app.parseAndTranslate('admin/manage/admins-mods', 'globalMods', { globalMods: [ui.item.user] }, function (html) {
$('.moderator-area[data-cid="' + cid + '"]').prepend(html);
$('.no-moderator-warning[data-cid="' + cid + '"]').addClass('hidden');
});
});
});
$('.moderator-area').on('click', '.remove-user-icon', function () {
var moderatorArea = $(this).parents('[data-cid]');
var cid = moderatorArea.attr('data-cid');
var userCard = $(this).parents('[data-uid]');
var uid = userCard.attr('data-uid');
bootbox.confirm('[[admin/manage/users:alerts.confirm-remove-moderator]]', function (confirm) {
if (confirm) {
socket.emit('admin.categories.setPrivilege', {
cid: cid,
privilege: ['moderate'],
set: false,
member: uid,
}, function (err) {
if (err) {
return app.alertError(err.message);
}
app.alertSuccess('[[admin/manage/users:alerts.remove-moderator-success]]');
userCard.remove();
if (!moderatorArea.children().length) {
$('.no-moderator-warning[data-cid="' + cid + '"]').removeClass('hidden');
}
});
}
});
});
};
return AdminsMods;
});

@ -1,6 +1,5 @@
'use strict';
define('admin/manage/category', [
'uploader',
'iconSelect',
@ -8,8 +7,7 @@ define('admin/manage/category', [
'autocomplete',
'translator',
'categorySelector',
'benchpress',
], function (uploader, iconSelect, colorpicker, autocomplete, translator, categorySelector, Benchpress) {
], function (uploader, iconSelect, colorpicker, autocomplete, translator, categorySelector) {
var Category = {};
var modified_categories = {};
@ -100,7 +98,7 @@ define('admin/manage/category', [
});
$('.copy-settings').on('click', function () {
selectCategoryModal(function (cid) {
categorySelector.modal(function (cid) {
socket.emit('admin.categories.copySettingsFrom', { fromCid: cid, toCid: ajaxify.data.category.cid }, function (err) {
if (err) {
return app.alertError(err.message);
@ -169,8 +167,6 @@ define('admin/manage/category', [
$('button[data-action="setParent"]').removeClass('hide');
});
});
Category.setupPrivilegeTable();
};
function modified(el) {
@ -208,102 +204,12 @@ define('admin/manage/category', [
});
}
Category.setupPrivilegeTable = function () {
$('.privilege-table-container').on('change', 'input[type="checkbox"]', function () {
var checkboxEl = $(this);
var privilege = checkboxEl.parent().attr('data-privilege');
var state = checkboxEl.prop('checked');
var rowEl = checkboxEl.parents('tr');
var member = rowEl.attr('data-group-name') || rowEl.attr('data-uid');
var isPrivate = parseInt(rowEl.attr('data-private') || 0, 10);
var isGroup = rowEl.attr('data-group-name') !== undefined;
if (member) {
if (isGroup && privilege === 'groups:moderate' && !isPrivate && state) {
bootbox.confirm('[[admin/manage/categories:alert.confirm-moderate]]', function (confirm) {
if (confirm) {
Category.setPrivilege(member, privilege, state, checkboxEl);
} else {
checkboxEl.prop('checked', !checkboxEl.prop('checked'));
}
});
} else {
Category.setPrivilege(member, privilege, state, checkboxEl);
}
} else {
app.alertError('[[error:invalid-data]]');
}
});
$('.privilege-table-container').on('click', '[data-action="search.user"]', Category.addUserToPrivilegeTable);
$('.privilege-table-container').on('click', '[data-action="search.group"]', Category.addGroupToPrivilegeTable);
$('.privilege-table-container').on('click', '[data-action="copyToChildren"]', Category.copyPrivilegesToChildren);
$('.privilege-table-container').on('click', '[data-action="copyPrivilegesFrom"]', Category.copyPrivilegesFromCategory);
Category.exposeAssumedPrivileges();
};
Category.refreshPrivilegeTable = function () {
socket.emit('admin.categories.getPrivilegeSettings', ajaxify.data.category.cid, function (err, privileges) {
if (err) {
return app.alertError(err.message);
}
Benchpress.parse('admin/partials/categories/privileges', {
privileges: privileges,
}, function (html) {
translator.translate(html, function (html) {
$('.privilege-table-container').html(html);
Category.exposeAssumedPrivileges();
});
});
});
};
Category.exposeAssumedPrivileges = function () {
/*
If registered-users has a privilege enabled, then all users and groups of that privilege
should be assumed to have that privilege as well, even if not set in the db, so reflect
this arrangement in the table
*/
var privs = [];
$('.privilege-table tr[data-group-name="registered-users"] td input[type="checkbox"]').parent().each(function (idx, el) {
if ($(el).find('input').prop('checked')) {
privs.push(el.getAttribute('data-privilege'));
}
});
for (var x = 0, numPrivs = privs.length; x < numPrivs; x += 1) {
var inputs = $('.privilege-table tr[data-group-name]:not([data-group-name="registered-users"],[data-group-name="guests"]) td[data-privilege="' + privs[x] + '"] input');
inputs.each(function (idx, el) {
if (!el.checked) {
el.indeterminate = true;
}
});
}
};
Category.setPrivilege = function (member, privilege, state, checkboxEl) {
socket.emit('admin.categories.setPrivilege', {
cid: ajaxify.data.category.cid,
privilege: privilege,
set: state,
member: member,
}, function (err) {
if (err) {
return app.alertError(err.message);
}
checkboxEl.replaceWith('<i class="fa fa-spin fa-spinner"></i>');
Category.refreshPrivilegeTable();
});
};
Category.launchParentSelector = function () {
var categories = ajaxify.data.allCategories.filter(function (category) {
return category && !category.disabled && parseInt(category.cid, 10) !== parseInt(ajaxify.data.category.cid, 10);
});
selectCategoryModal(categories, function (parentCid) {
categorySelector.modal(categories, function (parentCid) {
var payload = {};
payload[ajaxify.data.category.cid] = {
@ -327,117 +233,5 @@ define('admin/manage/category', [
});
};
Category.addUserToPrivilegeTable = function () {
var modal = bootbox.dialog({
title: '[[admin/manage/categories:alert.find-user]]',
message: '<input class="form-control input-lg" placeholder="[[admin/manage/categories:alert.user-search]]" />',
show: true,
});
modal.on('shown.bs.modal', function () {
var inputEl = modal.find('input');
autocomplete.user(inputEl, function (ev, ui) {
socket.emit('admin.categories.setPrivilege', {
cid: ajaxify.data.category.cid,
privilege: ['find', 'read', 'topics:read'],
set: true,
member: ui.item.user.uid,
}, function (err) {
if (err) {
return app.alertError(err.message);
}
Category.refreshPrivilegeTable();
modal.modal('hide');
});
});
});
};
Category.addGroupToPrivilegeTable = function () {
var modal = bootbox.dialog({
title: '[[admin/manage/categories:alert.find-group]]',
message: '<input class="form-control input-lg" placeholder="[[admin/manage/categories:alert.group-search]]" />',
show: true,
});
modal.on('shown.bs.modal', function () {
var inputEl = modal.find('input');
autocomplete.group(inputEl, function (ev, ui) {
socket.emit('admin.categories.setPrivilege', {
cid: ajaxify.data.category.cid,
privilege: ['groups:find', 'groups:read', 'groups:topics:read'],
set: true,
member: ui.item.group.name,
}, function (err) {
if (err) {
return app.alertError(err.message);
}
Category.refreshPrivilegeTable();
modal.modal('hide');
});
});
});
};
Category.copyPrivilegesToChildren = function () {
socket.emit('admin.categories.copyPrivilegesToChildren', ajaxify.data.category.cid, function (err) {
if (err) {
return app.alertError(err.message);
}
app.alertSuccess('Privileges copied!');
});
};
Category.copyPrivilegesFromCategory = function () {
selectCategoryModal(function (cid) {
socket.emit('admin.categories.copyPrivilegesFrom', { toCid: ajaxify.data.category.cid, fromCid: cid }, function (err) {
if (err) {
return app.alertError(err.message);
}
ajaxify.refresh();
});
});
};
function selectCategoryModal(categories, callback) {
if (typeof categories === 'function') {
callback = categories;
categories = ajaxify.data.allCategories;
}
Benchpress.parse('admin/partials/categories/select-category', {
categories: categories,
}, function (html) {
translator.translate(html, function (html) {
var modal = bootbox.dialog({
title: '[[modules:composer.select_category]]',
message: html,
buttons: {
save: {
label: '[[global:select]]',
className: 'btn-primary',
callback: submit,
},
},
});
categorySelector.init(modal.find('[component="category-selector"]'));
function submit(ev) {
ev.preventDefault();
var selectedCategory = categorySelector.getSelectedCategory();
if (selectedCategory) {
callback(selectedCategory.cid);
modal.modal('hide');
}
return false;
}
modal.find('form').on('submit', submit);
});
});
}
return Category;
});

@ -0,0 +1,194 @@
'use strict';
define('admin/manage/privileges', [
'autocomplete',
'translator',
'benchpress',
'categorySelector',
], function (autocomplete, translator, Benchpress, categorySelector) {
var Privileges = {};
var cid;
Privileges.init = function () {
cid = ajaxify.data.cid || 0;
$('#category-selector').on('change', function () {
var val = $(this).val();
ajaxify.go('admin/manage/privileges/' + (val === 'global' ? '' : $(this).val()));
});
Privileges.setupPrivilegeTable();
};
Privileges.setupPrivilegeTable = function () {
$('.privilege-table-container').on('change', 'input[type="checkbox"]', function () {
var checkboxEl = $(this);
var privilege = checkboxEl.parent().attr('data-privilege');
var state = checkboxEl.prop('checked');
var rowEl = checkboxEl.parents('tr');
var member = rowEl.attr('data-group-name') || rowEl.attr('data-uid');
var isPrivate = parseInt(rowEl.attr('data-private') || 0, 10);
var isGroup = rowEl.attr('data-group-name') !== undefined;
if (member) {
if (isGroup && privilege === 'groups:moderate' && !isPrivate && state) {
bootbox.confirm('[[admin/manage/categories:alert.confirm-moderate]]', function (confirm) {
if (confirm) {
Privileges.setPrivilege(member, privilege, state, checkboxEl);
} else {
checkboxEl.prop('checked', !checkboxEl.prop('checked'));
}
});
} else {
Privileges.setPrivilege(member, privilege, state, checkboxEl);
}
} else {
app.alertError('[[error:invalid-data]]');
}
});
$('.privilege-table-container').on('click', '[data-action="search.user"]', Privileges.addUserToPrivilegeTable);
$('.privilege-table-container').on('click', '[data-action="search.group"]', Privileges.addGroupToPrivilegeTable);
$('.privilege-table-container').on('click', '[data-action="copyToChildren"]', Privileges.copyPrivilegesToChildren);
$('.privilege-table-container').on('click', '[data-action="copyPrivilegesFrom"]', Privileges.copyPrivilegesFromCategory);
Privileges.exposeAssumedPrivileges();
};
Privileges.refreshPrivilegeTable = function () {
socket.emit('admin.categories.getPrivilegeSettings', cid, function (err, privileges) {
if (err) {
return app.alertError(err.message);
}
var tpl = cid ? 'admin/partials/categories/privileges' : 'admin/partials/global/privileges';
Benchpress.parse(tpl, {
privileges: privileges,
}, function (html) {
translator.translate(html, function (html) {
$('.privilege-table-container').html(html);
Privileges.exposeAssumedPrivileges();
});
});
});
};
Privileges.exposeAssumedPrivileges = function () {
/*
If registered-users has a privilege enabled, then all users and groups of that privilege
should be assumed to have that privilege as well, even if not set in the db, so reflect
this arrangement in the table
*/
var privs = [];
$('.privilege-table tr[data-group-name="registered-users"] td input[type="checkbox"]').parent().each(function (idx, el) {
if ($(el).find('input').prop('checked')) {
privs.push(el.getAttribute('data-privilege'));
}
});
for (var x = 0, numPrivs = privs.length; x < numPrivs; x += 1) {
var inputs = $('.privilege-table tr[data-group-name]:not([data-group-name="registered-users"],[data-group-name="guests"]) td[data-privilege="' + privs[x] + '"] input');
inputs.each(function (idx, el) {
if (!el.checked) {
el.indeterminate = true;
}
});
}
};
Privileges.setPrivilege = function (member, privilege, state, checkboxEl) {
socket.emit('admin.categories.setPrivilege', {
cid: cid,
privilege: privilege,
set: state,
member: member,
}, function (err) {
if (err) {
return app.alertError(err.message);
}
checkboxEl.replaceWith('<i class="fa fa-spin fa-spinner"></i>');
Privileges.refreshPrivilegeTable();
});
};
Privileges.addUserToPrivilegeTable = function () {
var modal = bootbox.dialog({
title: '[[admin/manage/categories:alert.find-user]]',
message: '<input class="form-control input-lg" placeholder="[[admin/manage/categories:alert.user-search]]" />',
show: true,
});
modal.on('shown.bs.modal', function () {
var inputEl = modal.find('input');
autocomplete.user(inputEl, function (ev, ui) {
var defaultPrivileges = cid ? ['find', 'read', 'topics:read'] : ['chat'];
socket.emit('admin.categories.setPrivilege', {
cid: cid,
privilege: defaultPrivileges,
set: true,
member: ui.item.user.uid,
}, function (err) {
if (err) {
return app.alertError(err.message);
}
Privileges.refreshPrivilegeTable();
modal.modal('hide');
});
});
});
};
Privileges.addGroupToPrivilegeTable = function () {
var modal = bootbox.dialog({
title: '[[admin/manage/categories:alert.find-group]]',
message: '<input class="form-control input-lg" placeholder="[[admin/manage/categories:alert.group-search]]" />',
show: true,
});
modal.on('shown.bs.modal', function () {
var inputEl = modal.find('input');
autocomplete.group(inputEl, function (ev, ui) {
var defaultPrivileges = cid ? ['groups:find', 'groups:read', 'groups:topics:read'] : ['groups:chat'];
socket.emit('admin.categories.setPrivilege', {
cid: cid,
privilege: defaultPrivileges,
set: true,
member: ui.item.group.name,
}, function (err) {
if (err) {
return app.alertError(err.message);
}
Privileges.refreshPrivilegeTable();
modal.modal('hide');
});
});
});
};
Privileges.copyPrivilegesToChildren = function () {
socket.emit('admin.categories.copyPrivilegesToChildren', cid, function (err) {
if (err) {
return app.alertError(err.message);
}
app.alertSuccess('Privileges copied!');
});
};
Privileges.copyPrivilegesFromCategory = function () {
categorySelector.modal(function (fromCid) {
socket.emit('admin.categories.copyPrivilegesFrom', { toCid: cid, fromCid: fromCid }, function (err) {
if (err) {
return app.alertError(err.message);
}
ajaxify.refresh();
});
});
};
return Privileges;
});

@ -127,36 +127,6 @@ define('admin/manage/users', ['translator', 'benchpress'], function (translator,
socket.emit('admin.user.resetLockouts', uids, done('[[admin/manage/users:alerts.lockout-reset-success]]'));
});
$('.admin-user').on('click', function () {
var uids = getSelectedUids();
if (!uids.length) {
return;
}
if (uids.indexOf(app.user.uid.toString()) !== -1) {
app.alertError('[[admin/manage/users:alerts.no-remove-yourself-admin]]');
} else {
socket.emit('admin.user.makeAdmins', uids, done('[[admin/manage/users:alerts.make-admin-success]]', '.administrator', true));
}
});
$('.remove-admin-user').on('click', function () {
var uids = getSelectedUids();
if (!uids.length) {
return;
}
if (uids.indexOf(app.user.uid.toString()) !== -1) {
app.alertError('[[admin/manage/users:alerts.no-remove-yourself-admin]]');
} else {
bootbox.confirm('[[admin/manage/users:alerts.confirm-remove-admin]]', function (confirm) {
if (confirm) {
socket.emit('admin.user.removeAdmins', uids, done('[[admin/manage/users:alerts.remove-admin-success]]', '.administrator', false));
}
});
}
});
$('.validate-email').on('click', function () {
var uids = getSelectedUids();
if (!uids.length) {

@ -63,7 +63,7 @@ define('forum/account/edit/password', ['forum/account/header', 'translator', 'zx
onPasswordConfirmChanged();
var btn = $(this);
if ((passwordvalid && passwordsmatch) || app.user.isAdmin) {
if (passwordvalid && passwordsmatch) {
btn.addClass('disabled').find('i').removeClass('hide');
socket.emit('user.changePassword', {
currentPassword: currentPassword.val(),

@ -335,7 +335,7 @@ define('forum/chats', [
ajaxify.data = payload;
Chats.setActive();
Chats.addEventListeners();
messages.scrollToBottom($('.expanded-chat ul'));
if (history.pushState) {
history.pushState({
url: 'user/' + payload.userslug + '/chats/' + payload.roomId,

@ -29,6 +29,11 @@ define('autocomplete', function () {
uid: user.uid,
name: user.username,
slug: user.userslug,
username: user.username,
userslug: user.userslug,
picture: user.picture,
'icon:text': user['icon:text'],
'icon:bgColor': user['icon:bgColor'],
},
};
});

@ -1,7 +1,6 @@
'use strict';
define('categorySelector', function () {
define('categorySelector', ['benchpress', 'translator'], function (Benchpress, translator) {
var categorySelector = {};
var selectedCategory;
var el;
@ -29,6 +28,42 @@ define('categorySelector', function () {
el.find('[component="category-selector-selected"]').html(categoryEl.find('[component="category-markup"]').html());
};
categorySelector.modal = function (categories, callback) {
if (typeof categories === 'function') {
callback = categories;
categories = ajaxify.data.allCategories;
}
Benchpress.parse('admin/partials/categories/select-category', {
categories: categories,
}, function (html) {
translator.translate(html, function (html) {
var modal = bootbox.dialog({
title: '[[modules:composer.select_category]]',
message: html,
buttons: {
save: {
label: '[[global:select]]',
className: 'btn-primary',
callback: submit,
},
},
});
categorySelector.init(modal.find('[component="category-selector"]'));
function submit(ev) {
ev.preventDefault();
var selectedCategory = categorySelector.getSelectedCategory();
if (selectedCategory) {
callback(selectedCategory.cid);
modal.modal('hide');
}
return false;
}
modal.find('form').on('submit', submit);
});
});
};
return categorySelector;
});

@ -338,7 +338,7 @@ Categories.buildForSelect = function (uid, privilege, callback) {
};
Categories.buildForSelectCategories = function (categories, callback) {
function recursive(category, categoriesData, level) {
function recursive(category, categoriesData, level, depth) {
if (category.link) {
return;
}
@ -347,10 +347,11 @@ Categories.buildForSelectCategories = function (categories, callback) {
category.value = category.cid;
category.level = level;
category.text = level + bullet + category.name;
category.depth = depth;
categoriesData.push(category);
category.children.forEach(function (child) {
recursive(child, categoriesData, '&nbsp;&nbsp;&nbsp;&nbsp;' + level);
recursive(child, categoriesData, '&nbsp;&nbsp;&nbsp;&nbsp;' + level, depth + 1);
});
}
@ -361,7 +362,7 @@ Categories.buildForSelectCategories = function (categories, callback) {
});
categories.forEach(function (category) {
recursive(category, categoriesData, '');
recursive(category, categoriesData, '', 0);
});
callback(null, categoriesData);
};

@ -62,7 +62,6 @@ module.exports = function (Categories) {
'posts:edit',
'posts:delete',
'topics:delete',
'upload:post:image',
];
async.series([

@ -31,7 +31,11 @@ module.exports = function (Categories) {
category.name = validator.escape(String(category.name || ''));
category.disabled = category.hasOwnProperty('disabled') ? parseInt(category.disabled, 10) === 1 : undefined;
category.isSection = category.hasOwnProperty('isSection') ? parseInt(category.isSection, 10) === 1 : undefined;
category.icon = category.icon || 'hidden';
if (category.hasOwnProperty('icon')) {
category.icon = category.icon || 'hidden';
}
if (category.hasOwnProperty('post_count')) {
category.post_count = category.post_count || 0;
category.totalPostCount = category.post_count;

@ -30,7 +30,7 @@ function updatePackageFile() {
exports.updatePackageFile = updatePackageFile;
function installAll() {
process.stdout.write('\n');
process.stdout.write(' started\n'.green);
var prod = global.env !== 'development';
var command = 'npm install';

@ -212,7 +212,7 @@ function upgradePlugins(callback) {
});
} else {
console.log('Package upgrades skipped'.yellow + '. Check for upgrades at any time by running "'.reset + './nodebb upgrade -p'.green + '".'.reset);
callback(null, true);
callback();
}
});
});

@ -16,6 +16,7 @@ var steps = {
handler: function (next) {
packageInstall.updatePackageFile();
packageInstall.preserveExtraneousPlugins();
process.stdout.write(' OK\n'.green);
next();
},
},
@ -54,11 +55,8 @@ function runSteps(tasks) {
tasks = tasks.map(function (key, i) {
return function (next) {
process.stdout.write('\n' + ((i + 1) + '. ').bold + steps[key].message.yellow);
return steps[key].handler(function (err, inhibitOk) {
return steps[key].handler(function (err) {
if (err) { return next(err); }
if (!inhibitOk) {
process.stdout.write(' OK'.green + '\n'.reset);
}
next();
});
};

@ -5,6 +5,7 @@ var async = require('async');
var messaging = require('../../messaging');
var meta = require('../../meta');
var user = require('../../user');
var privileges = require('../../privileges');
var helpers = require('../helpers');
var chatsController = module.exports;
@ -26,6 +27,13 @@ chatsController.get = function (req, res, callback) {
if (!uid) {
return callback();
}
privileges.global.can('chat', req.uid, next);
},
function (canChat, next) {
if (!canChat) {
return next(new Error('[[error:no-privileges]]'));
}
messaging.getRecentChats(req.uid, uid, 0, 19, next);
},
function (_recentChats, next) {

@ -3,6 +3,8 @@
var adminController = {
dashboard: require('./admin/dashboard'),
categories: require('./admin/categories'),
privileges: require('./admin/privileges'),
adminsMods: require('./admin/admins-mods'),
tags: require('./admin/tags'),
postQueue: require('./admin/postqueue'),
blacklist: require('./admin/blacklist'),

@ -0,0 +1,50 @@
'use strict';
var async = require('async');
var groups = require('../../groups');
var categories = require('../../categories');
var AdminsMods = module.exports;
AdminsMods.get = function (req, res, next) {
async.waterfall([
function (next) {
async.parallel({
admins: function (next) {
groups.get('administrators', { uid: req.uid }, next);
},
globalMods: function (next) {
groups.get('Global Moderators', { uid: req.uid }, next);
},
categories: function (next) {
getModeratorsOfCategories(req.uid, next);
},
}, next);
},
function (results) {
res.render('admin/manage/admins-mods', results);
},
], next);
};
function getModeratorsOfCategories(uid, callback) {
async.waterfall([
function (next) {
categories.buildForSelect(uid, 'find', next);
},
function (categoryData, next) {
async.map(categoryData, function (category, next) {
async.waterfall([
function (next) {
categories.getModerators(category.cid, next);
},
function (moderators, next) {
category.moderators = moderators;
next(null, category);
},
], next);
}, next);
},
], callback);
}

@ -3,7 +3,6 @@
var async = require('async');
var categories = require('../../categories');
var privileges = require('../../privileges');
var analytics = require('../../analytics');
var plugins = require('../../plugins');
var translator = require('../../translator');
@ -15,7 +14,6 @@ categoriesController.get = function (req, res, callback) {
function (next) {
async.parallel({
category: async.apply(categories.getCategories, [req.params.category_id], req.user.uid),
privileges: async.apply(privileges.categories.list, req.params.category_id),
allCategories: async.apply(categories.buildForSelect, req.uid, 'read'),
}, next);
},
@ -36,7 +34,6 @@ categoriesController.get = function (req, res, callback) {
req: req,
res: res,
category: category,
privileges: data.privileges,
allCategories: data.allCategories,
}, next);
},
@ -44,7 +41,6 @@ categoriesController.get = function (req, res, callback) {
data.category.name = translator.escape(String(data.category.name));
res.render('admin/manage/category', {
category: data.category,
privileges: data.privileges,
allCategories: data.allCategories,
});
},

@ -0,0 +1,39 @@
'use strict';
var async = require('async');
var categories = require('../../categories');
var privileges = require('../../privileges');
var privilegesController = module.exports;
privilegesController.get = function (req, res, callback) {
var cid = req.params.cid ? req.params.cid : 0;
async.waterfall([
function (next) {
async.parallel({
privileges: function (next) {
if (!cid) {
privileges.global.list(next);
} else {
privileges.categories.list(cid, next);
}
},
allCategories: async.apply(categories.buildForSelect, req.uid, 'read'),
}, next);
},
function (data) {
data.allCategories.forEach(function (category) {
if (category) {
category.selected = parseInt(category.cid, 10) === parseInt(cid, 10);
}
});
res.render('admin/manage/privileges', {
privileges: data.privileges,
allCategories: data.allCategories,
cid: cid,
});
},
], callback);
};

@ -1,61 +1,56 @@
'use strict';
var async = require('async');
var plugins = require('../plugins');
var meta = require('../meta');
var user = require('../user');
var pubsub = require('../pubsub');
var adminHomePageRoute;
var getRoute;
function configUpdated() {
adminHomePageRoute = (meta.config.homePageRoute || meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories';
getRoute = parseInt(meta.config.allowUserHomePage, 10) ? getRouteAllowUserHomePage : getRouteDisableUserHomePage;
}
function getRouteDisableUserHomePage(uid, next) {
next(null, adminHomePageRoute);
function adminHomePageRoute() {
return (meta.config.homePageRoute || meta.config.homePageCustom || '').replace(/^\/+/, '') || 'categories';
}
function getRouteAllowUserHomePage(uid, next) {
user.getSettings(uid, function (err, settings) {
if (err) {
return next(err);
}
var route = adminHomePageRoute;
if (settings.homePageRoute !== 'undefined' && settings.homePageRoute !== 'none') {
route = settings.homePageRoute || route;
}
next(null, route);
});
function getUserHomeRoute(uid, callback) {
async.waterfall([
function (next) {
user.getSettings(uid, next);
},
function (settings, next) {
var route = adminHomePageRoute();
if (settings.homePageRoute !== 'undefined' && settings.homePageRoute !== 'none') {
route = settings.homePageRoute || route;
}
next(null, route);
},
], callback);
}
pubsub.on('config:update', configUpdated);
configUpdated();
function rewrite(req, res, next) {
if (req.path !== '/' && req.path !== '/api/' && req.path !== '/api') {
return next();
}
getRoute(req.uid, function (err, route) {
if (err) {
return next(err);
}
var hook = 'action:homepage.get:' + route;
if (!plugins.hasListeners(hook)) {
req.url = req.path + (!req.path.endsWith('/') ? '/' : '') + route;
} else {
res.locals.homePageRoute = route;
}
next();
});
async.waterfall([
function (next) {
if (parseInt(meta.config.allowUserHomePage, 10)) {
getUserHomeRoute(req.uid, next);
} else {
next(null, adminHomePageRoute());
}
},
function (route, next) {
var hook = 'action:homepage.get:' + route;
if (!plugins.hasListeners(hook)) {
req.url = req.path + (!req.path.endsWith('/') ? '/' : '') + route;
} else {
res.locals.homePageRoute = route;
}
next();
},
], next);
}
exports.rewrite = rewrite;

@ -37,9 +37,6 @@ uploadsController.upload = function (req, res, filesIterator) {
uploadsController.uploadPost = function (req, res, next) {
uploadsController.upload(req, res, function (uploadedFile, next) {
if (!parseInt(req.body.cid, 10)) {
return next(new Error('[[error:category-not-selected]]'));
}
var isImage = uploadedFile.type.match(/image./);
if (isImage) {
uploadAsImage(req, uploadedFile, next);
@ -52,7 +49,7 @@ uploadsController.uploadPost = function (req, res, next) {
function uploadAsImage(req, uploadedFile, callback) {
async.waterfall([
function (next) {
privileges.categories.can('upload:post:image', req.body.cid, req.uid, next);
privileges.global.can('upload:post:image', req.uid, next);
},
function (canUpload, next) {
if (!canUpload) {
@ -82,7 +79,7 @@ function uploadAsImage(req, uploadedFile, callback) {
function uploadAsFile(req, uploadedFile, callback) {
async.waterfall([
function (next) {
privileges.categories.can('upload:post:file', req.body.cid, req.uid, next);
privileges.global.can('upload:post:file', req.uid, next);
},
function (canUpload, next) {
if (!canUpload) {

@ -83,8 +83,8 @@ module.exports = function (db, module) {
if (!key) {
return callback();
}
db.collection('objects').findAndModify({ _key: key }, {}, { $inc: { value: 1 } }, { new: true, upsert: true }, function (err, result) {
callback(err, result && result.value ? result.value.value : null);
db.collection('objects').findAndModify({ _key: key }, {}, { $inc: { data: 1 } }, { new: true, upsert: true }, function (err, result) {
callback(err, result && result.value ? result.value.data : null);
});
};
@ -108,6 +108,7 @@ module.exports = function (db, module) {
if (!data) {
return callback(null, null);
}
delete data.expireAt;
var keys = Object.keys(data);
if (keys.length === 4 && data.hasOwnProperty('_key') && data.hasOwnProperty('score') && data.hasOwnProperty('value')) {
return callback(null, 'zset');

@ -353,6 +353,11 @@ function createGlobalModeratorsGroup(next) {
], next);
}
function giveGlobalPrivileges(next) {
var privileges = require('./privileges');
privileges.global.give(['chat', 'upload:post:image'], 'registered-users', next);
}
function createCategories(next) {
var Categories = require('./categories');
@ -498,6 +503,7 @@ install.setup = function (callback) {
createCategories,
createAdministrator,
createGlobalModeratorsGroup,
giveGlobalPrivileges,
createMenuItems,
createWelcomePost,
enableDefaultPlugins,

@ -76,6 +76,7 @@ module.exports = function (Messaging) {
notifications.create({
type: 'new-chat',
subject: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]',
bodyShort: '[[notifications:new_message_from, ' + messageObj.fromUser.username + ']]',
bodyLong: messageObj.content,
nid: 'chat_' + fromuid + '_' + roomId,

@ -212,7 +212,7 @@ function build(targets, callback) {
}
winston.info('[build] Asset compilation successful. Completed in ' + totalTime + 'sec.');
callback(null, true);
callback();
});
}

@ -12,6 +12,7 @@ var meta = require('../meta');
var plugins = require('../plugins');
var navigation = require('../navigation');
var translator = require('../translator');
var privileges = require('../privileges');
var utils = require('../utils');
var controllers = {
@ -77,6 +78,9 @@ module.exports = function (middleware) {
isModerator: function (next) {
user.isModeratorOfAnyCategory(req.uid, next);
},
privileges: function (next) {
privileges.global.get(req.uid, next);
},
user: function (next) {
var userData = {
uid: 0,
@ -132,6 +136,8 @@ module.exports = function (middleware) {
results.user.isAdmin = results.isAdmin;
results.user.isGlobalMod = results.isGlobalMod;
results.user.isMod = !!results.isModerator;
results.user.privileges = results.privileges;
results.user.uid = parseInt(results.user.uid, 10);
results.user.email = String(results.user.email);
results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1;
@ -183,6 +189,7 @@ module.exports = function (middleware) {
templateValues.isAdmin = results.user.isAdmin;
templateValues.isGlobalMod = results.user.isGlobalMod;
templateValues.showModMenu = results.user.isAdmin || results.user.isGlobalMod || results.user.isMod;
templateValues.canChat = results.canChat && parseInt(meta.config.disableChat, 10) !== 1;
templateValues.user = results.user;
templateValues.userJSON = jsesc(JSON.stringify(results.user), { isScriptContext: true });
templateValues.useCustomCSS = parseInt(meta.config.useCustomCSS, 10) === 1 && meta.config.customCSS;

@ -220,7 +220,7 @@ function pushToUids(uids, notification, callback) {
async.eachLimit(uids, 3, function (uid, next) {
emailer.send('notification', uid, {
path: notification.path,
subject: '[[notifications:new_notification_from, ' + meta.config.title + ']]',
subject: notification.subject || '[[notifications:new_notification_from, ' + meta.config.title + ']]',
intro: utils.stripHTMLTags(notification.bodyShort),
body: utils.stripHTMLTags(notification.bodyLong || ''),
showUnsubscribe: true,

@ -213,8 +213,8 @@ Plugins.list = function (matching, callback) {
require('request')(url, {
json: true,
}, function (err, res, body) {
if (err) {
winston.error('Error parsing plugins', err);
if (err || (res && res.statusCode !== 200)) {
winston.error('Error loading ' + url, err || body);
return Plugins.normalise([], callback);
}
@ -225,7 +225,7 @@ Plugins.list = function (matching, callback) {
Plugins.normalise = function (apiReturn, callback) {
var pluginMap = {};
var dependencies = require(path.join(nconf.get('base_dir'), 'package.json')).dependencies;
apiReturn = apiReturn || [];
apiReturn = Array.isArray(apiReturn) ? apiReturn : [];
for (var i = 0; i < apiReturn.length; i += 1) {
apiReturn[i].id = apiReturn[i].name;
apiReturn[i].installed = false;

@ -12,8 +12,6 @@ privileges.privilegeLabels = [
{ name: 'Edit Posts' },
{ name: 'Delete Posts' },
{ name: 'Delete Topics' },
{ name: 'Upload Images' },
{ name: 'Upload Files' },
{ name: 'Purge' },
{ name: 'Moderate' },
];
@ -28,8 +26,6 @@ privileges.userPrivilegeList = [
'posts:edit',
'posts:delete',
'topics:delete',
'upload:post:image',
'upload:post:file',
'purge',
'moderate',
];
@ -40,6 +36,7 @@ privileges.groupPrivilegeList = privileges.userPrivilegeList.map(function (privi
privileges.privilegeList = privileges.userPrivilegeList.concat(privileges.groupPrivilegeList);
require('./privileges/global')(privileges);
require('./privileges/categories')(privileges);
require('./privileges/topics')(privileges);
require('./privileges/posts')(privileges);

@ -15,121 +15,20 @@ module.exports = function (privileges) {
privileges.categories.list = function (cid, callback) {
// Method used in admin/category controller to show all users/groups with privs in that given cid
var privilegeLabels = privileges.privilegeLabels.slice();
var userPrivilegeList = privileges.userPrivilegeList.slice();
var groupPrivilegeList = privileges.groupPrivilegeList.slice();
async.waterfall([
function (next) {
async.parallel({
labels: function (next) {
async.parallel({
users: async.apply(plugins.fireHook, 'filter:privileges.list_human', privilegeLabels),
groups: async.apply(plugins.fireHook, 'filter:privileges.groups.list_human', privilegeLabels),
users: async.apply(plugins.fireHook, 'filter:privileges.list_human', privileges.privilegeLabels.slice()),
groups: async.apply(plugins.fireHook, 'filter:privileges.groups.list_human', privileges.privilegeLabels.slice()),
}, next);
},
users: function (next) {
var userPrivileges;
var memberSets;
async.waterfall([
async.apply(plugins.fireHook, 'filter:privileges.list', userPrivilegeList),
function (_privs, next) {
userPrivileges = _privs;
groups.getMembersOfGroups(userPrivileges.map(function (privilege) {
return 'cid:' + cid + ':privileges:' + privilege;
}), next);
},
function (_memberSets, next) {
memberSets = _memberSets.map(function (set) {
return set.map(function (uid) {
return parseInt(uid, 10);
});
});
var members = _.uniq(_.flatten(memberSets));
user.getUsersFields(members, ['picture', 'username'], next);
},
function (memberData, next) {
memberData.forEach(function (member) {
member.privileges = {};
for (var x = 0, numPrivs = userPrivileges.length; x < numPrivs; x += 1) {
member.privileges[userPrivileges[x]] = memberSets[x].indexOf(parseInt(member.uid, 10)) !== -1;
}
});
next(null, memberData);
},
], next);
helpers.getUserPrivileges(cid, 'filter:privileges.list', privileges.userPrivilegeList, next);
},
groups: function (next) {
var groupPrivileges;
async.waterfall([
async.apply(plugins.fireHook, 'filter:privileges.groups.list', groupPrivilegeList),
function (_privs, next) {
groupPrivileges = _privs;
async.parallel({
memberSets: function (next) {
groups.getMembersOfGroups(groupPrivileges.map(function (privilege) {
return 'cid:' + cid + ':privileges:' + privilege;
}), next);
},
groupNames: function (next) {
groups.getGroups('groups:createtime', 0, -1, next);
},
}, next);
},
function (results, next) {
var memberSets = results.memberSets;
var uniqueGroups = _.uniq(_.flatten(memberSets));
var groupNames = results.groupNames.filter(function (groupName) {
return groupName.indexOf(':privileges:') === -1 && uniqueGroups.indexOf(groupName) !== -1;
});
groupNames = groups.ephemeralGroups.concat(groupNames);
var registeredUsersIndex = groupNames.indexOf('registered-users');
if (registeredUsersIndex !== -1) {
groupNames.splice(0, 0, groupNames.splice(registeredUsersIndex, 1)[0]);
} else {
groupNames = ['registered-users'].concat(groupNames);
}
var adminIndex = groupNames.indexOf('administrators');
if (adminIndex !== -1) {
groupNames.splice(adminIndex, 1);
}
var memberPrivs;
var memberData = groupNames.map(function (member) {
memberPrivs = {};
for (var x = 0, numPrivs = groupPrivileges.length; x < numPrivs; x += 1) {
memberPrivs[groupPrivileges[x]] = memberSets[x].indexOf(member) !== -1;
}
return {
name: member,
privileges: memberPrivs,
};
});
next(null, memberData);
},
function (memberData, next) {
// Grab privacy info for the groups as well
async.map(memberData, function (member, next) {
async.waterfall([
function (next) {
groups.isPrivate(member.name, next);
},
function (isPrivate, next) {
member.isPrivate = isPrivate;
next(null, member);
},
], next);
}, next);
},
], next);
helpers.getGroupPrivileges(cid, 'filter:privileges.groups.list', privileges.groupPrivilegeList, next);
},
}, next);
},
@ -299,19 +198,13 @@ module.exports = function (privileges) {
};
privileges.categories.give = function (privileges, cid, groupName, callback) {
giveOrRescind(groups.join, privileges, cid, groupName, callback);
helpers.giveOrRescind(groups.join, privileges, cid, groupName, callback);
};
privileges.categories.rescind = function (privileges, cid, groupName, callback) {
giveOrRescind(groups.leave, privileges, cid, groupName, callback);
helpers.giveOrRescind(groups.leave, privileges, cid, groupName, callback);
};
function giveOrRescind(method, privileges, cid, groupName, callback) {
async.eachSeries(privileges, function (privilege, next) {
method('cid:' + cid + ':privileges:groups:' + privilege, groupName, next);
}, callback);
}
privileges.categories.canMoveAllTopics = function (currentCid, targetCid, uid, callback) {
async.waterfall([
function (next) {

@ -0,0 +1,128 @@
'use strict';
var async = require('async');
var _ = require('lodash');
var user = require('../user');
var groups = require('../groups');
var helpers = require('./helpers');
var plugins = require('../plugins');
module.exports = function (privileges) {
privileges.global = {};
privileges.global.privilegeLabels = [
{ name: 'Chat' },
{ name: 'Upload Images' },
{ name: 'Upload Files' },
];
privileges.global.userPrivilegeList = [
'chat',
'upload:post:image',
'upload:post:file',
];
privileges.global.groupPrivilegeList = privileges.global.userPrivilegeList.map(function (privilege) {
return 'groups:' + privilege;
});
privileges.global.list = function (callback) {
async.waterfall([
function (next) {
async.parallel({
labels: function (next) {
async.parallel({
users: async.apply(plugins.fireHook, 'filter:privileges.global.list_human', privileges.global.privilegeLabels.slice()),
groups: async.apply(plugins.fireHook, 'filter:privileges.global.groups.list_human', privileges.global.privilegeLabels.slice()),
}, next);
},
users: function (next) {
helpers.getUserPrivileges(0, 'filter:privileges.global.list', privileges.global.userPrivilegeList, next);
},
groups: function (next) {
helpers.getGroupPrivileges(0, 'filter:privileges.global.groups.list', privileges.global.groupPrivilegeList, next);
},
}, next);
},
function (payload, next) {
// This is a hack because I can't do {labels.users.length} to echo the count in templates.js
payload.columnCount = payload.labels.users.length + 2;
next(null, payload);
},
], callback);
};
privileges.global.get = function (uid, callback) {
async.waterfall([
function (next) {
async.parallel({
privileges: function (next) {
helpers.isUserAllowedTo(privileges.global.userPrivilegeList, uid, 0, next);
},
isAdministrator: function (next) {
user.isAdministrator(uid, next);
},
isGlobalModerator: function (next) {
user.isGlobalModerator(uid, next);
},
}, next);
},
function (results, next) {
var privData = _.zipObject(privileges.global.userPrivilegeList, results.privileges);
var isAdminOrMod = results.isAdministrator || results.isGlobalModerator;
plugins.fireHook('filter:privileges.global.get', {
chat: privData.chat || isAdminOrMod,
'upload:post:image': privData['upload:post:image'] || isAdminOrMod,
'upload:post:file': privData['upload:post:file'] || isAdminOrMod,
}, next);
},
], callback);
};
privileges.global.can = function (privilege, uid, callback) {
helpers.some([
function (next) {
helpers.isUserAllowedTo(privilege, uid, [0], function (err, results) {
next(err, Array.isArray(results) && results.length ? results[0] : false);
});
},
function (next) {
user.isGlobalModerator(uid, next);
},
function (next) {
user.isAdministrator(uid, next);
},
], callback);
};
privileges.global.give = function (privileges, groupName, callback) {
helpers.giveOrRescind(groups.join, privileges, 0, groupName, callback);
};
privileges.global.rescind = function (privileges, groupName, callback) {
helpers.giveOrRescind(groups.leave, privileges, 0, groupName, callback);
};
privileges.global.userPrivileges = function (uid, callback) {
var tasks = {};
privileges.global.userPrivilegeList.forEach(function (privilege) {
tasks[privilege] = async.apply(groups.isMember, uid, 'cid:0:privileges:' + privilege);
});
async.parallel(tasks, callback);
};
privileges.global.groupPrivileges = function (groupName, callback) {
var tasks = {};
privileges.global.groupPrivilegeList.forEach(function (privilege) {
tasks[privilege] = async.apply(groups.isMember, groupName, 'cid:0:privileges:' + privilege);
});
async.parallel(tasks, callback);
};
};

@ -2,7 +2,11 @@
'use strict';
var async = require('async');
var _ = require('lodash');
var groups = require('../groups');
var user = require('../user');
var plugins = require('../plugins');
var helpers = module.exports;
@ -111,3 +115,115 @@ function isGuestAllowedToPrivileges(privileges, cid, callback) {
groups.isMemberOfGroups('guests', groupKeys, callback);
}
helpers.getUserPrivileges = function (cid, hookName, userPrivilegeList, callback) {
var userPrivileges;
var memberSets;
async.waterfall([
async.apply(plugins.fireHook, hookName, userPrivilegeList.slice()),
function (_privs, next) {
userPrivileges = _privs;
groups.getMembersOfGroups(userPrivileges.map(function (privilege) {
return 'cid:' + cid + ':privileges:' + privilege;
}), next);
},
function (_memberSets, next) {
memberSets = _memberSets.map(function (set) {
return set.map(function (uid) {
return parseInt(uid, 10);
});
});
var members = _.uniq(_.flatten(memberSets));
user.getUsersFields(members, ['picture', 'username'], next);
},
function (memberData, next) {
memberData.forEach(function (member) {
member.privileges = {};
for (var x = 0, numPrivs = userPrivileges.length; x < numPrivs; x += 1) {
member.privileges[userPrivileges[x]] = memberSets[x].indexOf(parseInt(member.uid, 10)) !== -1;
}
});
next(null, memberData);
},
], callback);
};
helpers.getGroupPrivileges = function (cid, hookName, groupPrivilegeList, callback) {
var groupPrivileges;
async.waterfall([
async.apply(plugins.fireHook, hookName, groupPrivilegeList.slice()),
function (_privs, next) {
groupPrivileges = _privs;
async.parallel({
memberSets: function (next) {
groups.getMembersOfGroups(groupPrivileges.map(function (privilege) {
return 'cid:' + cid + ':privileges:' + privilege;
}), next);
},
groupNames: function (next) {
groups.getGroups('groups:createtime', 0, -1, next);
},
}, next);
},
function (results, next) {
var memberSets = results.memberSets;
var uniqueGroups = _.uniq(_.flatten(memberSets));
var groupNames = results.groupNames.filter(function (groupName) {
return groupName.indexOf(':privileges:') === -1 && uniqueGroups.indexOf(groupName) !== -1;
});
groupNames = groups.ephemeralGroups.concat(groupNames);
var registeredUsersIndex = groupNames.indexOf('registered-users');
if (registeredUsersIndex !== -1) {
groupNames.splice(0, 0, groupNames.splice(registeredUsersIndex, 1)[0]);
} else {
groupNames = ['registered-users'].concat(groupNames);
}
var adminIndex = groupNames.indexOf('administrators');
if (adminIndex !== -1) {
groupNames.splice(adminIndex, 1);
}
var memberPrivs;
var memberData = groupNames.map(function (member) {
memberPrivs = {};
for (var x = 0, numPrivs = groupPrivileges.length; x < numPrivs; x += 1) {
memberPrivs[groupPrivileges[x]] = memberSets[x].indexOf(member) !== -1;
}
return {
name: member,
privileges: memberPrivs,
};
});
next(null, memberData);
},
function (memberData, next) {
// Grab privacy info for the groups as well
async.map(memberData, function (member, next) {
async.waterfall([
function (next) {
groups.isPrivate(member.name, next);
},
function (isPrivate, next) {
member.isPrivate = isPrivate;
next(null, member);
},
], next);
}, next);
},
], callback);
};
helpers.giveOrRescind = function (method, privileges, cid, groupName, callback) {
async.eachSeries(privileges, function (privilege, next) {
method('cid:' + cid + ':privileges:groups:' + privilege, groupName, next);
}, callback);
};

@ -55,6 +55,7 @@ function addRoutes(router, middleware, controllers) {
router.get('/manage/categories/:category_id', middlewares, controllers.admin.categories.get);
router.get('/manage/categories/:category_id/analytics', middlewares, controllers.admin.categories.getAnalytics);
router.get('/manage/privileges/:cid?', middlewares, controllers.admin.privileges.get);
router.get('/manage/tags', middlewares, controllers.admin.tags.get);
router.get('/manage/post-queue', middlewares, controllers.admin.postQueue.get);
router.get('/manage/ip-blacklist', middlewares, controllers.admin.blacklist.get);
@ -71,6 +72,8 @@ function addRoutes(router, middleware, controllers) {
router.get('/manage/users/banned', middlewares, controllers.admin.users.banned);
router.get('/manage/registration', middlewares, controllers.admin.users.registrationQueue);
router.get('/manage/admins-mods', middlewares, controllers.admin.adminsMods.get);
router.get('/manage/groups', middlewares, controllers.admin.groups.list);
router.get('/manage/groups/:name', middlewares, controllers.admin.groups.get);

@ -83,7 +83,11 @@ Categories.setPrivilege = function (socket, data, callback) {
};
Categories.getPrivilegeSettings = function (socket, cid, callback) {
privileges.categories.list(cid, callback);
if (!parseInt(cid, 10)) {
privileges.global.list(callback);
} else {
privileges.categories.list(cid, callback);
}
};
Categories.copyPrivilegesToChildren = function (socket, cid, callback) {

@ -11,6 +11,7 @@ var Messaging = require('../messaging');
var utils = require('../utils');
var server = require('./');
var user = require('../user');
var privileges = require('../privileges');
var SocketModules = module.exports;
@ -73,6 +74,12 @@ SocketModules.chats.newRoom = function (socket, data, callback) {
async.waterfall([
function (next) {
privileges.global.can('chat', socket.uid, next);
},
function (canChat, next) {
if (!canChat) {
return next(new Error('[[error:no-privileges]]'));
}
Messaging.canMessageUser(socket.uid, data.touid, next);
},
function (next) {
@ -92,6 +99,13 @@ SocketModules.chats.send = function (socket, data, callback) {
async.waterfall([
function (next) {
privileges.global.can('chat', socket.uid, next);
},
function (canChat, next) {
if (!canChat) {
return next(new Error('[[error:no-privileges]]'));
}
plugins.fireHook('filter:messaging.send', {
data: data,
uid: socket.uid,
@ -133,6 +147,13 @@ SocketModules.chats.loadRoom = function (socket, data, callback) {
async.waterfall([
function (next) {
privileges.global.can('chat', socket.uid, next);
},
function (canChat, next) {
if (!canChat) {
return next(new Error('[[error:no-privileges]]'));
}
Messaging.isUserInRoom(socket.uid, data.roomId, next);
},
function (inRoom, next) {
@ -174,6 +195,13 @@ SocketModules.chats.addUserToRoom = function (socket, data, callback) {
var uid;
async.waterfall([
function (next) {
privileges.global.can('chat', socket.uid, next);
},
function (canChat, next) {
if (!canChat) {
return next(new Error('[[error:no-privileges]]'));
}
Messaging.getUserCountInRoom(data.roomId, next);
},
function (userCount, next) {

@ -219,6 +219,7 @@ module.exports = function (Topics) {
notifications.create({
type: 'new-reply',
subject: title,
bodyShort: '[[notifications:user_posted_to, ' + postData.user.username + ', ' + titleEscaped + ']]',
bodyLong: postData.content,
pid: postData.pid,

@ -0,0 +1,12 @@
'use strict';
var groups = require('../../groups');
module.exports = {
name: 'Give chat privilege to registered-users',
timestamp: Date.UTC(2017, 11, 18),
method: function (callback) {
groups.join('cid:0:privileges:groups:chat', 'registered-users', callback);
},
};

@ -0,0 +1,45 @@
'use strict';
var async = require('async');
var groups = require('../../groups');
var privileges = require('../../privileges');
var db = require('../../database');
module.exports = {
name: 'Give upload privilege to registered-users globally if it is given on a category',
timestamp: Date.UTC(2018, 0, 3),
method: function (callback) {
db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) {
if (err) {
return callback(err);
}
async.eachSeries(cids, function (cid, next) {
getGroupPrivileges(cid, function (err, groupPrivileges) {
if (err) {
return next(err);
}
var privs = [];
if (groupPrivileges['groups:upload:post:image']) {
privs.push('upload:post:image');
}
if (groupPrivileges['groups:upload:post:file']) {
privs.push('upload:post:file');
}
privileges.global.give(privs, 'registered-users', next);
});
}, callback);
});
},
};
function getGroupPrivileges(cid, callback) {
var tasks = {};
['groups:upload:post:image', 'groups:upload:post:file'].forEach(function (privilege) {
tasks[privilege] = async.apply(groups.isMember, 'registered-users', 'cid:' + cid + ':privileges:' + privilege);
});
async.parallel(tasks, callback);
}

@ -0,0 +1,64 @@
<div class="admins-mods">
<h4><!-- IF admins.icon --><i class="fa {admins.icon}"></i> <!-- ENDIF admins.icon -->[[admin/manage/admins-mods:administrators]]</h4>
<div class="administrator-area">
<!-- BEGIN admins.members -->
<div class="user-card pull-left" data-uid="{admins.members.uid}">
<!-- IF admins.members.picture -->
<img class="avatar avatar-sm" src="{admins.members.picture}" />
<!-- ELSE -->
<div class="avatar avatar-sm" style="background-color: {admins.members.icon:bgColor};">{admins.members.icon:text}</div>
<!-- ENDIF admins.members.picture -->
<a href="{config.relative_path}/user/{admins.members.userslug}">{admins.members.username}</a>
<i class="remove-user-icon fa fa-times" role="button"></i>
</div>
<!-- END admins.members -->
</div>
<input id="admin-search" class="form-control" placeholder="[[admin/manage/admins-mods:add-administrator]]" />
<br/>
<h4><!-- IF globalMods.icon --><i class="fa {globalMods.icon}"></i> <!-- ENDIF globalMods.icon -->[[admin/manage/admins-mods:global-moderators]]</h4>
<div class="global-moderator-area">
<!-- BEGIN globalMods.members -->
<div class="user-card pull-left" data-uid="{globalMods.members.uid}">
<!-- IF globalMods.members.picture -->
<img class="avatar avatar-sm" src="{globalMods.members.picture}" />
<!-- ELSE -->
<div class="avatar avatar-sm" style="background-color: {globalMods.members.icon:bgColor};">{globalMods.members.icon:text}</div>
<!-- ENDIF globalMods.members.picture -->
<a href="{config.relative_path}/user/{globalMods.members.userslug}">{globalMods.members.username}</a>
<i class="remove-user-icon fa fa-times" role="button"></i>
</div>
<!-- END globalMods.members -->
</div>
<div id="no-global-mods-warning" class="<!-- IF globalMods.members.length -->hidden<!-- ENDIF globalMods.members.length -->">[[admin/manage/admins-mods:no-global-moderators]]</div>
<input id="global-mod-search" class="form-control" placeholder="[[admin/manage/admins-mods:add-global-moderator]]" />
<br/>
<!-- BEGIN categories -->
<div class="categories category-wrapper category-depth-{categories.depth}">
<h4><!-- IF categories.icon --><i class="fa {categories.icon}"></i> <!-- ENDIF categories.icon -->[[admin/manage/admins-mods:moderators-of-category, {categories.name}]]</h4>
<div class="moderator-area" data-cid="{categories.cid}">
<!-- BEGIN categories.moderators -->
<div class="user-card pull-left" data-uid="{categories.moderators.uid}">
<!-- IF categories.moderators.picture -->
<img class="avatar avatar-sm" src="{categories.moderators.picture}" />
<!-- ELSE -->
<div class="avatar avatar-sm" style="background-color: {categories.moderators.icon:bgColor};">{categories.moderators.icon:text}</div>
<!-- ENDIF categories.moderators.picture -->
<a href="{config.relative_path}/user/{categories.moderators.userslug}">{categories.moderators.username}</a>
<i class="remove-user-icon fa fa-times" role="button"></i>
</div>
<!-- END categories.moderators -->
</div>
<div data-cid="{categories.cid}" class="no-moderator-warning <!-- IF categories.moderators.length -->hidden<!-- ENDIF categories.moderators.length -->">[[admin/manage/admins-mods:no-moderators]]</div>
<input data-cid="{categories.cid}" class="form-control moderator-search" placeholder="[[admin/manage/admins-mods:add-moderator]]" />
</div>
<br/>
<!-- END categories -->
</div>

@ -2,15 +2,7 @@
<form role="form" class="category" data-cid="{category.cid}">
<div class="row">
<div class="col-md-9">
<ul class="nav nav-pills">
<li class="active"><a href="#category-settings" data-toggle="tab">
[[admin/manage/categories:settings]]
</a></li>
<li><a href="#privileges" data-toggle="tab">[[admin/manage/categories:privileges]]</a></li>
</ul>
</div>
<div class="col-md-3">
<div class="col-md-3 pull-right">
<select id="category-selector" class="form-control">
<!-- BEGIN allCategories -->
<option value="{allCategories.value}" <!-- IF allCategories.selected -->selected<!-- ENDIF allCategories.selected -->>{allCategories.text}</option>
@ -18,7 +10,7 @@
</select>
</div>
</div>
<br/>
<div class="tab-content">
@ -174,19 +166,6 @@
</div>
</div>
</div>
<div class="tab-pane fade col-xs-12" id="privileges">
<p>
[[admin/manage/categories:privileges.description]]
</p>
<p class="text-warning">
[[admin/manage/categories:privileges.warning]]
</p>
<hr />
<div class="privilege-table-container">
<!-- IMPORT admin/partials/categories/privileges.tpl -->
</div>
</div>
</div>
</form>
</div>

@ -0,0 +1,31 @@
<div class="row">
<form role="form" class="category">
<div class="row">
<div class="col-md-3 pull-right">
<select id="category-selector" class="form-control">
<option value="global" <!-- IF !cid --> selected <!-- ENDIF !cid -->>[[admin/manage/privileges:global]]</option>
<option disabled>_____________</option>
<!-- BEGIN allCategories -->
<option value="{allCategories.value}" <!-- IF allCategories.selected -->selected<!-- ENDIF allCategories.selected -->>{allCategories.text}</option>
<!-- END allCategories -->
</select>
</div>
</div>
<br/>
<div class="">
<p>
[[admin/manage/categories:privileges.description]]
</p>
<hr />
<div class="privilege-table-container">
<!-- IF cid -->
<!-- IMPORT admin/partials/categories/privileges.tpl -->
<!-- ELSE -->
<!-- IMPORT admin/partials/global/privileges.tpl -->
<!-- ENDIF cid -->
</div>
</div>
</form>
</div>

@ -8,9 +8,6 @@
<div class="btn-group pull-right">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[admin/manage/users:edit]] <span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a href="#" class="admin-user"><i class="fa fa-fw fa-shield"></i> [[admin/manage/users:make-admin]]</a></li>
<li><a href="#" class="remove-admin-user"><i class="fa fa-fw fa-ban"></i> [[admin/manage/users:remove-admin]]</a></li>
<li class="divider"></li>
<li><a href="#" class="validate-email"><i class="fa fa-fw fa-check"></i> [[admin/manage/users:validate-email]]</a></li>
<li><a href="#" class="send-validation-email"><i class="fa fa-fw fa-mail-forward"></i> [[admin/manage/users:send-validation-email]]</a></li>
<li><a href="#" class="password-reset-email"><i class="fa fa-fw fa-key"></i> [[admin/manage/users:password-reset-email]]</a></li>

@ -5,7 +5,7 @@
<th class="arrowed" colspan="3">
[[admin/manage/categories:privileges.section-viewing]]
</th>
<th class="arrowed" colspan="8">
<th class="arrowed" colspan="6">
[[admin/manage/categories:privileges.section-posting]]
</th>
<th class="arrowed" colspan="2">
@ -61,7 +61,7 @@
<th class="arrowed" colspan="3">
[[admin/manage/categories:privileges.section-viewing]]
</th>
<th class="arrowed" colspan="8">
<th class="arrowed" colspan="6">
[[admin/manage/categories:privileges.section-posting]]
</th>
<th class="arrowed" colspan="2">

@ -0,0 +1,86 @@
<table class="table table-striped privilege-table">
<thead>
<tr class="privilege-table-header">
<th colspan="3"></th>
</tr><tr><!-- zebrastripe reset --></tr>
<tr>
<th colspan="2">[[admin/manage/categories:privileges.section-user]]</th>
<!-- BEGIN privileges.labels.users -->
<th class="text-center">{privileges.labels.users.name}</th>
<!-- END privileges.labels.users -->
</tr>
</thead>
<tbody>
<!-- IF privileges.users.length -->
<!-- BEGIN privileges.users -->
<tr data-uid="{privileges.users.uid}">
<td>
<!-- IF ../picture -->
<img class="avatar avatar-sm" src="{privileges.users.picture}" title="{privileges.users.username}" />
<!-- ELSE -->
<div class="avatar avatar-sm" style="background-color: {../icon:bgColor};">{../icon:text}</div>
<!-- ENDIF ../picture -->
</td>
<td>{privileges.users.username}</td>
{function.spawnPrivilegeStates, privileges.users.username, ../privileges}
</tr>
<!-- END privileges.users -->
<tr>
<td colspan="{privileges.columnCount}">
<button type="button" class="btn btn-primary pull-right" data-ajaxify="false" data-action="search.user">
[[admin/manage/categories:privileges.search-user]]
</button>
</td>
</tr>
<!-- ELSE -->
<tr>
<td colspan="{privileges.columnCount}">
[[admin/manage/privileges:global.no-users]]
<button type="button" class="btn btn-primary pull-right" data-ajaxify="false" data-action="search.user">
[[admin/manage/categories:privileges.search-user]]
</button>
</td>
</tr>
<!-- ENDIF privileges.users.length -->
</tbody>
</table>
<table class="table table-striped privilege-table">
<thead>
<tr class="privilege-table-header">
<th colspan="3"></th>
</tr><tr><!-- zebrastripe reset --></tr>
<tr>
<th colspan="2">[[admin/manage/categories:privileges.section-group]]</th>
<!-- BEGIN privileges.labels.groups -->
<th class="text-center">{privileges.labels.groups.name}</th>
<!-- END privileges.labels.groups -->
</tr>
</thead>
<tbody>
<!-- BEGIN privileges.groups -->
<tr data-group-name="{privileges.groups.name}" data-private="<!-- IF privileges.groups.isPrivate -->1<!-- ELSE -->0<!-- ENDIF privileges.groups.isPrivate -->">
<td>
<!-- IF privileges.groups.isPrivate -->
<i class="fa fa-lock text-muted" title="[[admin/manage/categories:privileges.group-private]]"></i>
<!-- ENDIF privileges.groups.isPrivate -->
{privileges.groups.name}
</td>
<td></td>
{function.spawnPrivilegeStates, privileges.groups.name, ../privileges}
</tr>
<!-- END privileges.groups -->
<tr>
<td colspan="{privileges.columnCount}">
<div class="btn-toolbar">
<button type="button" class="btn btn-primary pull-right" data-ajaxify="false" data-action="search.group">
[[admin/manage/categories:privileges.search-group]]
</button>
</div>
</td>
</tr>
</tbody>
</table>
<div class="help-block">
[[admin/manage/categories:privileges.inherit]]
</div>

@ -15,7 +15,9 @@
<h3 class="menu-section-title">[[admin/menu:section-manage]]</h3>
<ul class="menu-section-list">
<li><a href="{relative_path}/admin/manage/categories">[[admin/menu:manage/categories]]</a></li>
<li><a href="{relative_path}/admin/manage/privileges">[[admin/menu:manage/privileges]]</a></li>
<li><a href="{relative_path}/admin/manage/users">[[admin/menu:manage/users]]</a></li>
<li><a href="{relative_path}/admin/manage/admins-mods">[[admin/menu:manage/admins-mods]]</a></li>
<li><a href="{relative_path}/admin/manage/groups">[[admin/menu:manage/groups]]</a></li>
<li><a href="{relative_path}/admin/manage/tags">[[admin/menu:manage/tags]]</a></li>
<li><a href="{relative_path}/admin/manage/registration">[[admin/menu:manage/registration]]</a></li>
@ -188,7 +190,9 @@
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">[[admin/menu:section-manage]]</a>
<ul class="dropdown-menu" role="menu">
<li><a href="{relative_path}/admin/manage/categories">[[admin/menu:manage/categories]]</a></li>
<li><a href="{relative_path}/admin/manage/privileges">[[admin/menu:manage/privileges]]</a></li>
<li><a href="{relative_path}/admin/manage/users">[[admin/menu:manage/users]]</a></li>
<li><a href="{relative_path}/admin/manage/admins-mods">[[admin/menu:manage/admins-mods]]</a></li>
<li><a href="{relative_path}/admin/manage/groups">[[admin/menu:manage/groups]]</a></li>
<li><a href="{relative_path}/admin/manage/tags">[[admin/menu:manage/tags]]</a></li>
<li><a href="{relative_path}/admin/manage/registration">[[admin/menu:manage/registration]]</a></li>

@ -638,7 +638,7 @@ describe('Categories', function () {
});
});
it('should load user privileges', function (done) {
it('should load category user privileges', function (done) {
privileges.categories.userPrivileges(categoryObj.cid, 1, function (err, data) {
assert.ifError(err);
assert.deepEqual(data, {
@ -651,8 +651,6 @@ describe('Categories', function () {
'topics:tag': false,
'topics:delete': false,
'posts:edit': false,
'upload:post:file': false,
'upload:post:image': false,
purge: false,
moderate: false,
});
@ -661,7 +659,20 @@ describe('Categories', function () {
});
});
it('should load group privileges', function (done) {
it('should load global user privileges', function (done) {
privileges.global.userPrivileges(1, function (err, data) {
assert.ifError(err);
assert.deepEqual(data, {
chat: false,
'upload:post:image': false,
'upload:post:file': false,
});
done();
});
});
it('should load category group privileges', function (done) {
privileges.categories.groupPrivileges(categoryObj.cid, 'registered-users', function (err, data) {
assert.ifError(err);
assert.deepEqual(data, {
@ -674,8 +685,6 @@ describe('Categories', function () {
'groups:posts:delete': true,
'groups:read': true,
'groups:topics:read': true,
'groups:upload:post:file': false,
'groups:upload:post:image': true,
'groups:purge': false,
'groups:moderate': false,
});
@ -684,6 +693,19 @@ describe('Categories', function () {
});
});
it('should load global group privileges', function (done) {
privileges.global.groupPrivileges('registered-users', function (err, data) {
assert.ifError(err);
assert.deepEqual(data, {
'groups:chat': true,
'groups:upload:post:image': true,
'groups:upload:post:file': false,
});
done();
});
});
it('should return false if cid is falsy', function (done) {
privileges.categories.isUserAllowedTo('find', null, adminUid, function (err, isAllowed) {
assert.ifError(err);

@ -255,6 +255,14 @@ describe('Admin Controllers', function () {
});
});
it('should load /admin/manage/admins-mods', function (done) {
request(nconf.get('url') + '/api/admin/manage/admins-mods', { jar: jar, json: true }, function (err, res, body) {
assert.ifError(err);
assert(body);
done();
});
});
it('should return 403 if no referer', function (done) {
request(nconf.get('url') + '/api/admin/users/csv', { jar: jar }, function (err, res, body) {
assert.ifError(err);

@ -149,6 +149,21 @@ describe('Key methods', function () {
done();
});
});
it('should set then increment a key', function (done) {
db.set('myIncrement', 1, function (err) {
assert.ifError(err);
db.increment('myIncrement', function (err, value) {
assert.ifError(err);
assert.equal(value, 2);
db.get('myIncrement', function (err, value) {
assert.ifError(err);
assert.equal(value, 2);
done();
});
});
});
});
});
describe('rename', function () {

@ -71,9 +71,9 @@ describe('Groups', function () {
describe('.list()', function () {
it('should list the groups present', function (done) {
Groups.getGroupsFromSet('groups:createtime', 0, 0, -1, function (err, groups) {
Groups.getGroupsFromSet('groups:visible:createtime', 0, 0, -1, function (err, groups) {
assert.ifError(err);
assert.equal(groups.length, 7);
assert.equal(groups.length, 4);
done();
});
});

@ -414,7 +414,7 @@ describe('Messaging Library', function () {
it('should fail to load room if user is not in', function (done) {
socketModules.chats.loadRoom({ uid: 0 }, { roomId: roomId }, function (err) {
assert.equal(err.message, '[[error:not-allowed]]');
assert.equal(err.message, '[[error:no-privileges]]');
done();
});
});
@ -629,11 +629,12 @@ describe('Messaging Library', function () {
});
});
it('should 404 for guest', function (done) {
it('should 500 for guest with no privilege error', function (done) {
meta.config.disableChat = 0;
request(nconf.get('url') + '/user/baz/chats', function (err, response) {
request(nconf.get('url') + '/api/user/baz/chats', { json: true }, function (err, response, body) {
assert.ifError(err);
assert.equal(response.statusCode, 404);
assert.equal(response.statusCode, 500);
assert.equal(body.error, '[[error:no-privileges]]');
done();
});
});

@ -154,6 +154,9 @@ function setupMockDefaults(callback) {
winston.info('test_database flushed');
setupDefaultConfigs(meta, next);
},
function (next) {
giveDefaultGlobalPrivileges(next);
},
function (next) {
meta.configs.init(next);
},
@ -182,6 +185,11 @@ function setupDefaultConfigs(meta, next) {
meta.configs.setOnEmpty(defaults, next);
}
function giveDefaultGlobalPrivileges(next) {
var privileges = require('../../src/privileges');
privileges.global.give(['chat', 'upload:post:image'], 'registered-users', next);
}
function enableDefaultPlugins(callback) {
winston.info('Enabling default plugins\n');

@ -62,7 +62,7 @@ describe('Upload Controllers', function () {
assert.ifError(err);
jar = _jar;
csrf_token = _csrf_token;
privileges.categories.give(['upload:post:file'], cid, 'registered-users', done);
privileges.global.give(['upload:post:file'], 'registered-users', done);
});
});
@ -77,17 +77,8 @@ describe('Upload Controllers', function () {
});
});
it('should fail to upload an image to a post with invalid cid', function (done) {
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/test.png'), { cid: '0' }, jar, csrf_token, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 500);
assert.equal(body.error, '[[error:category-not-selected]]');
done();
});
});
it('should upload an image to a post', function (done) {
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/test.png'), { cid: cid }, jar, csrf_token, function (err, res, body) {
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(Array.isArray(body));
@ -100,7 +91,7 @@ describe('Upload Controllers', function () {
it('should resize and upload an image to a post', function (done) {
var oldValue = meta.config.maximumImageWidth;
meta.config.maximumImageWidth = 10;
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/test.png'), { cid: cid }, jar, csrf_token, function (err, res, body) {
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(Array.isArray(body));
@ -116,7 +107,7 @@ describe('Upload Controllers', function () {
meta.config.allowFileUploads = 1;
var oldValue = meta.config.allowedFileExtensions;
meta.config.allowedFileExtensions = 'png,jpg,bmp,html';
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/503.html'), { cid: cid }, jar, csrf_token, function (err, res, body) {
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../test/files/503.html'), {}, jar, csrf_token, function (err, res, body) {
meta.config.allowedFileExtensions = oldValue;
assert.ifError(err);
assert.equal(res.statusCode, 200);

Loading…
Cancel
Save