2013-12-06 18:13:15 +04:00
|
|
|
// Contains all path information to be used throughout
|
|
|
|
// the codebase.
|
2013-11-20 17:58:52 +04:00
|
|
|
|
2016-05-18 17:27:54 +03:00
|
|
|
var moment = require('moment-timezone'),
|
2014-02-05 12:40:30 +04:00
|
|
|
_ = require('lodash'),
|
2016-09-12 14:53:04 +03:00
|
|
|
url = require('url'),
|
2016-09-09 13:23:47 +03:00
|
|
|
config = require('./../config'),
|
2015-12-15 13:41:53 +03:00
|
|
|
// @TODO: unify this with routes.apiBaseUrl
|
|
|
|
apiPath = '/ghost/api/v0.1';
|
2014-01-05 10:40:53 +04:00
|
|
|
|
2015-04-10 18:59:38 +03:00
|
|
|
function getBaseUrl(secure) {
|
2016-09-13 18:41:14 +03:00
|
|
|
if (secure && config.get('urlSSL')) {
|
|
|
|
return config.get('urlSSL');
|
2016-01-05 21:04:39 +03:00
|
|
|
} else {
|
|
|
|
if (secure) {
|
2016-09-13 18:41:14 +03:00
|
|
|
return config.get('url').replace('http://', 'https://');
|
2016-01-05 21:04:39 +03:00
|
|
|
} else {
|
2016-09-13 18:41:14 +03:00
|
|
|
return config.get('url');
|
2016-01-05 21:04:39 +03:00
|
|
|
}
|
|
|
|
}
|
2015-04-10 18:59:38 +03:00
|
|
|
}
|
|
|
|
|
2016-09-12 14:53:04 +03:00
|
|
|
function getSubdir() {
|
|
|
|
var localPath, subdir;
|
|
|
|
|
|
|
|
// Parse local path location
|
2016-09-13 18:41:14 +03:00
|
|
|
if (config.get('url')) {
|
|
|
|
localPath = url.parse(config.get('url')).path;
|
2016-09-12 14:53:04 +03:00
|
|
|
|
|
|
|
// Remove trailing slash
|
|
|
|
if (localPath !== '/') {
|
|
|
|
localPath = localPath.replace(/\/$/, '');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
subdir = localPath === '/' ? '' : localPath;
|
|
|
|
return subdir;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getProtectedSlugs() {
|
|
|
|
var subdir = getSubdir();
|
|
|
|
|
|
|
|
if (!_.isEmpty(subdir)) {
|
2016-09-13 18:41:14 +03:00
|
|
|
return config.get('slugs').protected.concat([subdir.split('/').pop()]);
|
2016-09-12 14:53:04 +03:00
|
|
|
} else {
|
2016-09-13 18:41:14 +03:00
|
|
|
return config.get('slugs').protected;
|
2016-09-12 14:53:04 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-10 18:59:38 +03:00
|
|
|
function urlJoin() {
|
|
|
|
var args = Array.prototype.slice.call(arguments),
|
|
|
|
prefixDoubleSlash = false,
|
2016-09-12 14:53:04 +03:00
|
|
|
subdir = getSubdir().replace(/^\/|\/+$/, ''),
|
2015-04-10 18:59:38 +03:00
|
|
|
subdirRegex,
|
|
|
|
url;
|
|
|
|
|
|
|
|
// Remove empty item at the beginning
|
|
|
|
if (args[0] === '') {
|
|
|
|
args.shift();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle schemeless protocols
|
|
|
|
if (args[0].indexOf('//') === 0) {
|
|
|
|
prefixDoubleSlash = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// join the elements using a slash
|
|
|
|
url = args.join('/');
|
|
|
|
|
|
|
|
// Fix multiple slashes
|
|
|
|
url = url.replace(/(^|[^:])\/\/+/g, '$1/');
|
|
|
|
|
|
|
|
// Put the double slash back at the beginning if this was a schemeless protocol
|
|
|
|
if (prefixDoubleSlash) {
|
|
|
|
url = url.replace(/^\//, '//');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deduplicate subdirectory
|
|
|
|
if (subdir) {
|
2016-01-11 14:37:59 +03:00
|
|
|
subdirRegex = new RegExp(subdir + '\/' + subdir + '\/');
|
|
|
|
url = url.replace(subdirRegex, subdir + '/');
|
2015-04-10 18:59:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return url;
|
|
|
|
}
|
|
|
|
|
2014-01-03 04:37:21 +04:00
|
|
|
// ## createUrl
|
|
|
|
// Simple url creation from a given path
|
|
|
|
// Ensures that our urls contain the subdirectory if there is one
|
|
|
|
// And are correctly formatted as either relative or absolute
|
|
|
|
// Usage:
|
|
|
|
// createUrl('/', true) -> http://my-ghost-blog.com/
|
|
|
|
// E.g. /blog/ subdir
|
|
|
|
// createUrl('/welcome-to-ghost/') -> /blog/welcome-to-ghost/
|
|
|
|
// Parameters:
|
|
|
|
// - urlPath - string which must start and end with a slash
|
|
|
|
// - absolute (optional, default:false) - boolean whether or not the url should be absolute
|
2014-02-22 05:25:31 +04:00
|
|
|
// - secure (optional, default:false) - boolean whether or not to use urlSSL or url config
|
2014-01-03 04:37:21 +04:00
|
|
|
// Returns:
|
|
|
|
// - a URL which always ends with a slash
|
2014-02-22 05:25:31 +04:00
|
|
|
function createUrl(urlPath, absolute, secure) {
|
2014-01-03 04:37:21 +04:00
|
|
|
urlPath = urlPath || '/';
|
|
|
|
absolute = absolute || false;
|
2015-04-10 18:59:38 +03:00
|
|
|
var base;
|
2014-01-03 04:37:21 +04:00
|
|
|
|
|
|
|
// create base of url, always ends without a slash
|
|
|
|
if (absolute) {
|
2015-04-10 18:59:38 +03:00
|
|
|
base = getBaseUrl(secure);
|
2014-01-03 04:37:21 +04:00
|
|
|
} else {
|
2016-09-12 14:53:04 +03:00
|
|
|
base = getSubdir();
|
2014-01-03 04:37:21 +04:00
|
|
|
}
|
|
|
|
|
2015-04-10 18:59:38 +03:00
|
|
|
return urlJoin(base, urlPath);
|
2014-01-03 04:37:21 +04:00
|
|
|
}
|
|
|
|
|
2016-05-18 17:27:54 +03:00
|
|
|
/**
|
|
|
|
* creates the url path for a post based on blog timezone and permalink pattern
|
|
|
|
*
|
|
|
|
* @param {JSON} post
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
2015-12-02 13:06:44 +03:00
|
|
|
function urlPathForPost(post) {
|
2014-01-03 04:37:21 +04:00
|
|
|
var output = '',
|
2016-09-13 18:41:14 +03:00
|
|
|
permalinks = config.get('theme').permalinks,
|
|
|
|
publishedAtMoment = moment.tz(post.published_at || Date.now(), config.get('theme').timezone),
|
2014-01-03 04:37:21 +04:00
|
|
|
tags = {
|
2016-05-18 17:27:54 +03:00
|
|
|
year: function () { return publishedAtMoment.format('YYYY'); },
|
|
|
|
month: function () { return publishedAtMoment.format('MM'); },
|
|
|
|
day: function () { return publishedAtMoment.format('DD'); },
|
2014-10-26 04:15:24 +03:00
|
|
|
author: function () { return post.author.slug; },
|
|
|
|
slug: function () { return post.slug; },
|
|
|
|
id: function () { return post.id; }
|
2014-01-03 04:37:21 +04:00
|
|
|
};
|
|
|
|
|
2014-06-12 13:44:10 +04:00
|
|
|
if (post.page) {
|
2014-01-03 04:37:21 +04:00
|
|
|
output += '/:slug/';
|
|
|
|
} else {
|
2014-12-10 17:03:39 +03:00
|
|
|
output += permalinks;
|
2014-01-03 04:37:21 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// replace tags like :slug or :year with actual values
|
|
|
|
output = output.replace(/(:[a-z]+)/g, function (match) {
|
|
|
|
if (_.has(tags, match.substr(1))) {
|
|
|
|
return tags[match.substr(1)]();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ## urlFor
|
|
|
|
// Synchronous url creation for a given context
|
|
|
|
// Can generate a url for a named path, given path, or known object (post)
|
|
|
|
// Determines what sort of context it has been given, and delegates to the correct generation method,
|
|
|
|
// Finally passing to createUrl, to ensure any subdirectory is honoured, and the url is absolute if needed
|
|
|
|
// Usage:
|
|
|
|
// urlFor('home', true) -> http://my-ghost-blog.com/
|
|
|
|
// E.g. /blog/ subdir
|
2015-03-26 10:01:39 +03:00
|
|
|
// urlFor({relativeUrl: '/my-static-page/'}) -> /blog/my-static-page/
|
2014-01-03 04:37:21 +04:00
|
|
|
// E.g. if post object represents welcome post, and slugs are set to standard
|
|
|
|
// urlFor('post', {...}) -> /welcome-to-ghost/
|
|
|
|
// E.g. if post object represents welcome post, and slugs are set to date
|
|
|
|
// urlFor('post', {...}) -> /2014/01/01/welcome-to-ghost/
|
|
|
|
// Parameters:
|
|
|
|
// - context - a string, or json object describing the context for which you need a url
|
|
|
|
// - data (optional) - a json object containing data needed to generate a url
|
|
|
|
// - absolute (optional, default:false) - boolean whether or not the url should be absolute
|
|
|
|
// This is probably not the right place for this, but it's the best place for now
|
|
|
|
function urlFor(context, data, absolute) {
|
|
|
|
var urlPath = '/',
|
2014-10-21 14:50:34 +04:00
|
|
|
secure, imagePathRe,
|
2015-01-28 08:57:19 +03:00
|
|
|
knownObjects = ['post', 'tag', 'author', 'image', 'nav'], baseUrl,
|
|
|
|
hostname,
|
2014-05-02 03:42:23 +04:00
|
|
|
|
|
|
|
// this will become really big
|
|
|
|
knownPaths = {
|
2014-09-10 08:06:24 +04:00
|
|
|
home: '/',
|
|
|
|
rss: '/rss/',
|
2015-12-15 13:41:53 +03:00
|
|
|
api: apiPath,
|
2015-04-10 18:59:38 +03:00
|
|
|
sitemap_xsl: '/sitemap.xsl'
|
2014-05-02 03:42:23 +04:00
|
|
|
};
|
2014-01-03 04:37:21 +04:00
|
|
|
|
|
|
|
// Make data properly optional
|
|
|
|
if (_.isBoolean(data)) {
|
|
|
|
absolute = data;
|
|
|
|
data = null;
|
|
|
|
}
|
|
|
|
|
2014-02-22 05:25:31 +04:00
|
|
|
// Can pass 'secure' flag in either context or data arg
|
|
|
|
secure = (context && context.secure) || (data && data.secure);
|
|
|
|
|
2014-01-03 04:37:21 +04:00
|
|
|
if (_.isObject(context) && context.relativeUrl) {
|
|
|
|
urlPath = context.relativeUrl;
|
|
|
|
} else if (_.isString(context) && _.indexOf(knownObjects, context) !== -1) {
|
|
|
|
// trying to create a url for an object
|
2014-12-10 17:03:39 +03:00
|
|
|
if (context === 'post' && data.post) {
|
|
|
|
urlPath = data.post.url;
|
|
|
|
secure = data.secure;
|
2014-02-13 20:15:29 +04:00
|
|
|
} else if (context === 'tag' && data.tag) {
|
2016-09-13 18:41:14 +03:00
|
|
|
urlPath = urlJoin('/', config.get('routeKeywords').tag, data.tag.slug, '/');
|
2014-02-22 05:25:31 +04:00
|
|
|
secure = data.tag.secure;
|
2014-07-20 20:41:59 +04:00
|
|
|
} else if (context === 'author' && data.author) {
|
2016-09-13 18:41:14 +03:00
|
|
|
urlPath = urlJoin('/', config.get('routeKeywords').author, data.author.slug, '/');
|
2014-07-20 20:41:59 +04:00
|
|
|
secure = data.author.secure;
|
2014-10-21 14:50:34 +04:00
|
|
|
} else if (context === 'image' && data.image) {
|
|
|
|
urlPath = data.image;
|
2016-09-13 18:41:14 +03:00
|
|
|
imagePathRe = new RegExp('^' + getSubdir() + '/' + config.get('paths').imagesRelPath);
|
2014-10-21 14:50:34 +04:00
|
|
|
absolute = imagePathRe.test(data.image) ? absolute : false;
|
|
|
|
secure = data.image.secure;
|
2014-11-17 06:38:10 +03:00
|
|
|
|
2014-12-02 17:24:51 +03:00
|
|
|
if (absolute) {
|
|
|
|
// Remove the sub-directory from the URL because ghostConfig will add it back.
|
2016-09-12 14:53:04 +03:00
|
|
|
urlPath = urlPath.replace(new RegExp('^' + getSubdir()), '');
|
2015-04-10 18:59:38 +03:00
|
|
|
baseUrl = getBaseUrl(secure).replace(/\/$/, '');
|
2014-12-02 17:24:51 +03:00
|
|
|
urlPath = baseUrl + urlPath;
|
|
|
|
}
|
|
|
|
|
|
|
|
return urlPath;
|
2015-01-28 08:57:19 +03:00
|
|
|
} else if (context === 'nav' && data.nav) {
|
|
|
|
urlPath = data.nav.url;
|
2016-01-05 21:04:39 +03:00
|
|
|
secure = data.nav.secure || secure;
|
2015-04-10 18:59:38 +03:00
|
|
|
baseUrl = getBaseUrl(secure);
|
2016-09-12 14:53:04 +03:00
|
|
|
hostname = baseUrl.split('//')[1] + getSubdir();
|
2016-09-13 18:41:14 +03:00
|
|
|
|
2015-07-18 04:45:06 +03:00
|
|
|
if (urlPath.indexOf(hostname) > -1
|
2016-07-21 16:05:13 +03:00
|
|
|
&& !urlPath.split(hostname)[0].match(/\.|mailto:/)
|
|
|
|
&& urlPath.split(hostname)[1].substring(0,1) !== ':') {
|
2015-01-28 08:57:19 +03:00
|
|
|
// make link relative to account for possible
|
|
|
|
// mismatch in http/https etc, force absolute
|
2015-03-20 06:00:32 +03:00
|
|
|
// do not do so if link is a subdomain of blog url
|
2016-06-09 01:16:42 +03:00
|
|
|
// or if hostname is inside of the slug
|
2016-07-21 16:05:13 +03:00
|
|
|
// or if slug is a port
|
2015-03-20 06:00:32 +03:00
|
|
|
urlPath = urlPath.split(hostname)[1];
|
|
|
|
if (urlPath.substring(0, 1) !== '/') {
|
|
|
|
urlPath = '/' + urlPath;
|
|
|
|
}
|
2015-01-28 08:57:19 +03:00
|
|
|
absolute = true;
|
|
|
|
}
|
2014-01-03 04:37:21 +04:00
|
|
|
}
|
|
|
|
// other objects are recognised but not yet supported
|
|
|
|
} else if (_.isString(context) && _.indexOf(_.keys(knownPaths), context) !== -1) {
|
|
|
|
// trying to create a url for a named path
|
|
|
|
urlPath = knownPaths[context] || '/';
|
|
|
|
}
|
|
|
|
|
2015-02-13 02:09:12 +03:00
|
|
|
// This url already has a protocol so is likely an external url to be returned
|
2015-09-26 00:31:54 +03:00
|
|
|
// or it is an alternative scheme, protocol-less, or an anchor-only path
|
|
|
|
if (urlPath && (urlPath.indexOf('://') !== -1 || urlPath.match(/^(\/\/|#|[a-zA-Z0-9\-]+:)/))) {
|
2015-02-13 02:09:12 +03:00
|
|
|
return urlPath;
|
|
|
|
}
|
|
|
|
|
2014-02-22 05:25:31 +04:00
|
|
|
return createUrl(urlPath, absolute, secure);
|
2014-01-03 04:37:21 +04:00
|
|
|
}
|
|
|
|
|
2016-09-19 16:41:50 +03:00
|
|
|
/**
|
|
|
|
* CASE: generate api url for CORS
|
|
|
|
* - we delete the http protocol if your blog runs with http and https (configured by nginx)
|
|
|
|
* - in that case your config.js configures Ghost with http and no admin ssl force
|
|
|
|
* - the browser then reads the protocol dynamically
|
|
|
|
*/
|
|
|
|
function apiUrl(options) {
|
|
|
|
options = options || {cors: false};
|
|
|
|
|
2015-12-15 13:41:53 +03:00
|
|
|
// @TODO unify this with urlFor
|
|
|
|
var url;
|
|
|
|
|
2016-09-13 18:41:14 +03:00
|
|
|
if (config.get('forceAdminSSL')) {
|
|
|
|
url = (config.get('urlSSL') || config.get('url')).replace(/^.*?:\/\//g, 'https://');
|
|
|
|
} else if (config.get('urlSSL')) {
|
|
|
|
url = config.get('urlSSL').replace(/^.*?:\/\//g, 'https://');
|
|
|
|
} else if (config.get('url').match(/^https:/)) {
|
|
|
|
url = config.get('url');
|
2015-12-15 13:41:53 +03:00
|
|
|
} else {
|
2016-09-19 16:41:50 +03:00
|
|
|
if (options.cors === false) {
|
2016-09-13 18:41:14 +03:00
|
|
|
url = config.get('url');
|
2016-09-19 16:41:50 +03:00
|
|
|
} else {
|
2016-09-13 18:41:14 +03:00
|
|
|
url = config.get('url').replace(/^.*?:\/\//g, '//');
|
2016-09-19 16:41:50 +03:00
|
|
|
}
|
2015-12-15 13:41:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return url.replace(/\/$/, '') + apiPath + '/';
|
|
|
|
}
|
|
|
|
|
2016-09-12 14:53:04 +03:00
|
|
|
module.exports.getProtectedSlugs = getProtectedSlugs;
|
|
|
|
module.exports.getSubdir = getSubdir;
|
2015-04-10 18:59:38 +03:00
|
|
|
module.exports.urlJoin = urlJoin;
|
2014-01-03 04:37:21 +04:00
|
|
|
module.exports.urlFor = urlFor;
|
2014-12-10 17:03:39 +03:00
|
|
|
module.exports.urlPathForPost = urlPathForPost;
|
2015-12-15 13:41:53 +03:00
|
|
|
module.exports.apiUrl = apiUrl;
|
2016-01-05 21:04:39 +03:00
|
|
|
module.exports.getBaseUrl = getBaseUrl;
|