diff --git a/package.json b/package.json index a440575644..e4f665bfc6 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,8 @@ "prompt": "~0.2.11", "uglify-js": "~2.4.0", "validator": "~1.5.1", - "nodebb-plugin-mentions": "~0.1.16", - "nodebb-plugin-markdown": "~0.2.1", + "nodebb-plugin-mentions": "~0.1", + "nodebb-plugin-markdown": "~0.3", "nodebb-theme-vanilla": "~0.0.12", "nodebb-theme-cerulean": "0.0.10", "cron": "~1.0.1", diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js index 192a707142..f148dc2d49 100644 --- a/public/src/modules/composer.js +++ b/public/src/modules/composer.js @@ -94,9 +94,8 @@ define(['taskbar'], function(taskbar) { var postContainer = $(composerTemplate[0]); - if(config.imgurClientIDSet) { + if(config.allowFileUploads || config.imgurClientIDSet) initializeFileReader(post_uuid); - } var postData = composer.posts[post_uuid], titleEl = postContainer.find('.title'), diff --git a/public/templates/admin/settings.tpl b/public/templates/admin/settings.tpl index 5f334cad02..aa364e4742 100644 --- a/public/templates/admin/settings.tpl +++ b/public/templates/admin/settings.tpl @@ -123,7 +123,7 @@
diff --git a/public/templates/category.tpl b/public/templates/category.tpl index 8b56a89107..30dd871851 100644 --- a/public/templates/category.tpl +++ b/public/templates/category.tpl @@ -10,12 +10,13 @@
- +
     
+

diff --git a/src/categories.js b/src/categories.js index ac65ae2405..b511ede803 100644 --- a/src/categories.js +++ b/src/categories.js @@ -80,6 +80,7 @@ var db = require('./database.js'), 'category_id': category_id, 'active_users': [], 'topics': [], + 'disableSocialButtons': meta.config.disableSocialButtons !== undefined ? parseInt(meta.config.disableSocialButtons, 10) !== 0 : false, 'twitter-intent-url': 'https://twitter.com/intent/tweet?url=' + encodeURIComponent(nconf.get('url') + 'category/' + category_slug) + '&text=' + encodeURIComponent(category_name), 'facebook-share-url': 'https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent(nconf.get('url') + 'category/' + category_slug), 'google-share-url': 'https://plus.google.com/share?url=' + encodeURIComponent(nconf.get('url') + 'category/' + category_slug), diff --git a/src/database/mongo.js b/src/database/mongo.js index f9b6ee8eea..fceb39d00f 100644 --- a/src/database/mongo.js +++ b/src/database/mongo.js @@ -50,13 +50,20 @@ function createCollections() { db.createCollection('objects', function(err, collection) { if(err) { - winston.error("Error creating collection " + err.message); + winston.error('Error creating collection ' + err.message); return; } + if(collection) { - collection.ensureIndex({_key :1}, {background:true}, function(err, name){ + collection.ensureIndex({_key :1}, {background:true}, function(err, name) { if(err) { - winston.error("Error creating index " + err.message); + winston.error('Error creating index ' + err.message); + } + }); + + collection.ensureIndex({'expireAt':1}, {expireAfterSeconds:0, background:true}, function(err, name) { + if(err) { + winston.error('Error creating index ' + err.message); } }); } @@ -64,13 +71,13 @@ db.createCollection('search', function(err, collection) { if(err) { - winston.error("Error creating collection " + err.message); + winston.error('Error creating collection ' + err.message); return; } if(collection) { collection.ensureIndex({content:'text'}, {background:true}, function(err, name){ if(err) { - winston.error("Error creating index " + err.message); + winston.error('Error creating index ' + err.message); } }); } @@ -241,6 +248,14 @@ }); } + module.expire = function(key, seconds, callback) { + module.expireAt(key, Math.round(Date.now() / 1000) + seconds, callback); + } + + module.expireAt = function(key, timestamp, callback) { + module.setObjectField(key, 'expireAt', new Date(timestamp * 1000), callback); + } + //hashes module.setObject = function(key, data, callback) { data['_key'] = key; @@ -254,6 +269,9 @@ module.setObjectField = function(key, field, value, callback) { var data = {}; // if there is a '.' in the field name it inserts subdocument in mongo, replace '.'s with \uff0E + if(typeof field !== 'string') { + field = field.toString(); + } field = field.replace(/\./g, '\uff0E'); data[field] = value; db.collection('objects').update({_key:key}, {$set:data}, {upsert:true, w: 1}, function(err, result) { @@ -303,7 +321,7 @@ var _fields = {}; for(var i=0; i 0) { return opts.express.ofn(req,res,next); - } - else { + } else { return next(); } } @@ -140,11 +150,13 @@ var opts = { for(var v in clients) { var client = clients[v]; - if(client.oEmit != undefined && client.oEmit != client.emit) + if(client.oEmit != undefined && client.oEmit != client.emit) { client.emit = client.oEmit; + } - if(client.$oEmit != undefined && client.$oEmit != client.$emit) + if(client.$oEmit != undefined && client.$oEmit != client.$emit) { client.$emit = client.$oEmit; + } } } diff --git a/src/postTools.js b/src/postTools.js index 5af411e55e..37d1ac0a53 100644 --- a/src/postTools.js +++ b/src/postTools.js @@ -152,7 +152,7 @@ var winston = require('winston'), // Delete the thread if it is the last undeleted post threadTools.getLatestUndeletedPid(postData.tid, function(err, pid) { if (err && err.message === 'no-undeleted-pids-found') { - threadTools.delete(postData.tid, function(err) { + threadTools.delete(postData.tid, uid, function(err) { if (err) { winston.error('Could not delete topic (tid: ' + postData.tid + ')', err.stack); } diff --git a/src/posts.js b/src/posts.js index 0eb19b4a24..0ab4222321 100644 --- a/src/posts.js +++ b/src/posts.js @@ -360,24 +360,26 @@ var db = require('./database'), Posts.uploadPostImage = function(image, callback) { - if(!meta.config.imgurClientID) { - return callback('imgurClientID not set', null); - } + if(meta.config.imgurClientID) { + if(!image) { + return callback('invalid image', null); + } - if(!image) { - return callback('invalid image', null); + require('./imgur').upload(meta.config.imgurClientID, image.data, 'base64', function(err, data) { + if(err) { + callback(err.message, null); + } else { + callback(null, { + url: data.link, + name: image.name + }); + } + }); + } else if (meta.config.allowFileUploads) { + Posts.uploadPostFile(image, callback); + } else { + callback('Uploads are disabled!'); } - - require('./imgur').upload(meta.config.imgurClientID, image.data, 'base64', function(err, data) { - if(err) { - callback(err.message, null); - } else { - callback(null, { - url: data.link, - name: image.name - }); - } - }); } Posts.uploadPostFile = function(file, callback) { @@ -400,7 +402,7 @@ var db = require('./database'), var uploadPath = path.join(nconf.get('base_dir'), nconf.get('upload_path'), filename); fs.writeFile(uploadPath, buffer, function (err) { - if(err) { + if(err) { callback(err.message, null); } else { callback(null, { diff --git a/src/routes/api.js b/src/routes/api.js index 3f0dbc270c..c637dd58f5 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -66,7 +66,7 @@ var path = require('path'), data.motd_class = (parseInt(meta.config.show_motd, 10) === 1 || meta.config.show_motd === undefined) ? '' : ' none'; data.motd_class += (meta.config.motd && meta.config.motd.length > 0 ? '' : ' default'); - data.motd = require('marked')(meta.config.motd || "\n\n# NodeBB v" + pkg.version + "\nWelcome to NodeBB, the discussion platform of the future."); + data.motd = require('marked')(meta.config.motd || "\n\n# NodeBB v" + pkg.version + "\nWelcome to NodeBB, the discussion platform of the future."); res.json(data); }); }); diff --git a/src/routes/user.js b/src/routes/user.js index dd5491b43e..144cf08508 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -539,7 +539,7 @@ var fs = require('fs'), if (!data.birthday) { data.age = ''; } else { - data.age = new Date().getFullYear() - new Date(data.birthday).getFullYear(); + data.age = Math.floor((new Date().getTime() - new Date(data.birthday).getTime()) / 31536000000); } function canSeeEmail() { @@ -581,4 +581,4 @@ var fs = require('fs'), }; -}(exports)); \ No newline at end of file +}(exports)); diff --git a/src/threadTools.js b/src/threadTools.js index 859d6faac4..b69c42fa7c 100644 --- a/src/threadTools.js +++ b/src/threadTools.js @@ -91,7 +91,7 @@ var winston = require('winston'), } } - ThreadTools.delete = function(tid, callback) { + ThreadTools.delete = function(tid, uid, callback) { topics.delete(tid); db.decrObjectField('global', 'topicCount'); @@ -112,7 +112,7 @@ var winston = require('winston'), } } - ThreadTools.restore = function(tid, socket, callback) { + ThreadTools.restore = function(tid, uid, callback) { topics.restore(tid); db.incrObjectField('global', 'topicCount'); ThreadTools.unlock(tid); diff --git a/src/webserver.js b/src/webserver.js index e5e3915ec5..035e8ae0b3 100644 --- a/src/webserver.js +++ b/src/webserver.js @@ -55,7 +55,7 @@ var path = require('path'), /** * `options` object requires: req, res - * accepts: metaTags + * accepts: metaTags, linkTags */ app.build_header = function (options, callback) { var custom_header = { @@ -83,8 +83,6 @@ var path = require('path'), rel: 'apple-touch-icon', href: meta.config['brand:logo'] || nconf.get('relative_path') + '/logo.png' }], - metaString = utils.buildMetaTags(defaultMetaTags.concat(options.metaTags || [])), - linkTags = utils.buildLinkTags(defaultLinkTags.concat(options.linkTags || [])), templateValues = { cssSrc: meta.config['theme:src'] || nconf.get('relative_path') + '/vendor/bootstrap/css/bootstrap.min.css', pluginCSS: plugins.cssFiles.map(function(file) { return { path: file + (meta.config['cache-buster'] ? '?v=' + meta.config['cache-buster'] : '') }; }), @@ -96,16 +94,30 @@ var path = require('path'), browserTitle: meta.config.title || 'NodeBB', csrf: options.res.locals.csrf_token, relative_path: nconf.get('relative_path'), - meta_tags: metaString, - link_tags: linkTags, clientScripts: clientScripts, navigation: custom_header.navigation, 'cache-buster': meta.config['cache-buster'] ? 'v=' + meta.config['cache-buster'] : '', allowRegistration: meta.config.allowRegistration === undefined || parseInt(meta.config.allowRegistration, 10) === 1 + }, + escapeList = { + '&': '&', + '<': '<', + '>': '>', + "'": ''', + '"': '"' }; var uid = '0'; + // Meta Tags + templateValues.meta_tags = utils.buildMetaTags(defaultMetaTags.concat(options.metaTags || []).map(function(tag) { + tag.content = tag.content.replace(/[&<>'"]/g, function(tag) { + return escapeList[tag] || tag; + }); + return tag; + })); + templateValues.link_tags = utils.buildLinkTags(defaultLinkTags.concat(options.linkTags || [])); + if(options.req.user && options.req.user.uid) { uid = options.req.user.uid; } diff --git a/src/websockets.js b/src/websockets.js index b435d4b6ec..f6bf315df3 100644 --- a/src/websockets.js +++ b/src/websockets.js @@ -562,7 +562,7 @@ websockets.init = function(io) { socket.on('api:topic.delete', function(data) { threadTools.privileges(data.tid, uid, function(err, privileges) { if (!err && privileges.editable) { - threadTools.delete(data.tid, function(err) { + threadTools.delete(data.tid, uid, function(err) { if (!err) { emitTopicPostStats(); socket.emit('api:topic.delete', { @@ -578,7 +578,7 @@ websockets.init = function(io) { socket.on('api:topic.restore', function(data) { threadTools.privileges(data.tid, uid, function(err, privileges) { if (!err && privileges.editable) { - threadTools.restore(data.tid, socket, function(err) { + threadTools.restore(data.tid, uid, function(err) { emitTopicPostStats(); socket.emit('api:topic.restore', {