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

v1.18.x
Julian Lam 8 years ago
commit 62e0f5f883

@ -1,23 +1,23 @@
"use strict"; "use strict";
var fork = require('child_process').fork, var fork = require('child_process').fork;
env = process.env, var env = process.env;
worker, updateWorker, var worker, updateWorker, initWorker;
incomplete = [], var incomplete = [];
running = 0; var running = 0;
module.exports = function (grunt) { module.exports = function (grunt) {
var args = []; var args = [];
var initArgs = ['--build'];
if (!grunt.option('verbose')) { if (!grunt.option('verbose')) {
args.push('--log-level=info'); args.push('--log-level=info');
initArgs.push('--log-level=info');
} }
function update(action, filepath, target) { function update(action, filepath, target) {
var updateArgs = args.slice(), var updateArgs = args.slice();
fromFile = '', var compiling = '';
compiling = '', var time = Date.now();
time = Date.now();
if (target === 'lessUpdated_Client') { if (target === 'lessUpdated_Client') {
compiling = 'clientCSS'; compiling = 'clientCSS';
@ -44,12 +44,16 @@ module.exports = function (grunt) {
if (updateWorker) { if (updateWorker) {
updateWorker.kill('SIGKILL'); updateWorker.kill('SIGKILL');
} }
updateWorker = fork('app.js', updateArgs, { env: env }); updateWorker = fork('app.js', updateArgs, {
env: env
});
++running; ++running;
updateWorker.on('exit', function () { updateWorker.on('exit', function () {
--running; --running;
if (running === 0) { if (running === 0) {
worker = fork('app.js', args, { env: env }); worker = fork('app.js', args, {
env: env
});
worker.on('message', function () { worker.on('message', function () {
if (incomplete.length) { if (incomplete.length) {
incomplete = []; incomplete = [];
@ -131,15 +135,24 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-contrib-watch');
if (grunt.option('skip')) {
grunt.registerTask('default', ['watch:serverUpdated']);
} else {
grunt.registerTask('default', ['watch']); grunt.registerTask('default', ['watch']);
} env.NODE_ENV = 'development';
if (grunt.option('skip')) {
worker = fork('app.js', args, {
env: env
});
} else {
initWorker = fork('app.js', initArgs, {
env: env
});
env.NODE_ENV = 'development'; initWorker.on('exit', function () {
worker = fork('app.js', args, {
env: env
});
});
}
worker = fork('app.js', args, { env: env });
grunt.event.on('watch', update); grunt.event.on('watch', update);
}; };

@ -1,9 +1,9 @@
{ {
"loading": "Načítání motivů…", "loading": "Načítání motivů…",
"homepage": "Homepage", "homepage": "Domovská stránka",
"select-skin": "Select Skin", "select-skin": "Vyber motiv",
"current-skin": "Current Skin", "current-skin": "Současný motiv",
"skin-updated": "Skin Updated", "skin-updated": "Motiv aktualizován",
"applied-success": "%1 skin was succesfully applied", "applied-success": "%1 skin was succesfully applied",
"revert-success": "Skin reverted to base colours" "revert-success": "Skin reverted to base colours"
} }

@ -1,21 +1,21 @@
{ {
"authentication": "Authentication", "authentication": "Ověření",
"allow-local-login": "Allow local login", "allow-local-login": "Povolit místní přihlášení",
"require-email-confirmation": "Require Email Confirmation", "require-email-confirmation": "Vyžadovat potvrzení e-mailem",
"email-confirm-interval": "User may not resend a confirmation email until", "email-confirm-interval": "User may not resend a confirmation email until",
"email-confirm-email2": "minutes have elapsed", "email-confirm-email2": "minut uplynulo",
"allow-login-with": "Allow login with", "allow-login-with": "Allow login with",
"allow-login-with.username-email": "Username or Email", "allow-login-with.username-email": "Uživatelské jméno nebo e-mail",
"allow-login-with.username": "Username Only", "allow-login-with.username": "Pouze uživatelské jméno",
"allow-login-with.email": "Email Only", "allow-login-with.email": "Pouze e-mail",
"account-settings": "Account Settings", "account-settings": "Nastavení účtu",
"disable-username-changes": "Disable username changes", "disable-username-changes": "Zakázat změnu uživatelského jména",
"disable-email-changes": "Disable email changes", "disable-email-changes": "Zakázat změnu e-mailu",
"disable-password-changes": "Disable password changes", "disable-password-changes": "Zakázat změnu hesla",
"allow-account-deletion": "Allow account deletion", "allow-account-deletion": "Povolit smazání účtu",
"user-info-private": "Make user info private", "user-info-private": "Make user info private",
"themes": "Themes", "themes": "Témata",
"disable-user-skins": "Prevent users from choosing a custom skin", "disable-user-skins": "Zabránit uživateli ve výběru vlastního vzhledu",
"account-protection": "Account Protection", "account-protection": "Account Protection",
"login-attempts": "Login attempts per hour", "login-attempts": "Login attempts per hour",
"login-attempts-help": "If login attempts to a user's account exceeds this threshold, that account will be locked for a pre-configured amount of time", "login-attempts-help": "If login attempts to a user's account exceeds this threshold, that account will be locked for a pre-configured amount of time",
@ -34,10 +34,10 @@
"registration.max-invites": "Maximum Invitations per User", "registration.max-invites": "Maximum Invitations per User",
"max-invites": "Maximum Invitations per User", "max-invites": "Maximum Invitations per User",
"max-invites-help": "0 for no restriction. Admins get infinite invitations<br>Only applicable for \"Invite Only\"", "max-invites-help": "0 for no restriction. Admins get infinite invitations<br>Only applicable for \"Invite Only\"",
"min-username-length": "Minimum Username Length", "min-username-length": "Minimální délka uživatelského jména",
"max-username-length": "Maximum Username Length", "max-username-length": "Maximální délka uživatelského jména",
"min-password-length": "Minimum Password Length", "min-password-length": "Minimální délka hesla",
"max-about-me-length": "Maximum About Me Length", "max-about-me-length": "Maximální délka hesla",
"terms-of-use": "Forum Terms of Use <small>(Leave blank to disable)</small>", "terms-of-use": "Forum Terms of Use <small>(Leave blank to disable)</small>",
"user-search": "User Search", "user-search": "User Search",
"user-search-results-per-page": "Number of results to display", "user-search-results-per-page": "Number of results to display",
@ -48,10 +48,10 @@
"outgoing-new-tab": "Open outgoing links in new tab", "outgoing-new-tab": "Open outgoing links in new tab",
"topic-search": "Enable In-Topic Searching", "topic-search": "Enable In-Topic Searching",
"digest-freq": "Subscribe to Digest", "digest-freq": "Subscribe to Digest",
"digest-freq.off": "Off", "digest-freq.off": "Vypnuto",
"digest-freq.daily": "Daily", "digest-freq.daily": "Denně",
"digest-freq.weekly": "Weekly", "digest-freq.weekly": "Týdně",
"digest-freq.monthly": "Monthly", "digest-freq.monthly": "Měsíčně",
"email-chat-notifs": "Send an email if a new chat message arrives and I am not online", "email-chat-notifs": "Send an email if a new chat message arrives and I am not online",
"email-post-notif": "Send an email when replies are made to topics I am subscribed to", "email-post-notif": "Send an email when replies are made to topics I am subscribed to",
"follow-created-topics": "Follow topics you create", "follow-created-topics": "Follow topics you create",

@ -8,5 +8,5 @@
"failed_login_attempt": "Přihlášení neúspěšné", "failed_login_attempt": "Přihlášení neúspěšné",
"login_successful": "Přihlášení proběhlo úspěšně!", "login_successful": "Přihlášení proběhlo úspěšně!",
"dont_have_account": "Nemáte účet?", "dont_have_account": "Nemáte účet?",
"logged-out-due-to-inactivity": "You have been logged out of the Admin Control Panel due to inactivity" "logged-out-due-to-inactivity": "Z důvodu nečinnosti jste byl odhlášen z ovládacího panelu administrátora"
} }

@ -13,7 +13,7 @@
"chat.contacts": "Kontakty", "chat.contacts": "Kontakty",
"chat.message-history": "Historie zpráv", "chat.message-history": "Historie zpráv",
"chat.pop-out": "Skrýt chat", "chat.pop-out": "Skrýt chat",
"chat.minimize": "Minimize", "chat.minimize": "Minimalizovat",
"chat.maximize": "Maximalizovat", "chat.maximize": "Maximalizovat",
"chat.seven_days": "7 dní", "chat.seven_days": "7 dní",
"chat.thirty_days": "30 dní", "chat.thirty_days": "30 dní",

@ -1,6 +1,6 @@
{ {
"register": "Registrace", "register": "Registrace",
"cancel_registration": "Cancel Registration", "cancel_registration": "Zrušit registraci",
"help.email": "Ve výchozím nastavení bude váš e-mail skrytý.", "help.email": "Ve výchozím nastavení bude váš e-mail skrytý.",
"help.username_restrictions": "Jedinečné uživatelské jméno dlouhé %1 až %2 znaků. Ostatní uživatelé Vás mohou zmínit jako @<span id='yourUsername'>uživatelské-jméno</span>.", "help.username_restrictions": "Jedinečné uživatelské jméno dlouhé %1 až %2 znaků. Ostatní uživatelé Vás mohou zmínit jako @<span id='yourUsername'>uživatelské-jméno</span>.",
"help.minimum_password_length": "Délka vašeho hesla musí být alespoň %1 znaků.", "help.minimum_password_length": "Délka vašeho hesla musí být alespoň %1 znaků.",

@ -94,7 +94,7 @@
"topics_per_page": "Témat na stránce", "topics_per_page": "Témat na stránce",
"posts_per_page": "Příspěvků na stránce", "posts_per_page": "Příspěvků na stránce",
"notification_sounds": "Přehrát zvuk když dostanete notifikaci", "notification_sounds": "Přehrát zvuk když dostanete notifikaci",
"notifications_and_sounds": "Notifications & Sounds", "notifications_and_sounds": "Upozornění a zvuky",
"incoming-message-sound": "Incoming message sound", "incoming-message-sound": "Incoming message sound",
"outgoing-message-sound": "Outgoing message sound", "outgoing-message-sound": "Outgoing message sound",
"notification-sound": "Notification sound", "notification-sound": "Notification sound",

@ -107,10 +107,10 @@
"more_guests": "ゲストさんが%1人", "more_guests": "ゲストさんが%1人",
"users_and_others": "%1と他は%2", "users_and_others": "%1と他は%2",
"sort_by": "並び替え", "sort_by": "並び替え",
"oldest_to_newest": "新しい順", "oldest_to_newest": "古い\bものから新しい順",
"newest_to_oldest": "古い順", "newest_to_oldest": "新しいものから古い順",
"most_votes": "最高評価", "most_votes": "最も投票された順",
"most_posts": "最高投稿", "most_posts": "最も投稿された順",
"stale.title": "新しいスレッドを作りますか?", "stale.title": "新しいスレッドを作りますか?",
"stale.warning": "あなたが返信しようとしてるスレッドが古いスレッドです。新しいスレッドを作って、そしてこのスレッドが参考として入れた方を勧めます。そうしますか?", "stale.warning": "あなたが返信しようとしてるスレッドが古いスレッドです。新しいスレッドを作って、そしてこのスレッドが参考として入れた方を勧めます。そうしますか?",
"stale.create": "新しいスレッドを作ります。", "stale.create": "新しいスレッドを作ります。",

@ -1,14 +1,14 @@
{ {
"figure-x": "Znázorniť %1", "figure-x": "Znázorniť %1",
"error-events-per-day": "<code>%1</code> events per day", "error-events-per-day": "<code>%1</code> events per day",
"error.404": "404 Not Found", "error.404": "404 Nenájdené",
"error.503": "503 Service Unavailable", "error.503": "503 Služba nie je k dispozícií",
"manage-error-log": "Manage Error Log", "manage-error-log": "Manage Error Log",
"export-error-log": "Export Error Log (CSV)", "export-error-log": "Export Error Log (CSV)",
"clear-error-log": "Clear Error Log", "clear-error-log": "Clear Error Log",
"route": "Route", "route": "Route",
"count": "Count", "count": "Count",
"no-routes-not-found": "Hooray! No 404 errors!", "no-routes-not-found": "Hurá! Žiadne chyby 404!",
"clear404-confirm": "Are you sure you wish to clear the 404 error logs?", "clear404-confirm": "Are you sure you wish to clear the 404 error logs?",
"clear404-success": "\"404 Not Found\" errors cleared" "clear404-success": "Chybné hlásenia \"404 Nenájdené\" vyčistené"
} }

@ -1,6 +1,6 @@
{ {
"events": "Udalosti", "events": "Udalosti",
"no-events": "There are no events", "no-events": "Zatiaľ neexistujô žiadne udalosti",
"control-panel": "Events Control Panel", "control-panel": "Ovládací panel udalostí",
"delete-events": "Delete Events" "delete-events": "Odstrániť udalosť"
} }

@ -1,7 +1,7 @@
{ {
"logs": "Protokoly", "logs": "Záznamy",
"control-panel": "Logs Control Panel", "control-panel": "Ovládací panel záznamov",
"reload": "Reload Logs", "reload": "Znovu načítať záznamy",
"clear": "Clear Logs", "clear": "Vyčistiť záznamy",
"clear-success": "Logs Cleared!" "clear-success": "Záznamy vyčistené!"
} }

@ -1,9 +1,9 @@
{ {
"loading": "Loading Skins...", "loading": "Načítať vzhľady...",
"homepage": "Homepage", "homepage": "Domovska stránka",
"select-skin": "Select Skin", "select-skin": "Vybrať vzhľad",
"current-skin": "Current Skin", "current-skin": "Aktuálny vzhľad",
"skin-updated": "Skin Updated", "skin-updated": "Vzhľad aktualizovaný",
"applied-success": "%1 skin was succesfully applied", "applied-success": "%1 vzhľad bol úspešne aplikovaný",
"revert-success": "Skin reverted to base colours" "revert-success": "Vzhľad bol obnovený do základných farieb"
} }

@ -1,11 +1,11 @@
{ {
"checking-for-installed": "Checking for installed themes...", "checking-for-installed": "Kontrola nainštalovaných motívov...",
"homepage": "Homepage", "homepage": "Domovská stránka",
"select-theme": "Select Theme", "select-theme": "Vybrať motív",
"current-theme": "Current Theme", "current-theme": "Aktuálny motív",
"no-themes": "No installed themes found", "no-themes": "Žiadne nainštalované motívy neboli nájdené",
"revert-confirm": "Are you sure you wish to restore the default NodeBB theme?", "revert-confirm": "Ste si istý, že chcete obnoviť predvolený NodeBB motív?",
"theme-changed": "Theme Changed", "theme-changed": "Motív zmenený",
"revert-success": "You have successfully reverted your NodeBB back to it's default theme.", "revert-success": "Úspešne sa Vám podarilo obnoviť Váš NodeBB do predvoleného motívu.",
"restart-to-activate": "Please restart your NodeBB to fully activate this theme" "restart-to-activate": "Prosím, reštartujte Váš NodeBB pre úplne aktivovanie tohto motívu."
} }

@ -1,20 +1,20 @@
{ {
"forum-traffic": "Forum Traffic", "forum-traffic": "Prevádzka fóra",
"page-views": "Page Views", "page-views": "Zobrazenia stránok",
"unique-visitors": "Unique Visitors", "unique-visitors": "Unikátne návštevy",
"users": "Users", "users": "Užívatelia",
"posts": "Posts", "posts": "Príspevky",
"topics": "Topics", "topics": "Témy",
"page-views-last-month": "Page views Last Month", "page-views-last-month": "Zobrazenia stránok za posledný mesiac",
"page-views-this-month": "Page views This Month", "page-views-this-month": "Zobrazenia stránok za tento mesiac",
"page-views-last-day": "Page views in last 24 hours", "page-views-last-day": "Zobrazenia stránok za posledných 24 hodín",
"stats.day": "Day", "stats.day": "D",
"stats.week": "Week", "stats.week": "Týždeň",
"stats.month": "Month", "stats.month": "Mesiac",
"stats.all": "All Time", "stats.all": "Celé obdobie",
"updates": "Updates", "updates": "Aktualizácie",
"running-version": "You are running <strong>NodeBB v<span id=\"version\">%1</span></strong>.", "running-version": "You are running <strong>NodeBB v<span id=\"version\">%1</span></strong>.",
"keep-updated": "Always make sure that your NodeBB is up to date for the latest security patches and bug fixes.", "keep-updated": "Always make sure that your NodeBB is up to date for the latest security patches and bug fixes.",
"up-to-date": "<p>You are <strong>up-to-date</strong> <i class=\"fa fa-check\"></i></p>", "up-to-date": "<p>You are <strong>up-to-date</strong> <i class=\"fa fa-check\"></i></p>",
@ -22,42 +22,42 @@
"prerelease-upgrade-available": "<p>This is an outdated pre-release version of NodeBB. A new version (v%1) has been released. Consider <a href=\"https://docs.nodebb.org/en/latest/upgrading/index.html\">upgrading your NodeBB</a>.</p>", "prerelease-upgrade-available": "<p>This is an outdated pre-release version of NodeBB. A new version (v%1) has been released. Consider <a href=\"https://docs.nodebb.org/en/latest/upgrading/index.html\">upgrading your NodeBB</a>.</p>",
"prerelease-warning": "<p>This is a <strong>pre-release</strong> version of NodeBB. Unintended bugs may occur. <i class=\"fa fa-exclamation-triangle\"></i></p>", "prerelease-warning": "<p>This is a <strong>pre-release</strong> version of NodeBB. Unintended bugs may occur. <i class=\"fa fa-exclamation-triangle\"></i></p>",
"notices": "Notices", "notices": "Upozornenie",
"restart-not-required": "Restart not required", "restart-not-required": "Reštart nie je potrebný",
"restart-required": "Restart required", "restart-required": "Reštart je potrebný",
"search-plugin-installed": "Search Plugin installed", "search-plugin-installed": "Vyhľadávací doplnok bol nainštalovaný",
"search-plugin-not-installed": "Search Plugin not installed", "search-plugin-not-installed": "Vyhľadávací doplnok nebol nainštalovaný",
"search-plugin-tooltip": "Install a search plugin from the plugin page in order to activate search functionality", "search-plugin-tooltip": "Install a search plugin from the plugin page in order to activate search functionality",
"control-panel": "System Control", "control-panel": "System Control",
"reload": "Reload", "reload": "Obnoviť",
"restart": "Restart", "restart": "Reštartovať",
"restart-warning": "Reloading or Restarting your NodeBB will drop all existing connections for a few seconds.", "restart-warning": "Reloading or Restarting your NodeBB will drop all existing connections for a few seconds.",
"maintenance-mode": "Maintenance Mode", "maintenance-mode": "Maintenance Mode",
"maintenance-mode-title": "Click here to set up maintenance mode for NodeBB", "maintenance-mode-title": "Click here to set up maintenance mode for NodeBB",
"realtime-chart-updates": "Realtime Chart Updates", "realtime-chart-updates": "Realtime Chart Updates",
"active-users": "Active Users", "active-users": "Aktívny užívatelia",
"active-users.users": "Users", "active-users.users": "Užívatelia",
"active-users.guests": "Guests", "active-users.guests": "Hostia",
"active-users.total": "Total", "active-users.total": "Celkovo",
"active-users.connections": "Connections", "active-users.connections": "Pripojení",
"anonymous-registered-users": "Anonymous vs Registered Users", "anonymous-registered-users": "Neznámy vs Zaregistrovaný užívatelia",
"anonymous": "Anonymous", "anonymous": "Neznámy",
"registered": "Registered", "registered": "Zaregistrovaný",
"user-presence": "User Presence", "user-presence": "User Presence",
"on-categories": "On categories list", "on-categories": "On categories list",
"reading-posts": "Reading posts", "reading-posts": "Reading posts",
"browsing-topics": "Browsing topics", "browsing-topics": "Browsing topics",
"recent": "Recent", "recent": "Nedávne",
"unread": "Unread", "unread": "Neprečitané",
"high-presence-topics": "High Presence Topics", "high-presence-topics": "High Presence Topics",
"graphs.page-views": "Page Views", "graphs.page-views": "Zobrazenia stránok",
"graphs.unique-visitors": "Unique Visitors", "graphs.unique-visitors": "Unikátny navštevníci",
"graphs.registered-users": "Registered Users", "graphs.registered-users": "Zarestrovaný užívatelia",
"graphs.anonymous-users": "Anonymous Users" "graphs.anonymous-users": "Neznámy užívatelia"
} }

@ -6,7 +6,7 @@
"description": "Select tags via clicking and/or dragging, use shift to select multiple.", "description": "Select tags via clicking and/or dragging, use shift to select multiple.",
"create": "Create Tag", "create": "Create Tag",
"modify": "Modify Tags", "modify": "Modify Tags",
"delete": "Delete Selected Tags", "delete": "Odstrániť vybraté značky",
"search": "Search for tags...", "search": "Search for tags...",
"settings": "Click <a href=\"%1\">here</a> to visit the tag settings page.", "settings": "Click <a href=\"%1\">here</a> to visit the tag settings page.",
"name": "Tag Name", "name": "Tag Name",

@ -1,7 +1,7 @@
{ {
"posts": "İletiler", "posts": "İletiler",
"allow-files": "Allow users to upload regular files", "allow-files": "Allow users to upload regular files",
"private": "Make uploaded files private", "private": "Yüklenen dosyaları gizli yap",
"max-image-width": "Resize images down to specified width (in pixels)", "max-image-width": "Resize images down to specified width (in pixels)",
"max-image-width-help": "(in pixels, default: 760 pixels, set to 0 to disable)", "max-image-width-help": "(in pixels, default: 760 pixels, set to 0 to disable)",
"max-file-size": "Maksimum Dosya Boyutu (KiB)", "max-file-size": "Maksimum Dosya Boyutu (KiB)",
@ -19,10 +19,10 @@
"profile-image-dimension-help": "(in pixels, default: 128 pixels)", "profile-image-dimension-help": "(in pixels, default: 128 pixels)",
"max-profile-image-size": "Maximum Profile Image File Size", "max-profile-image-size": "Maximum Profile Image File Size",
"max-profile-image-size-help": "(in kilobytes, default: 256 KiB)", "max-profile-image-size-help": "(in kilobytes, default: 256 KiB)",
"max-cover-image-size": "Maximum Cover Image File Size", "max-cover-image-size": "Maksimum Kapak Görseli Dosya Boyutu",
"max-cover-image-size-help": "(in kilobytes, default: 2,048 KiB)", "max-cover-image-size-help": "(in kilobytes, default: 2,048 KiB)",
"keep-all-user-images": "Keep old versions of avatars and profile covers on the server", "keep-all-user-images": "Keep old versions of avatars and profile covers on the server",
"profile-covers": "Profile Covers", "profile-covers": "Profil Kapakları",
"default-covers": "Default Cover Images", "default-covers": "Varsayılan Kapak Görseli",
"default-covers-help": "Add comma-separated default cover images for accounts that don't have an uploaded cover image" "default-covers-help": "Add comma-separated default cover images for accounts that don't have an uploaded cover image"
} }

@ -33,7 +33,9 @@ define('pictureCropper', ['translator', 'cropper'], function (translator, croppe
module.handleImageCrop = function (data, callback) { module.handleImageCrop = function (data, callback) {
$('#crop-picture-modal').remove(); $('#crop-picture-modal').remove();
templates.parse('modals/crop_picture', {url: data.url}, function (cropperHtml) { templates.parse('modals/crop_picture', {
url: data.url
}, function (cropperHtml) {
translator.translate(cropperHtml, function (translated) { translator.translate(cropperHtml, function (translated) {
var cropperModal = $(translated); var cropperModal = $(translated);
cropperModal.modal('show'); cropperModal.modal('show');

@ -59,7 +59,10 @@ function uploadAsImage(req, uploadedFile, callback) {
return next(new Error('[[error:no-privileges]]')); return next(new Error('[[error:no-privileges]]'));
} }
if (plugins.hasListeners('filter:uploadImage')) { if (plugins.hasListeners('filter:uploadImage')) {
return plugins.fireHook('filter:uploadImage', {image: uploadedFile, uid: req.uid}, callback); return plugins.fireHook('filter:uploadImage', {
image: uploadedFile,
uid: req.uid
}, callback);
} }
file.isFileTypeAllowed(uploadedFile.path, next); file.isFileTypeAllowed(uploadedFile.path, next);
}, },
@ -156,7 +159,10 @@ uploadsController.uploadThumb = function (req, res, next) {
} }
if (plugins.hasListeners('filter:uploadImage')) { if (plugins.hasListeners('filter:uploadImage')) {
return plugins.fireHook('filter:uploadImage', {image: uploadedFile, uid: req.uid}, next); return plugins.fireHook('filter:uploadImage', {
image: uploadedFile,
uid: req.uid
}, next);
} }
uploadFile(req.uid, uploadedFile, next); uploadFile(req.uid, uploadedFile, next);
@ -167,11 +173,17 @@ uploadsController.uploadThumb = function (req, res, next) {
uploadsController.uploadGroupCover = function (uid, uploadedFile, callback) { uploadsController.uploadGroupCover = function (uid, uploadedFile, callback) {
if (plugins.hasListeners('filter:uploadImage')) { if (plugins.hasListeners('filter:uploadImage')) {
return plugins.fireHook('filter:uploadImage', {image: uploadedFile, uid: uid}, callback); return plugins.fireHook('filter:uploadImage', {
image: uploadedFile,
uid: uid
}, callback);
} }
if (plugins.hasListeners('filter:uploadFile')) { if (plugins.hasListeners('filter:uploadFile')) {
return plugins.fireHook('filter:uploadFile', {file: uploadedFile, uid: uid}, callback); return plugins.fireHook('filter:uploadFile', {
file: uploadedFile,
uid: uid
}, callback);
} }
file.isFileTypeAllowed(uploadedFile.path, function (err) { file.isFileTypeAllowed(uploadedFile.path, function (err) {
@ -184,7 +196,10 @@ uploadsController.uploadGroupCover = function (uid, uploadedFile, callback) {
function uploadFile(uid, uploadedFile, callback) { function uploadFile(uid, uploadedFile, callback) {
if (plugins.hasListeners('filter:uploadFile')) { if (plugins.hasListeners('filter:uploadFile')) {
return plugins.fireHook('filter:uploadFile', {file: uploadedFile, uid: uid}, callback); return plugins.fireHook('filter:uploadFile', {
file: uploadedFile,
uid: uid
}, callback);
} }
if (!uploadedFile) { if (!uploadedFile) {
@ -197,7 +212,7 @@ function uploadFile(uid, uploadedFile, callback) {
if (meta.config.hasOwnProperty('allowedFileExtensions')) { if (meta.config.hasOwnProperty('allowedFileExtensions')) {
var allowed = file.allowedExtensions(); var allowed = file.allowedExtensions();
var extension = typeToExtension(uploadedFile.type); var extension = file.typeToExtension(uploadedFile.type);
if (!extension || (allowed.length > 0 && allowed.indexOf(extension) === -1)) { if (!extension || (allowed.length > 0 && allowed.indexOf(extension) === -1)) {
return callback(new Error('[[error:invalid-file-type, ' + allowed.join('&#44; ') + ']]')); return callback(new Error('[[error:invalid-file-type, ' + allowed.join('&#44; ') + ']]'));
} }
@ -207,7 +222,7 @@ function uploadFile(uid, uploadedFile, callback) {
} }
function saveFileToLocal(uploadedFile, callback) { function saveFileToLocal(uploadedFile, callback) {
var extension = typeToExtension(uploadedFile.type); var extension = file.typeToExtension(uploadedFile.type);
if (!extension) { if (!extension) {
return callback(new Error('[[error:invalid-extension]]')); return callback(new Error('[[error:invalid-extension]]'));
} }
@ -228,14 +243,6 @@ function saveFileToLocal(uploadedFile, callback) {
}); });
} }
function typeToExtension(type) {
var extension;
if (type) {
extension = '.' + mime.extension(type);
}
return extension;
}
function deleteTempFiles(files) { function deleteTempFiles(files) {
async.each(files, function (file, next) { async.each(files, function (file, next) {
fs.unlink(file.path, function (err) { fs.unlink(file.path, function (err) {
@ -247,6 +254,4 @@ function deleteTempFiles(files) {
}); });
} }
module.exports = uploadsController; module.exports = uploadsController;

@ -6,6 +6,7 @@ var path = require('path');
var winston = require('winston'); var winston = require('winston');
var jimp = require('jimp'); var jimp = require('jimp');
var mkdirp = require('mkdirp'); var mkdirp = require('mkdirp');
var mime = require('mime');
var utils = require('../public/src/utils'); var utils = require('../public/src/utils');
@ -118,7 +119,8 @@ file.existsSync = function (path) {
file.link = function link(filePath, destPath, cb) { file.link = function link(filePath, destPath, cb) {
if (process.platform === 'win32') { if (process.platform === 'win32') {
fs.link(filePath, destPath, cb); fs.link(filePath, destPath, cb);
} else { }
else {
fs.symlink(filePath, destPath, 'file', cb); fs.symlink(filePath, destPath, 'file', cb);
} }
}; };
@ -128,4 +130,12 @@ file.linkDirs = function linkDirs(sourceDir, destDir, callback) {
fs.symlink(sourceDir, destDir, type, callback); fs.symlink(sourceDir, destDir, type, callback);
}; };
file.typeToExtension = function (type) {
var extension;
if (type) {
extension = '.' + mime.extension(type);
}
return extension;
};
module.exports = file; module.exports = file;

@ -10,6 +10,7 @@ var mime = require('mime');
var winston = require('winston'); var winston = require('winston');
var db = require('../database'); var db = require('../database');
var image = require('../image');
var uploadsController = require('../controllers/uploads'); var uploadsController = require('../controllers/uploads');
module.exports = function (Groups) { module.exports = function (Groups) {
@ -37,7 +38,7 @@ module.exports = function (Groups) {
if (tempPath) { if (tempPath) {
return next(null, tempPath); return next(null, tempPath);
} }
writeImageDataToFile(data.imageData, next); image.writeImageDataToTempFile(data.imageData, next);
}, },
function (_tempPath, next) { function (_tempPath, next) {
tempPath = _tempPath; tempPath = _tempPath;
@ -97,24 +98,6 @@ module.exports = function (Groups) {
}); });
} }
function writeImageDataToFile(imageData, callback) {
// Calculate md5sum of image
// This is required because user data can be private
var md5sum = crypto.createHash('md5');
md5sum.update(imageData);
md5sum = md5sum.digest('hex');
// Save image
var tempPath = path.join(nconf.get('upload_path'), md5sum + '.png');
var buffer = new Buffer(imageData.slice(imageData.indexOf('base64') + 7), 'base64');
fs.writeFile(tempPath, buffer, {
encoding: 'base64'
}, function (err) {
callback(err, tempPath);
});
}
Groups.removeCover = function (data, callback) { Groups.removeCover = function (data, callback) {
db.deleteObjectFields('group:' + data.groupName, ['cover:url', 'cover:thumb:url', 'cover:position'], callback); db.deleteObjectFields('group:' + data.groupName, ['cover:url', 'cover:thumb:url', 'cover:position'], callback);
}; };

@ -1,8 +1,13 @@
'use strict'; 'use strict';
var os = require('os');
var fs = require('fs'); var fs = require('fs');
var path = require('path');
var Jimp = require('jimp'); var Jimp = require('jimp');
var async = require('async'); var async = require('async');
var crypto = require('crypto');
var file = require('./file');
var plugins = require('./plugins'); var plugins = require('./plugins');
var image = module.exports; var image = module.exports;
@ -65,9 +70,6 @@ image.resizeImage = function (data, callback) {
} }
}, },
function (image, next) { function (image, next) {
if (data.write === false) {
return next();
}
image.write(data.target || data.path, next); image.write(data.target || data.path, next);
} }
], function (err) { ], function (err) {
@ -83,7 +85,7 @@ image.normalise = function (path, extension, callback) {
path: path, path: path,
extension: extension extension: extension
}, function (err) { }, function (err) {
callback(err); callback(err, path + '.png');
}); });
} else { } else {
new Jimp(path, function (err, image) { new Jimp(path, function (err, image) {
@ -91,7 +93,7 @@ image.normalise = function (path, extension, callback) {
return callback(err); return callback(err);
} }
image.write(path + '.png', function (err) { image.write(path + '.png', function (err) {
callback(err); callback(err, path + '.png');
}); });
}); });
} }
@ -116,3 +118,28 @@ image.convertImageToBase64 = function (path, callback) {
callback(err, data ? data.toString('base64') : null); callback(err, data ? data.toString('base64') : null);
}); });
}; };
image.mimeFromBase64 = function (imageData) {
return imageData.slice(5, imageData.indexOf('base64') - 1);
};
image.extensionFromBase64 = function (imageData) {
return file.typeToExtension(image.mimeFromBase64(imageData));
};
image.writeImageDataToTempFile = function (imageData, callback) {
var filename = crypto.createHash('md5').update(imageData).digest('hex');
var type = image.mimeFromBase64(imageData);
var extension = file.typeToExtension(type);
var filepath = path.join(os.tmpdir(), filename + extension);
var buffer = new Buffer(imageData.slice(imageData.indexOf('base64') + 7), 'base64');
fs.writeFile(filepath, buffer, {
encoding: 'base64'
}, function (err) {
callback(err, filepath);
});
};

@ -389,8 +389,8 @@ function createMenuItems(next) {
if (err || exists) { if (err || exists) {
return next(err); return next(err);
} }
var navigation = require('./navigation/admin'), var navigation = require('./navigation/admin');
data = require('../install/data/navigation.json'); var data = require('../install/data/navigation.json');
navigation.save(data, next); navigation.save(data, next);
}); });

@ -1,14 +1,12 @@
"use strict"; "use strict";
var async = require('async'); var async = require('async');
var plugins = require('../plugins'); var plugins = require('../plugins');
var db = require('../database'); var db = require('../database');
var translator = require('../../public/src/modules/translator'); var translator = require('../../public/src/modules/translator');
var pubsub = require('../pubsub'); var pubsub = require('../pubsub');
var admin = {}; var admin = module.exports;
admin.cache = null; admin.cache = null;
pubsub.on('admin:navigation:save', function () { pubsub.on('admin:navigation:save', function () {
@ -71,5 +69,3 @@ function getAvailable(callback) {
plugins.fireHook('filter:navigation.available', core, callback); plugins.fireHook('filter:navigation.available', core, callback);
} }
module.exports = admin;

@ -23,9 +23,7 @@ module.exports = function (SocketTopics) {
if (!canMove) { if (!canMove) {
return next(new Error('[[error:no-privileges]]')); return next(new Error('[[error:no-privileges]]'));
} }
next();
},
function (next) {
topics.getTopicFields(tid, ['cid', 'slug'], next); topics.getTopicFields(tid, ['cid', 'slug'], next);
}, },
function (_topicData, next) { function (_topicData, next) {

@ -272,35 +272,34 @@ SocketUser.invite = function (socket, email, callback) {
return callback(new Error('[[error:forum-not-invite-only]]')); return callback(new Error('[[error:forum-not-invite-only]]'));
} }
var max = meta.config.maximumInvites; async.waterfall([
function (next) {
user.isAdministrator(socket.uid, function (err, admin) { user.isAdministrator(socket.uid, next);
if (err) { },
return callback(err); function (isAdmin, next) {
if (registrationType === 'admin-invite-only' && !isAdmin) {
return next(new Error('[[error:no-privileges]]'));
} }
if (registrationType === 'admin-invite-only' && !admin) {
return callback(new Error('[[error:no-privileges]]')); var max = parseInt(meta.config.maximumInvites, 10);
if (!max) {
return user.sendInvitationEmail(socket.uid, email, callback);
} }
if (max) {
async.waterfall([ async.waterfall([
function (next) { function (next) {
user.getInvitesNumber(socket.uid, next); user.getInvitesNumber(socket.uid, next);
}, },
function (invites, next) { function (invites, next) {
if (!admin && invites > max) { if (!isAdmin && invites >= max) {
return next(new Error('[[error:invite-maximum-met, ' + invites + ', ' + max + ']]')); return next(new Error('[[error:invite-maximum-met, ' + invites + ', ' + max + ']]'));
} }
next();
},
function (next) {
user.sendInvitationEmail(socket.uid, email, next); user.sendInvitationEmail(socket.uid, email, next);
} }
], callback); ], next);
} else {
user.sendInvitationEmail(socket.uid, email, callback);
} }
}); ], callback);
}; };
SocketUser.getUserByUID = function (socket, uid, callback) { SocketUser.getUserByUID = function (socket, uid, callback) {

@ -99,6 +99,7 @@ function setupConfigs() {
nconf.set('use_port', !!urlObject.port); nconf.set('use_port', !!urlObject.port);
nconf.set('relative_path', relativePath); nconf.set('relative_path', relativePath);
nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567); nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567);
nconf.set('upload_url', '/assets/uploads');
} }
function printStartupInfo() { function printStartupInfo() {

@ -61,9 +61,7 @@ module.exports = function (User) {
if (exists) { if (exists) {
return next(new Error('[[error:email-taken]]')); return next(new Error('[[error:email-taken]]'));
} }
next();
},
function (next) {
async.parallel([ async.parallel([
function (next) { function (next) {
db.setAdd('invitation:uid:' + uid, email, next); db.setAdd('invitation:uid:' + uid, email, next);
@ -131,11 +129,11 @@ module.exports = function (User) {
return next(new Error('[[error:invalid-username]]')); return next(new Error('[[error:invalid-username]]'));
} }
async.parallel([ async.parallel([
function deleteFromReferenceList(next) { function (next) {
db.setRemove('invitation:uid:' + invitedByUid, email, next); deleteFromReferenceList(invitedByUid, email, next);
}, },
function deleteInviteKey(next) { function (next) {
db.delete('invitation:email:' + email, callback); db.delete('invitation:email:' + email, next);
} }
], function (err) { ], function (err) {
next(err); next(err);
@ -146,7 +144,37 @@ module.exports = function (User) {
User.deleteInvitationKey = function (email, callback) { User.deleteInvitationKey = function (email, callback) {
callback = callback || function () {}; callback = callback || function () {};
db.delete('invitation:email:' + email, callback);
async.waterfall([
function (next) {
User.getInvitingUsers(next);
},
function (uids, next) {
async.each(uids, function (uid, next) {
deleteFromReferenceList(uid, email, next);
}, next);
},
function (next) {
db.delete('invitation:email:' + email, next);
}
], callback);
}; };
function deleteFromReferenceList(uid, email, callback) {
async.waterfall([
function (next) {
db.setRemove('invitation:uid:' + uid, email, next);
},
function (next) {
db.setCount('invitation:uid:' + uid, next);
},
function (count, next) {
if (count === 0) {
return db.setRemove('invitation:uids', uid, next);
}
setImmediate(next);
}
], callback);
}
}; };

@ -1,11 +1,8 @@
'use strict'; 'use strict';
var async = require('async'); var async = require('async');
var path = require('path');
var fs = require('fs'); var fs = require('fs');
var os = require('os');
var nconf = require('nconf'); var nconf = require('nconf');
var crypto = require('crypto');
var winston = require('winston'); var winston = require('winston');
var request = require('request'); var request = require('request');
var mime = require('mime'); var mime = require('mime');
@ -19,72 +16,7 @@ var db = require('../database');
module.exports = function (User) { module.exports = function (User) {
User.uploadPicture = function (uid, picture, callback) { User.uploadPicture = function (uid, picture, callback) {
User.uploadCroppedPicture({uid: uid, file: picture}, callback);
var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256;
var extension = path.extname(picture.name);
var updateUid = uid;
var imageDimension = parseInt(meta.config.profileImageDimension, 10) || 128;
var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1;
var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1;
var uploadedImage;
if (parseInt(meta.config.allowProfileImageUploads) !== 1) {
return callback(new Error('[[error:profile-image-uploads-disabled]]'));
}
if (picture.size > uploadSize * 1024) {
return callback(new Error('[[error:file-too-big, ' + uploadSize + ']]'));
}
if (!extension) {
return callback(new Error('[[error:invalid-image-extension]]'));
}
async.waterfall([
function (next) {
if (plugins.hasListeners('filter:uploadImage')) {
return plugins.fireHook('filter:uploadImage', {image: picture, uid: updateUid}, next);
}
var filename = updateUid + '-profileimg' + (keepAllVersions ? '-' + Date.now() : '') + (convertToPNG ? '.png' : extension);
async.waterfall([
function (next) {
file.isFileTypeAllowed(picture.path, next);
},
function (next) {
image.resizeImage({
path: picture.path,
extension: extension,
width: imageDimension,
height: imageDimension,
write: false,
}, next);
},
function (next) {
if (!convertToPNG) {
return next();
}
async.series([
async.apply(image.normalise, picture.path, extension),
async.apply(fs.rename, picture.path + '.png', picture.path)
], function (err) {
next(err);
});
},
function (next) {
file.saveFileToLocal(filename, 'profile', picture.path, next);
},
], next);
},
function (_image, next) {
uploadedImage = _image;
User.setUserFields(updateUid, {uploadedpicture: uploadedImage.url, picture: uploadedImage.url}, next);
},
function (next) {
next(null, uploadedImage);
}
], callback);
}; };
User.uploadFromUrl = function (uid, url, callback) { User.uploadFromUrl = function (uid, url, callback) {
@ -92,10 +24,11 @@ module.exports = function (User) {
return callback(new Error('[[error:no-plugin]]')); return callback(new Error('[[error:no-plugin]]'));
} }
request.head(url, function (err, res) { async.waterfall([
if (err) { function (next) {
return callback(err); request.head(url, next);
} },
function (res, body, next) {
var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256; var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256;
var size = res.headers['content-length']; var size = res.headers['content-length'];
var type = res.headers['content-type']; var type = res.headers['content-type'];
@ -109,15 +42,23 @@ module.exports = function (User) {
return callback(new Error('[[error:file-too-big, ' + uploadSize + ']]')); return callback(new Error('[[error:file-too-big, ' + uploadSize + ']]'));
} }
var picture = {url: url, name: ''}; plugins.fireHook('filter:uploadImage', {
plugins.fireHook('filter:uploadImage', {image: picture, uid: uid}, function (err, image) { uid: uid,
if (err) { image: {
return callback(err); url: url,
name: ''
} }
User.setUserFields(uid, {uploadedpicture: image.url, picture: image.url}); }, next);
callback(null, image); },
}); function (image, next) {
User.setUserFields(uid, {
uploadedpicture: image.url,
picture: image.url
}, function (err) {
next(err, image);
}); });
}
], callback);
}; };
User.updateCoverPosition = function (uid, position, callback) { User.updateCoverPosition = function (uid, position, callback) {
@ -125,8 +66,12 @@ module.exports = function (User) {
}; };
User.updateCoverPicture = function (data, callback) { User.updateCoverPicture = function (data, callback) {
var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1;
var url, md5sum; var url;
var picture = {
name: 'profileCover',
uid: data.uid
};
if (!data.imageData && data.position) { if (!data.imageData && data.position) {
return User.updateCoverPosition(data.uid, data.position, callback); return User.updateCoverPosition(data.uid, data.position, callback);
@ -145,128 +90,141 @@ module.exports = function (User) {
} }
if (data.file) { if (data.file) {
return next(); return setImmediate(next, null, data.file.path);
} }
md5sum = crypto.createHash('md5'); image.writeImageDataToTempFile(data.imageData, next);
md5sum.update(data.imageData);
md5sum = md5sum.digest('hex');
data.file = {
path: path.join(os.tmpdir(), md5sum)
};
var buffer = new Buffer(data.imageData.slice(data.imageData.indexOf('base64') + 7), 'base64');
fs.writeFile(data.file.path, buffer, {
encoding: 'base64'
}, next);
}, },
function (next) { function (path, next) {
var image = { picture.path = path;
name: 'profileCover',
path: data.file.path,
uid: data.uid
};
if (plugins.hasListeners('filter:uploadImage')) { var extension = data.file ? file.typeToExtension(data.file.type) : image.extensionFromBase64(data.imageData);
return plugins.fireHook('filter:uploadImage', {image: image, uid: data.uid}, next); var filename = generateProfileImageFilename(data.uid, 'profilecover', extension);
} uploadProfileOrCover(filename, picture, next);
var filename = data.uid + '-profilecover' + (keepAllVersions ? '-' + Date.now() : '');
async.waterfall([
function (next) {
file.isFileTypeAllowed(data.file.path, next);
},
function (next) {
file.saveFileToLocal(filename, 'profile', image.path, next);
},
function (upload, next) {
next(null, {
url: nconf.get('relative_path') + upload.url,
name: image.name
});
}
], next);
}, },
function (uploadData, next) { function (uploadData, next) {
url = uploadData.url; url = uploadData.url;
User.setUserField(data.uid, 'cover:url', uploadData.url, next); User.setUserField(data.uid, 'cover:url', uploadData.url, next);
}, },
function (next) { function (next) {
fs.unlink(data.file.path, function (err) { if (data.position) {
if (err) { User.updateCoverPosition(data.uid, data.position, next);
winston.error(err); } else {
setImmediate(next);
} }
next();
});
} }
], function (err) { ], function (err) {
if (err) { deleteFile(picture.path);
return fs.unlink(data.file.path, function (unlinkErr) { callback(err, {
if (unlinkErr) { url: url
winston.error(unlinkErr);
}
callback(err); // send back the original error
});
}
if (data.position) {
User.updateCoverPosition(data.uid, data.position, function (err) {
callback(err, {url: url});
}); });
} else {
callback(err, {url: url});
}
}); });
}; };
User.uploadCroppedPicture = function (data, callback) { User.uploadCroppedPicture = function (data, callback) {
var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1;
var url, md5sum;
if (!data.imageData) { if (parseInt(meta.config.allowProfileImageUploads) !== 1) {
return callback(new Error('[[error:profile-image-uploads-disabled]]'));
}
if (!data.imageData && !data.file) {
return callback(new Error('[[error:invalid-data]]')); return callback(new Error('[[error:invalid-data]]'));
} }
async.waterfall([
function (next) {
var size = data.file ? data.file.size : data.imageData.length; var size = data.file ? data.file.size : data.imageData.length;
var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256; var uploadSize = parseInt(meta.config.maximumProfileImageSize, 10) || 256;
if (size > uploadSize * 1024) { if (size > uploadSize * 1024) {
return next(new Error('[[error:file-too-big, ' + meta.config.maximumProfileImageSize + ']]')); return callback(new Error('[[error:file-too-big, ' + meta.config.maximumProfileImageSize + ']]'));
} }
md5sum = crypto.createHash('md5'); var type = data.file ? data.file.type : image.mimeFromBase64(data.imageData);
md5sum.update(data.imageData); var extension = file.typeToExtension(type);
md5sum = md5sum.digest('hex'); if (!extension) {
return callback(new Error('[[error:invalid-image-extension]]'));
}
data.file = { var uploadedImage;
path: path.join(os.tmpdir(), md5sum)
var picture = {
name: 'profileAvatar',
uid: data.uid
}; };
var buffer = new Buffer(data.imageData.slice(data.imageData.indexOf('base64') + 7), 'base64'); async.waterfall([
function (next) {
if (data.file) {
return setImmediate(next, null, data.file.path);
}
image.writeImageDataToTempFile(data.imageData, next);
},
function (path, next) {
convertToPNG(path, extension, next);
},
function (path, next) {
picture.path = path;
fs.writeFile(data.file.path, buffer, { var imageDimension = parseInt(meta.config.profileImageDimension, 10) || 128;
encoding: 'base64' image.resizeImage({
path: picture.path,
extension: extension,
width: imageDimension,
height: imageDimension
}, next); }, next);
}, },
function (next) { function (next) {
var image = { var filename = generateProfileImageFilename(data.uid, 'profileavatar', extension);
name: 'profileAvatar', uploadProfileOrCover(filename, picture, next);
path: data.file.path, },
uid: data.uid function (_uploadedImage, next) {
uploadedImage = _uploadedImage;
User.setUserFields(data.uid, {
uploadedpicture: uploadedImage.url,
picture: uploadedImage.url
}, next);
}
], function (err) {
deleteFile(picture.path);
callback(err, uploadedImage);
});
}; };
function convertToPNG(path, extension, callback) {
var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1;
if (!convertToPNG) {
return setImmediate(callback, null, path);
}
image.normalise(path, extension, function (err, newPath) {
if (err) {
return callback(err);
}
deleteFile(path);
callback(null, newPath);
});
}
function uploadProfileOrCover(filename, image, callback) {
if (plugins.hasListeners('filter:uploadImage')) { if (plugins.hasListeners('filter:uploadImage')) {
return plugins.fireHook('filter:uploadImage', {image: image, uid: data.uid}, next); return plugins.fireHook('filter:uploadImage', {
image: image,
uid: image.uid
}, callback);
}
saveFileToLocal(filename, image, callback);
}
function generateProfileImageFilename(uid, type, extension) {
var keepAllVersions = parseInt(meta.config['profile:keepAllUserImages'], 10) === 1;
var convertToPNG = parseInt(meta.config['profile:convertProfileImageToPNG'], 10) === 1;
return uid + '-' + type + (keepAllVersions ? '-' + Date.now() : '') + (convertToPNG ? '.png' : extension);
} }
var filename = data.uid + '-profileavatar' + (keepAllVersions ? '-' + Date.now() : ''); function saveFileToLocal(filename, image, callback) {
async.waterfall([ async.waterfall([
function (next) { function (next) {
file.isFileTypeAllowed(data.file.path, next); file.isFileTypeAllowed(image.path, next);
}, },
function (next) { function (next) {
file.saveFileToLocal(filename, 'profile', image.path, next); file.saveFileToLocal(filename, 'profile', image.path, next);
@ -274,32 +232,23 @@ module.exports = function (User) {
function (upload, next) { function (upload, next) {
next(null, { next(null, {
url: nconf.get('relative_path') + upload.url, url: nconf.get('relative_path') + upload.url,
path: upload.path,
name: image.name name: image.name
}); });
} }
], next); ], callback);
}, }
function (uploadData, next) {
url = uploadData.url; function deleteFile(path) {
User.setUserFields(data.uid, {uploadedpicture: url, picture: url}, next); if (path) {
}, fs.unlink(path, function (err) {
function (next) {
fs.unlink(data.file.path, function (err) {
if (err) { if (err) {
winston.error(err); winston.error(err);
} }
next();
}); });
} }
], function (err) {
if (err) {
callback(err); // send back the original error
} }
callback(err, {url: url});
});
};
User.removeCoverPicture = function (data, callback) { User.removeCoverPicture = function (data, callback) {
db.deleteObjectFields('user:' + data.uid, ['cover:url', 'cover:position'], callback); db.deleteObjectFields('user:' + data.uid, ['cover:url', 'cover:position'], callback);
}; };

@ -29,6 +29,12 @@ describe('Controllers', function () {
}, },
user: function (next) { user: function (next) {
user.create({username: 'foo', password: 'barbar'}, next); user.create({username: 'foo', password: 'barbar'}, next);
},
navigation: function (next) {
var navigation = require('../src/navigation/admin');
var data = require('../install/data/navigation.json');
navigation.save(data, next);
} }
}, function (err, results) { }, function (err, results) {
if (err) { if (err) {

@ -0,0 +1,177 @@
<html>
<head>
<title>Excessive Load Warning</title>
<link href='https://fonts.googleapis.com/css?family=Ubuntu:400,500,700' rel='stylesheet' type='text/css'>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
body {
background: #00A9EA;
color: white;
font-family: 'Ubuntu', sans-serif;
text-align: center;
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
transform-style: preserve-3d;
}
h1 {
font-size: 250px;
color: #fff;
opacity: 0.5;
margin: 10px;
cursor: pointer;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
}
p {
font-size: 20px;
}
p strong {
font-size: 28px;
}
@media (max-width: 640px) {
h1 {
font-size: 125px;
}
p {
font-size: 16px;
}
p strong {
font-size: 20px;
}
}
.center {
position: relative;
top: 50%;
-webkit-transform: translateY(50%);
-ms-transform: translateY(50%);
transform: translateY(50%);
}
@-webkit-keyframes bounce {
0%, 20%, 53%, 80%, 100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
40%, 43% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
-webkit-transform: translate3d(0,-4px,0);
transform: translate3d(0,-4px,0);
}
}
@keyframes bounce {
0%, 20%, 53%, 80%, 100% {
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
-webkit-transform: translate3d(0,0,0);
transform: translate3d(0,0,0);
}
40%, 43% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
-webkit-transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
transition-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
-webkit-transform: translate3d(0,-4px,0);
transform: translate3d(0,-4px,0);
}
}
.bounce {
-webkit-animation-name: bounce;
animation-name: bounce;
-webkit-transform-origin: center bottom;
-ms-transform-origin: center bottom;
transform-origin: center bottom;
}
.animated {
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.animated.infinite {
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
.animated.hinge {
-webkit-animation-duration: 2s;
animation-duration: 2s;
}
.hide {
display: none;
}
</style>
<script type="text/javascript">
window.onload = function() {
var count = 0,
bounce = document.getElementById('click-me');
bounce.onclick = function() {
count++;
bounce.className = '';
setTimeout(function() {
bounce.className = 'animated bounce';
}, 50);
if (count > 5) {
document.getElementById('hide').className = '';
}
};
}
</script>
</head>
<body>
<div class="wrapper">
<div class="center">
<h1 id="click-me" class="animated bounce">503</h1>
<p>
<strong>This forum is temporarily unavailable due to excessive load.</strong>
</p>
<p>
We shouldn't be down for long. Please check back shortly. Sorry for the inconvenience!
</p>
<p>
&nbsp;<small id="hide" class="hide">Alright. You can stop clicking... it's not going to make the site come back sooner!</small>
</p>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

@ -707,8 +707,8 @@ describe('Groups', function () {
describe('groups cover', function () { describe('groups cover', function () {
var socketGroups = require('../src/socket.io/groups'); var socketGroups = require('../src/socket.io/groups');
var regularUid; var regularUid;
var logoPath = path.join(__dirname, '../public/logo.png'); var logoPath = path.join(__dirname, '../test/files/test.png');
var imagePath = path.join(__dirname, '../public/groupcover.png'); var imagePath = path.join(__dirname, '../test/files/groupcover.png');
before(function (done) { before(function (done) {
User.create({username: 'regularuser', password: '123456'}, function (err, uid) { User.create({username: 'regularuser', password: '123456'}, function (err, uid) {
assert.ifError(err); assert.ifError(err);

@ -1,2 +1,2 @@
--reporter dot --reporter dot
--timeout 15000 --timeout 25000

@ -67,18 +67,18 @@ describe('Upload Controllers', function () {
}); });
it('should upload a profile picture', function (done) { it('should upload a profile picture', function (done) {
helpers.uploadFile(nconf.get('url') + '/api/user/regular/uploadpicture', path.join(__dirname, '../public/logo.png'), {}, jar, csrf_token, function (err, res, body) { helpers.uploadFile(nconf.get('url') + '/api/user/regular/uploadpicture', path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, function (err, res, body) {
assert.ifError(err); assert.ifError(err);
assert.equal(res.statusCode, 200); assert.equal(res.statusCode, 200);
assert(Array.isArray(body)); assert(Array.isArray(body));
assert.equal(body.length, 1); assert.equal(body.length, 1);
assert.equal(body[0].url, '/assets/uploads/profile/' + regularUid + '-profileimg.png'); assert.equal(body[0].url, '/assets/uploads/profile/' + regularUid + '-profileavatar.png');
done(); done();
}); });
}); });
it('should upload an image to a post', function (done) { it('should upload an image to a post', function (done) {
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../public/logo.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'), {cid: cid}, jar, csrf_token, function (err, res, body) {
assert.ifError(err); assert.ifError(err);
assert.equal(res.statusCode, 200); assert.equal(res.statusCode, 200);
assert(Array.isArray(body)); assert(Array.isArray(body));
@ -91,7 +91,7 @@ describe('Upload Controllers', function () {
it('should upload a file to a post', function (done) { it('should upload a file to a post', function (done) {
meta.config.allowFileUploads = 1; meta.config.allowFileUploads = 1;
helpers.uploadFile(nconf.get('url') + '/api/post/upload', path.join(__dirname, '../public/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'), {cid: cid}, jar, csrf_token, function (err, res, body) {
assert.ifError(err); assert.ifError(err);
assert.equal(res.statusCode, 200); assert.equal(res.statusCode, 200);
assert(Array.isArray(body)); assert(Array.isArray(body));
@ -118,7 +118,7 @@ describe('Upload Controllers', function () {
}); });
it('should upload site logo', function (done) { it('should upload site logo', function (done) {
helpers.uploadFile(nconf.get('url') + '/api/admin/uploadlogo', path.join(__dirname, '../public/logo.png'), {}, jar, csrf_token, function (err, res, body) { helpers.uploadFile(nconf.get('url') + '/api/admin/uploadlogo', path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, function (err, res, body) {
assert.ifError(err); assert.ifError(err);
assert.equal(res.statusCode, 200); assert.equal(res.statusCode, 200);
assert(Array.isArray(body)); assert(Array.isArray(body));
@ -128,7 +128,7 @@ describe('Upload Controllers', function () {
}); });
it('should upload category image', function (done) { it('should upload category image', function (done) {
helpers.uploadFile(nconf.get('url') + '/api/admin/category/uploadpicture', path.join(__dirname, '../public/logo.png'), {params: JSON.stringify({cid: cid})}, jar, csrf_token, function (err, res, body) { helpers.uploadFile(nconf.get('url') + '/api/admin/category/uploadpicture', path.join(__dirname, '../test/files/test.png'), {params: JSON.stringify({cid: cid})}, jar, csrf_token, function (err, res, body) {
assert.ifError(err); assert.ifError(err);
assert.equal(res.statusCode, 200); assert.equal(res.statusCode, 200);
assert(Array.isArray(body)); assert(Array.isArray(body));
@ -138,7 +138,7 @@ describe('Upload Controllers', function () {
}); });
it('should upload favicon', function (done) { it('should upload favicon', function (done) {
helpers.uploadFile(nconf.get('url') + '/api/admin/uploadfavicon', path.join(__dirname, '../public/favicon.ico'), {}, jar, csrf_token, function (err, res, body) { helpers.uploadFile(nconf.get('url') + '/api/admin/uploadfavicon', path.join(__dirname, '../test/files/favicon.ico'), {}, jar, csrf_token, function (err, res, body) {
assert.ifError(err); assert.ifError(err);
assert.equal(res.statusCode, 200); assert.equal(res.statusCode, 200);
assert(Array.isArray(body)); assert(Array.isArray(body));
@ -148,7 +148,7 @@ describe('Upload Controllers', function () {
}); });
it('should upload touch icon', function (done) { it('should upload touch icon', function (done) {
helpers.uploadFile(nconf.get('url') + '/api/admin/uploadTouchIcon', path.join(__dirname, '../public/logo.png'), {}, jar, csrf_token, function (err, res, body) { helpers.uploadFile(nconf.get('url') + '/api/admin/uploadTouchIcon', path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, function (err, res, body) {
assert.ifError(err); assert.ifError(err);
assert.equal(res.statusCode, 200); assert.equal(res.statusCode, 200);
assert(Array.isArray(body)); assert(Array.isArray(body));

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save