├── .gitignore
├── core
├── shared
│ ├── robots.txt
│ ├── img
│ │ ├── user-cover.png
│ │ └── user-image.png
│ ├── favicon.ico
│ └── lib
│ │ └── showdown
│ │ └── extensions
│ │ └── ghostimagepreview.js
├── client
│ └── assets
│ │ ├── img
│ │ ├── large.png
│ │ ├── medium.png
│ │ ├── small.png
│ │ ├── 404-ghost.png
│ │ ├── loadingcat.gif
│ │ ├── 404-ghost@2x.png
│ │ ├── contributors
│ │ │ ├── wjake
│ │ │ ├── ErisDS
│ │ │ ├── halfdan
│ │ │ ├── hswolff
│ │ │ ├── jgable
│ │ │ ├── rwjblue
│ │ │ ├── sebgie
│ │ │ ├── JohnONolan
│ │ │ ├── SiR-DanieL
│ │ │ ├── cobbspur
│ │ │ ├── jaswilli
│ │ │ ├── javorszky
│ │ │ ├── jillesme
│ │ │ ├── morficus
│ │ │ ├── novaugust
│ │ │ ├── shindakun
│ │ │ ├── PaulAdamDavis
│ │ │ ├── felixrieseberg
│ │ │ ├── manuelmitasch
│ │ │ └── mattiascibien
│ │ ├── touch-icon-ipad.png
│ │ └── touch-icon-iphone.png
│ │ ├── fonts
│ │ ├── icons.eot
│ │ ├── icons.ttf
│ │ └── icons.woff
│ │ └── lib
│ │ └── touch-editor.js
├── server
│ ├── data
│ │ ├── import
│ │ │ ├── 001.js
│ │ │ ├── 002.js
│ │ │ └── 003.js
│ │ ├── utils
│ │ │ └── clients
│ │ │ │ ├── index.js
│ │ │ │ ├── sqlite3.js
│ │ │ │ ├── pg.js
│ │ │ │ └── mysql.js
│ │ ├── default-settings.json
│ │ ├── export
│ │ │ └── index.js
│ │ ├── versioning
│ │ │ └── index.js
│ │ ├── migration
│ │ │ └── commands.js
│ │ └── fixtures
│ │ │ └── permissions
│ │ │ └── index.js
│ ├── helpers
│ │ ├── tpl
│ │ │ ├── nav.hbs
│ │ │ └── pagination.hbs
│ │ ├── encode.js
│ │ ├── title.js
│ │ ├── utils.js
│ │ ├── ghost_script_tags.js
│ │ ├── is.js
│ │ ├── url.js
│ │ ├── date.js
│ │ ├── ghost_foot.js
│ │ ├── excerpt.js
│ │ ├── asset.js
│ │ ├── meta_description.js
│ │ ├── post_class.js
│ │ ├── plural.js
│ │ ├── content.js
│ │ ├── author.js
│ │ ├── meta_title.js
│ │ ├── page_url.js
│ │ ├── tags.js
│ │ ├── pagination.js
│ │ ├── has.js
│ │ ├── template.js
│ │ ├── foreach.js
│ │ └── body_class.js
│ ├── routes
│ │ ├── index.js
│ │ ├── admin.js
│ │ ├── frontend.js
│ │ └── api.js
│ ├── permissions
│ │ ├── object-type-model-map.js
│ │ └── effective.js
│ ├── utils
│ │ ├── sequence.js
│ │ ├── pipeline.js
│ │ └── index.js
│ ├── models
│ │ ├── client.js
│ │ ├── accesstoken.js
│ │ ├── refreshtoken.js
│ │ ├── app-field.js
│ │ ├── app-setting.js
│ │ ├── permission.js
│ │ ├── tag.js
│ │ ├── app.js
│ │ ├── index.js
│ │ ├── basetoken.js
│ │ └── role.js
│ ├── errors
│ │ ├── email-error.js
│ │ ├── not-found-error.js
│ │ ├── bad-request-error.js
│ │ ├── unauthorized-error.js
│ │ ├── no-permission-error.js
│ │ ├── internal-server-error.js
│ │ ├── unsupported-media-type-error.js
│ │ ├── request-too-large-error.js
│ │ ├── validation-error.js
│ │ └── data-import-error.js
│ ├── storage
│ │ ├── index.js
│ │ ├── base.js
│ │ └── local-file-store.js
│ ├── api
│ │ ├── tags.js
│ │ ├── utils.js
│ │ ├── upload.js
│ │ ├── slugs.js
│ │ ├── configuration.js
│ │ ├── roles.js
│ │ └── themes.js
│ ├── views
│ │ ├── error.hbs
│ │ ├── default.hbs
│ │ └── user-error.hbs
│ ├── apps
│ │ ├── dependencies.js
│ │ ├── permissions.js
│ │ ├── sandbox.js
│ │ ├── proxy.js
│ │ └── index.js
│ ├── controllers
│ │ └── admin.js
│ ├── middleware
│ │ ├── ghost-busboy.js
│ │ └── auth-strategies.js
│ ├── xmlrpc.js
│ ├── filters.js
│ └── email-templates
│ │ ├── reset-password.html
│ │ └── test.html
└── index.js
├── loaderio-cfd0e26d73d2c018ce430ea0e6b739e6.html
├── content
├── apps
│ └── README.md
├── plugins
│ └── README.md
├── themes
│ ├── casper
│ │ ├── package.json
│ │ ├── assets
│ │ │ ├── fonts
│ │ │ │ ├── icons.eot
│ │ │ │ ├── icons.ttf
│ │ │ │ ├── icons.woff
│ │ │ │ ├── casper-icons.eot
│ │ │ │ ├── casper-icons.ttf
│ │ │ │ ├── casper-icons.woff
│ │ │ │ └── icons.svg
│ │ │ └── js
│ │ │ │ ├── ja.js
│ │ │ │ ├── jquery.fitvids.js
│ │ │ │ └── index.js
│ │ ├── default_edits.hbs
│ │ ├── index_edits.hbs
│ │ ├── partials
│ │ │ └── loop.hbs
│ │ ├── tag.hbs
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── post_edits.hbs
│ │ ├── page.hbs
│ │ ├── author.hbs
│ │ ├── index.hbs
│ │ └── default.hbs
│ └── kevgriffin
│ │ ├── assets
│ │ ├── fonts
│ │ │ ├── icons.eot
│ │ │ ├── icons.ttf
│ │ │ └── icons.woff
│ │ └── js
│ │ │ ├── index.js
│ │ │ └── jquery.fitvids.js
│ │ ├── page.hbs
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── index.hbs
│ │ ├── default.hbs
│ │ └── post.hbs
├── data
│ ├── ghost.db
│ ├── ghost-dev.db
│ └── README.md
└── images
│ ├── 2014
│ ├── Oct
│ │ └── everleap-20-1--1-.png
│ └── Feb
│ │ ├── Kevin_in_Snow_with_Boys.jpeg
│ │ └── Kevin_in_Snow_with_Boys-1.jpeg
│ └── README.md
├── bower.json
├── npm-debug.log
├── index.js
├── LICENSE
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/core/shared/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /ghost/
--------------------------------------------------------------------------------
/loaderio-cfd0e26d73d2c018ce430ea0e6b739e6.html:
--------------------------------------------------------------------------------
1 | loaderio-cfd0e26d73d2c018ce430ea0e6b739e6
--------------------------------------------------------------------------------
/content/apps/README.md:
--------------------------------------------------------------------------------
1 | # Content / Apps
2 |
3 | Coming soon, Ghost apps will appear here.
--------------------------------------------------------------------------------
/content/plugins/README.md:
--------------------------------------------------------------------------------
1 | # Content / Plugins
2 |
3 | Coming soon, Ghost plugins will appear here.
--------------------------------------------------------------------------------
/content/themes/casper/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Casper",
3 | "version": "1.1.1"
4 | }
5 |
--------------------------------------------------------------------------------
/content/data/ghost.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/content/data/ghost.db
--------------------------------------------------------------------------------
/content/data/ghost-dev.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/content/data/ghost-dev.db
--------------------------------------------------------------------------------
/core/shared/img/user-cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/shared/img/user-cover.png
--------------------------------------------------------------------------------
/core/shared/img/user-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/shared/img/user-image.png
--------------------------------------------------------------------------------
/core/client/assets/img/large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/large.png
--------------------------------------------------------------------------------
/core/client/assets/img/medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/medium.png
--------------------------------------------------------------------------------
/core/client/assets/img/small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/small.png
--------------------------------------------------------------------------------
/content/images/README.md:
--------------------------------------------------------------------------------
1 | # Content / Images
2 |
3 | If using the standard file storage, Ghost will upload images to this directory.
--------------------------------------------------------------------------------
/core/client/assets/fonts/icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/fonts/icons.eot
--------------------------------------------------------------------------------
/core/client/assets/fonts/icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/fonts/icons.ttf
--------------------------------------------------------------------------------
/core/client/assets/fonts/icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/fonts/icons.woff
--------------------------------------------------------------------------------
/core/client/assets/img/404-ghost.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/404-ghost.png
--------------------------------------------------------------------------------
/core/client/assets/img/loadingcat.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/loadingcat.gif
--------------------------------------------------------------------------------
/core/client/assets/img/404-ghost@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/404-ghost@2x.png
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/wjake:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/wjake
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/ErisDS:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/ErisDS
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/halfdan:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/halfdan
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/hswolff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/hswolff
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/jgable:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/jgable
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/rwjblue:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/rwjblue
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/sebgie:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/sebgie
--------------------------------------------------------------------------------
/core/client/assets/img/touch-icon-ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/touch-icon-ipad.png
--------------------------------------------------------------------------------
/content/data/README.md:
--------------------------------------------------------------------------------
1 | # Content / Data
2 |
3 | This is the home of your Ghost database, do not overwrite this folder or any of the files inside of it.
--------------------------------------------------------------------------------
/content/images/2014/Oct/everleap-20-1--1-.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/content/images/2014/Oct/everleap-20-1--1-.png
--------------------------------------------------------------------------------
/content/themes/casper/assets/fonts/icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/content/themes/casper/assets/fonts/icons.eot
--------------------------------------------------------------------------------
/content/themes/casper/assets/fonts/icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/content/themes/casper/assets/fonts/icons.ttf
--------------------------------------------------------------------------------
/content/themes/casper/assets/fonts/icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/content/themes/casper/assets/fonts/icons.woff
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/JohnONolan:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/JohnONolan
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/SiR-DanieL:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/SiR-DanieL
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/cobbspur:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/cobbspur
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/jaswilli:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/jaswilli
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/javorszky:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/javorszky
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/jillesme:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/jillesme
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/morficus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/morficus
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/novaugust:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/novaugust
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/shindakun:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/shindakun
--------------------------------------------------------------------------------
/core/client/assets/img/touch-icon-iphone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/touch-icon-iphone.png
--------------------------------------------------------------------------------
/content/themes/kevgriffin/assets/fonts/icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/content/themes/kevgriffin/assets/fonts/icons.eot
--------------------------------------------------------------------------------
/content/themes/kevgriffin/assets/fonts/icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/content/themes/kevgriffin/assets/fonts/icons.ttf
--------------------------------------------------------------------------------
/content/themes/casper/assets/fonts/casper-icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/content/themes/casper/assets/fonts/casper-icons.eot
--------------------------------------------------------------------------------
/content/themes/casper/assets/fonts/casper-icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/content/themes/casper/assets/fonts/casper-icons.ttf
--------------------------------------------------------------------------------
/content/themes/kevgriffin/assets/fonts/icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/content/themes/kevgriffin/assets/fonts/icons.woff
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/PaulAdamDavis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/PaulAdamDavis
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/felixrieseberg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/felixrieseberg
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/manuelmitasch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/manuelmitasch
--------------------------------------------------------------------------------
/core/client/assets/img/contributors/mattiascibien:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/core/client/assets/img/contributors/mattiascibien
--------------------------------------------------------------------------------
/content/images/2014/Feb/Kevin_in_Snow_with_Boys.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/content/images/2014/Feb/Kevin_in_Snow_with_Boys.jpeg
--------------------------------------------------------------------------------
/content/themes/casper/assets/fonts/casper-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/content/themes/casper/assets/fonts/casper-icons.woff
--------------------------------------------------------------------------------
/content/images/2014/Feb/Kevin_in_Snow_with_Boys-1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/1kevgriff/kevgriffincom/master/content/images/2014/Feb/Kevin_in_Snow_with_Boys-1.jpeg
--------------------------------------------------------------------------------
/core/server/data/import/001.js:
--------------------------------------------------------------------------------
1 | var Importer000 = require('./000');
2 |
3 | module.exports = {
4 | Importer001: Importer000,
5 | importData: function (data) {
6 | return new Importer000.importData(data);
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/core/server/data/import/002.js:
--------------------------------------------------------------------------------
1 | var Importer000 = require('./000');
2 |
3 | module.exports = {
4 | Importer002: Importer000,
5 | importData: function (data) {
6 | return new Importer000.importData(data);
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/core/server/data/import/003.js:
--------------------------------------------------------------------------------
1 | var Importer000 = require('./000');
2 |
3 | module.exports = {
4 | Importer003: Importer000,
5 | importData: function (data) {
6 | return new Importer000.importData(data);
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/core/server/helpers/tpl/nav.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{#links}}
4 |
5 | {{/links}}
6 |
7 |
--------------------------------------------------------------------------------
/content/themes/casper/default_edits.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/core/server/data/utils/clients/index.js:
--------------------------------------------------------------------------------
1 | var sqlite3 = require('./sqlite3'),
2 | mysql = require('./mysql'),
3 | pg = require('./pg');
4 |
5 | module.exports = {
6 | sqlite3: sqlite3,
7 | mysql: mysql,
8 | pg: pg,
9 | postgres: pg,
10 | postgresql: pg
11 | };
12 |
--------------------------------------------------------------------------------
/core/server/routes/index.js:
--------------------------------------------------------------------------------
1 | var api = require('./api'),
2 | admin = require('./admin'),
3 | frontend = require('./frontend');
4 |
5 | module.exports = {
6 | apiBaseUri: '/ghost/api/v0.1/',
7 | api: api,
8 | admin: admin,
9 | frontend: frontend
10 | };
11 |
--------------------------------------------------------------------------------
/content/themes/kevgriffin/assets/js/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Main JS file for Casper behaviours
3 | */
4 |
5 | /*globals jQuery, document */
6 | (function ($) {
7 | "use strict";
8 |
9 | $(document).ready(function(){
10 |
11 | $(".post-content").fitVids();
12 |
13 | });
14 |
15 | }(jQuery));
--------------------------------------------------------------------------------
/core/server/permissions/object-type-model-map.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | post: require('../models/post').Post,
3 | role: require('../models/role').Role,
4 | user: require('../models/user').User,
5 | permission: require('../models/permission').Permission,
6 | setting: require('../models/settings').Settings
7 | };
8 |
--------------------------------------------------------------------------------
/core/server/routes/admin.js:
--------------------------------------------------------------------------------
1 | var admin = require('../controllers/admin'),
2 | express = require('express'),
3 |
4 | adminRoutes;
5 |
6 | adminRoutes = function () {
7 | var router = express.Router();
8 |
9 | router.get('*', admin.index);
10 |
11 | return router;
12 | };
13 |
14 | module.exports = adminRoutes;
15 |
--------------------------------------------------------------------------------
/core/server/utils/sequence.js:
--------------------------------------------------------------------------------
1 | var Promise = require('bluebird');
2 |
3 | function sequence(tasks) {
4 | return Promise.reduce(tasks, function (results, task) {
5 | return task().then(function (result) {
6 | results.push(result);
7 |
8 | return results;
9 | });
10 | }, []);
11 | }
12 |
13 | module.exports = sequence;
14 |
--------------------------------------------------------------------------------
/core/index.js:
--------------------------------------------------------------------------------
1 | // # Ghost bootloader
2 | // Orchestrates the loading of Ghost
3 | // When run from command line.
4 |
5 | var server = require('./server');
6 |
7 | process.env.NODE_ENV = process.env.NODE_ENV || 'development';
8 |
9 | function makeGhost(options) {
10 | options = options || {};
11 |
12 | return server(options);
13 | }
14 |
15 | module.exports = makeGhost;
16 |
--------------------------------------------------------------------------------
/core/server/helpers/tpl/pagination.hbs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/server/helpers/encode.js:
--------------------------------------------------------------------------------
1 | // # Encode Helper
2 | //
3 | // Usage: `{{encode uri}}`
4 | //
5 | // Returns URI encoded string
6 |
7 | var hbs = require('express-hbs'),
8 | encode;
9 |
10 | encode = function (context, str) {
11 | var uri = context || str;
12 | return new hbs.handlebars.SafeString(encodeURIComponent(uri));
13 | };
14 |
15 | module.exports = encode;
16 |
--------------------------------------------------------------------------------
/core/server/helpers/title.js:
--------------------------------------------------------------------------------
1 | // # Title Helper
2 | // Usage: `{{title}}`
3 | //
4 | // Overrides the standard behaviour of `{[title}}` to ensure the content is correctly escaped
5 |
6 | var hbs = require('express-hbs'),
7 | title;
8 |
9 | title = function () {
10 | return new hbs.handlebars.SafeString(hbs.handlebars.Utils.escapeExpression(this.title || ''));
11 | };
12 |
13 | module.exports = title;
14 |
--------------------------------------------------------------------------------
/core/server/models/client.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 |
3 | Client,
4 | Clients;
5 |
6 | Client = ghostBookshelf.Model.extend({
7 | tableName: 'clients'
8 | });
9 |
10 | Clients = ghostBookshelf.Collection.extend({
11 | model: Client
12 | });
13 |
14 | module.exports = {
15 | Client: ghostBookshelf.model('Client', Client),
16 | Clients: ghostBookshelf.collection('Clients', Clients)
17 | };
18 |
--------------------------------------------------------------------------------
/core/server/errors/email-error.js:
--------------------------------------------------------------------------------
1 | // # Email error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function EmailError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 500;
8 | this.type = this.name;
9 | }
10 |
11 | EmailError.prototype = Object.create(Error.prototype);
12 | EmailError.prototype.name = 'EmailError';
13 |
14 | module.exports = EmailError;
15 |
--------------------------------------------------------------------------------
/core/server/errors/not-found-error.js:
--------------------------------------------------------------------------------
1 | // # Not found error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function NotFoundError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 404;
8 | this.type = this.name;
9 | }
10 |
11 | NotFoundError.prototype = Object.create(Error.prototype);
12 | NotFoundError.prototype.name = 'NotFoundError';
13 |
14 | module.exports = NotFoundError;
15 |
--------------------------------------------------------------------------------
/core/server/errors/bad-request-error.js:
--------------------------------------------------------------------------------
1 | // # Bad request error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function BadRequestError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 400;
8 | this.type = this.name;
9 | }
10 |
11 | BadRequestError.prototype = Object.create(Error.prototype);
12 | BadRequestError.prototype.name = 'BadRequestError';
13 |
14 | module.exports = BadRequestError;
15 |
--------------------------------------------------------------------------------
/core/server/errors/unauthorized-error.js:
--------------------------------------------------------------------------------
1 | // # Unauthorized error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function UnauthorizedError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 401;
8 | this.type = this.name;
9 | }
10 |
11 | UnauthorizedError.prototype = Object.create(Error.prototype);
12 | UnauthorizedError.prototype.name = 'UnauthorizedError';
13 |
14 | module.exports = UnauthorizedError;
15 |
--------------------------------------------------------------------------------
/core/server/errors/no-permission-error.js:
--------------------------------------------------------------------------------
1 | // # No Permission Error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function NoPermissionError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 403;
8 | this.type = this.name;
9 | }
10 |
11 | NoPermissionError.prototype = Object.create(Error.prototype);
12 | NoPermissionError.prototype.name = 'NoPermissionError';
13 |
14 | module.exports = NoPermissionError;
15 |
--------------------------------------------------------------------------------
/core/server/errors/internal-server-error.js:
--------------------------------------------------------------------------------
1 | // # Internal Server Error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function InternalServerError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 500;
8 | this.type = this.name;
9 | }
10 |
11 | InternalServerError.prototype = Object.create(Error.prototype);
12 | InternalServerError.prototype.name = 'InternalServerError';
13 |
14 | module.exports = InternalServerError;
15 |
--------------------------------------------------------------------------------
/core/server/errors/unsupported-media-type-error.js:
--------------------------------------------------------------------------------
1 | // # Unsupported Media Type
2 | // Custom error class with status code and type prefilled.
3 |
4 | function UnsupportedMediaTypeError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 415;
8 | this.type = this.name;
9 | }
10 |
11 | UnsupportedMediaTypeError.prototype = Object.create(Error.prototype);
12 | UnsupportedMediaTypeError.prototype.name = 'UnsupportedMediaTypeError';
13 |
14 | module.exports = UnsupportedMediaTypeError;
15 |
--------------------------------------------------------------------------------
/core/server/models/accesstoken.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 | Basetoken = require('./basetoken'),
3 |
4 | Accesstoken,
5 | Accesstokens;
6 |
7 | Accesstoken = Basetoken.extend({
8 | tableName: 'accesstokens'
9 | });
10 |
11 | Accesstokens = ghostBookshelf.Collection.extend({
12 | model: Accesstoken
13 | });
14 |
15 | module.exports = {
16 | Accesstoken: ghostBookshelf.model('Accesstoken', Accesstoken),
17 | Accesstokens: ghostBookshelf.collection('Accesstokens', Accesstokens)
18 | };
19 |
--------------------------------------------------------------------------------
/core/server/errors/request-too-large-error.js:
--------------------------------------------------------------------------------
1 | // # Request Entity Too Large Error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function RequestEntityTooLargeError(message) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 413;
8 | this.type = this.name;
9 | }
10 |
11 | RequestEntityTooLargeError.prototype = Object.create(Error.prototype);
12 | RequestEntityTooLargeError.prototype.name = 'RequestEntityTooLargeError';
13 |
14 | module.exports = RequestEntityTooLargeError;
15 |
--------------------------------------------------------------------------------
/core/server/models/refreshtoken.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 | Basetoken = require('./basetoken'),
3 |
4 | Refreshtoken,
5 | Refreshtokens;
6 |
7 | Refreshtoken = Basetoken.extend({
8 | tableName: 'refreshtokens'
9 | });
10 |
11 | Refreshtokens = ghostBookshelf.Collection.extend({
12 | model: Refreshtoken
13 | });
14 |
15 | module.exports = {
16 | Refreshtoken: ghostBookshelf.model('Refreshtoken', Refreshtoken),
17 | Refreshtokens: ghostBookshelf.collection('Refreshtokens', Refreshtokens)
18 | };
19 |
--------------------------------------------------------------------------------
/core/server/models/app-field.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 | AppField,
3 | AppFields;
4 |
5 | AppField = ghostBookshelf.Model.extend({
6 | tableName: 'app_fields',
7 |
8 | post: function () {
9 | return this.morphOne('Post', 'relatable');
10 | }
11 | });
12 |
13 | AppFields = ghostBookshelf.Collection.extend({
14 | model: AppField
15 | });
16 |
17 | module.exports = {
18 | AppField: ghostBookshelf.model('AppField', AppField),
19 | AppFields: ghostBookshelf.collection('AppFields', AppFields)
20 | };
21 |
--------------------------------------------------------------------------------
/core/server/models/app-setting.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 | AppSetting,
3 | AppSettings;
4 |
5 | AppSetting = ghostBookshelf.Model.extend({
6 | tableName: 'app_settings',
7 |
8 | app: function () {
9 | return this.belongsTo('App');
10 | }
11 | });
12 |
13 | AppSettings = ghostBookshelf.Collection.extend({
14 | model: AppSetting
15 | });
16 |
17 | module.exports = {
18 | AppSetting: ghostBookshelf.model('AppSetting', AppSetting),
19 | AppSettings: ghostBookshelf.collection('AppSettings', AppSettings)
20 | };
21 |
--------------------------------------------------------------------------------
/core/server/errors/validation-error.js:
--------------------------------------------------------------------------------
1 | // # Validation Error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function ValidationError(message, offendingProperty) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 422;
8 | if (offendingProperty) {
9 | this.property = offendingProperty;
10 | }
11 | this.type = this.name;
12 | }
13 |
14 | ValidationError.prototype = Object.create(Error.prototype);
15 | ValidationError.prototype.name = 'ValidationError';
16 |
17 | module.exports = ValidationError;
18 |
--------------------------------------------------------------------------------
/core/server/errors/data-import-error.js:
--------------------------------------------------------------------------------
1 | // # Data import error
2 | // Custom error class with status code and type prefilled.
3 |
4 | function DataImportError(message, offendingProperty, value) {
5 | this.message = message;
6 | this.stack = new Error().stack;
7 | this.code = 500;
8 | this.type = this.name;
9 | this.property = offendingProperty || undefined;
10 | this.value = value || undefined;
11 | }
12 |
13 | DataImportError.prototype = Object.create(Error.prototype);
14 | DataImportError.prototype.name = 'DataImportError';
15 |
16 | module.exports = DataImportError;
17 |
--------------------------------------------------------------------------------
/content/themes/casper/assets/js/ja.js:
--------------------------------------------------------------------------------
1 | (function (i, s, o, g, r, a, m) {
2 | i['GoogleAnalyticsObject'] = r;
3 | i[r] = i[r] || function () {
4 | (i[r].q = i[r].q || []).push(arguments)
5 | }, i[r].l = 1 * new Date();
6 | a = s.createElement(o),
7 | m = s.getElementsByTagName(o)[0];
8 | a.async = 1;
9 | a.src = g;
10 | m.parentNode.insertBefore(a, m)
11 | })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
12 |
13 | ga('create', 'UA-28954757-1', 'auto');
14 | ga('send', 'pageview');
--------------------------------------------------------------------------------
/content/themes/casper/index_edits.hbs:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ghost",
3 | "dependencies": {
4 | "backbone": "1.0.0",
5 | "codemirror": "4.0.1",
6 | "Countable": "2.0.2",
7 | "fastclick": "1.0.0",
8 | "ghost-ui": "0.1.3",
9 | "handlebars": "1.3.0",
10 | "jquery": "1.11.0",
11 | "jquery-file-upload": "9.5.6",
12 | "jquery-hammerjs": "1.0.1",
13 | "jquery-ui": "1.10.4",
14 | "lodash": "2.4.1",
15 | "moment": "2.4.0",
16 | "nprogress": "0.1.2",
17 | "showdown": "https://github.com/ErisDS/showdown.git#v0.3.2-ghost",
18 | "validator-js": "3.4.0"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/core/server/utils/pipeline.js:
--------------------------------------------------------------------------------
1 | var Promise = require('bluebird');
2 |
3 | function pipeline(tasks /* initial arguments */) {
4 | var args = Array.prototype.slice.call(arguments, 1),
5 |
6 | runTask = function (task, args) {
7 | runTask = function (task, arg) {
8 | return task(arg);
9 | };
10 |
11 | return task.apply(null, args);
12 | };
13 |
14 | return Promise.all(tasks).reduce(function (arg, task) {
15 | return Promise.resolve(runTask(task, arg)).then(function (result) {
16 | return result;
17 | });
18 | }, args);
19 | }
20 |
21 | module.exports = pipeline;
22 |
--------------------------------------------------------------------------------
/core/server/helpers/utils.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | utils;
3 |
4 | utils = {
5 | assetTemplate: _.template('<%= source %>?v=<%= version %>'),
6 | linkTemplate: _.template('<%= text %> '),
7 | scriptTemplate: _.template(''),
8 | isProduction: process.env.NODE_ENV === 'production',
9 | scriptFiles: {
10 | production: [
11 | 'vendor.min.js',
12 | 'ghost.min.js'
13 | ],
14 | development: [
15 | 'vendor-dev.js',
16 | 'templates-dev.js',
17 | 'ghost-dev.js'
18 | ]
19 | }
20 | };
21 |
22 | module.exports = utils;
23 |
--------------------------------------------------------------------------------
/core/server/models/permission.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 |
3 | Permission,
4 | Permissions;
5 |
6 | Permission = ghostBookshelf.Model.extend({
7 |
8 | tableName: 'permissions',
9 |
10 | roles: function () {
11 | return this.belongsToMany('Role');
12 | },
13 |
14 | users: function () {
15 | return this.belongsToMany('User');
16 | },
17 |
18 | apps: function () {
19 | return this.belongsToMany('App');
20 | }
21 | });
22 |
23 | Permissions = ghostBookshelf.Collection.extend({
24 | model: Permission
25 | });
26 |
27 | module.exports = {
28 | Permission: ghostBookshelf.model('Permission', Permission),
29 | Permissions: ghostBookshelf.collection('Permissions', Permissions)
30 | };
31 |
--------------------------------------------------------------------------------
/npm-debug.log:
--------------------------------------------------------------------------------
1 | 0 info it worked if it ends with ok
2 | 1 verbose cli [ '/usr/bin/node', '/usr/bin/npm', 'start', 'production' ]
3 | 2 info using npm@1.4.28
4 | 3 info using node@v0.10.32
5 | 4 verbose node symlink /usr/bin/node
6 | 5 error Error: ENOENT, open '/home/kevin/ghost/node_modules/production/package.json'
7 | 6 error If you need help, you may report this *entire* log,
8 | 6 error including the npm and node versions, at:
9 | 6 error
10 | 7 error System Linux 3.13.0-36-generic
11 | 8 error command "/usr/bin/node" "/usr/bin/npm" "start" "production"
12 | 9 error cwd /home/kevin/ghost
13 | 10 error node -v v0.10.32
14 | 11 error npm -v 1.4.28
15 | 12 error path /home/kevin/ghost/node_modules/production/package.json
16 | 13 error code ENOENT
17 | 14 error errno 34
18 | 15 verbose exit [ 34, true ]
19 |
--------------------------------------------------------------------------------
/core/server/storage/index.js:
--------------------------------------------------------------------------------
1 | var errors = require('../errors'),
2 | storage = {};
3 |
4 | function getStorage(storageChoice) {
5 | // TODO: this is where the check for storage apps should go
6 | // Local file system is the default. Fow now that is all we support.
7 | storageChoice = 'local-file-store';
8 |
9 | if (storage[storageChoice]) {
10 | return storage[storageChoice];
11 | }
12 |
13 | try {
14 | // TODO: determine if storage has all the necessary methods.
15 | storage[storageChoice] = require('./' + storageChoice);
16 | } catch (e) {
17 | errors.logError(e);
18 | }
19 |
20 | // Instantiate and cache the storage module instance.
21 | storage[storageChoice] = new storage[storageChoice]();
22 |
23 | return storage[storageChoice];
24 | }
25 |
26 | module.exports.getStorage = getStorage;
27 |
--------------------------------------------------------------------------------
/core/server/helpers/ghost_script_tags.js:
--------------------------------------------------------------------------------
1 | // # Ghost Script Tags Helpers
2 | // Used in the ghost admin only
3 | //
4 | // We use the name ghost_script_tags to match the helper for consistency:
5 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
6 |
7 | var _ = require('lodash'),
8 | utils = require('./utils'),
9 | config = require('../config'),
10 | ghost_script_tags;
11 |
12 | ghost_script_tags = function () {
13 | var scriptList = utils.isProduction ? utils.scriptFiles.production : utils.scriptFiles.development;
14 |
15 | scriptList = _.map(scriptList, function (fileName) {
16 | return utils.scriptTemplate({
17 | source: config.paths.subdir + '/ghost/scripts/' + fileName,
18 | version: config.assetHash
19 | });
20 | });
21 |
22 | return scriptList.join('');
23 | };
24 |
25 | module.exports = ghost_script_tags;
26 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // # Ghost bootloader
2 | // Orchestrates the loading of Ghost
3 | // When run from command line.
4 |
5 | var express,
6 | ghost,
7 | parentApp,
8 | errors;
9 |
10 | // Make sure dependencies are installed and file system permissions are correct.
11 | require('./core/server/utils/startup-check').check();
12 |
13 | // Proceed with startup
14 | express = require('express');
15 | ghost = require('./core');
16 | errors = require('./core/server/errors');
17 |
18 | // Create our parent express app instance.
19 | parentApp = express();
20 |
21 | ghost().then(function (ghostServer) {
22 | // Mount our ghost instance on our desired subdirectory path if it exists.
23 | parentApp.use(ghostServer.config.paths.subdir, ghostServer.rootApp);
24 |
25 | // Let ghost handle starting our server instance.
26 | ghostServer.start(parentApp);
27 | }).catch(function (err) {
28 | errors.logErrorAndExit(err, err.context, err.help);
29 | });
30 |
--------------------------------------------------------------------------------
/core/server/helpers/is.js:
--------------------------------------------------------------------------------
1 | // # Is Helper
2 | // Usage: `{{#is "paged"}}`, `{{#is "index, paged"}}`
3 | // Checks whether we're in a given context.
4 | var _ = require('lodash'),
5 | errors = require('../errors'),
6 | is;
7 |
8 | is = function (context, options) {
9 | options = options || {};
10 |
11 | var currentContext = options.data.root.context;
12 |
13 | if (!_.isString(context)) {
14 | errors.logWarn('Invalid or no attribute given to is helper');
15 | return;
16 | }
17 |
18 | function evaluateContext(expr) {
19 | return expr.split(',').map(function (v) {
20 | return v.trim();
21 | }).reduce(function (p, c) {
22 | return p || _.contains(currentContext, c);
23 | }, false);
24 | }
25 |
26 | if (evaluateContext(context)) {
27 | return options.fn(this);
28 | }
29 | return options.inverse(this);
30 | };
31 |
32 | module.exports = is;
33 |
--------------------------------------------------------------------------------
/content/themes/kevgriffin/page.hbs:
--------------------------------------------------------------------------------
1 | {{!< default}}
2 |
3 | {{! This is a page template. A page outputs content just like any other post, and has all the same
4 | attributes by default, but you can also customise it to behave differently if you prefer. }}
5 |
6 |
7 |
8 |
9 |
10 |
19 |
20 | {{#post}}
21 |
22 | {{{title}}}
23 |
24 |
27 |
28 | {{/post}}
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/core/server/api/tags.js:
--------------------------------------------------------------------------------
1 | // # Tag API
2 | // RESTful API for the Tag resource
3 | var Promise = require('bluebird'),
4 | canThis = require('../permissions').canThis,
5 | dataProvider = require('../models'),
6 | errors = require('../errors'),
7 | tags;
8 |
9 | /**
10 | * ## Tags API Methods
11 | *
12 | * **See:** [API Methods](index.js.html#api%20methods)
13 | */
14 | tags = {
15 | /**
16 | * ### Browse
17 | * @param {{context}} options
18 | * @returns {Promise(Tags)}
19 | */
20 | browse: function browse(options) {
21 | return canThis(options.context).browse.tag().then(function () {
22 | return dataProvider.Tag.findAll(options).then(function (result) {
23 | return {tags: result.toJSON()};
24 | });
25 | }, function () {
26 | return Promise.reject(new errors.NoPermissionError('You do not have permission to browse tags.'));
27 | });
28 | }
29 | };
30 |
31 | module.exports = tags;
32 |
--------------------------------------------------------------------------------
/content/themes/casper/partials/loop.hbs:
--------------------------------------------------------------------------------
1 | {{! Previous/next page links - only displayed on page 2+ }}
2 |
5 |
6 | {{! This is the post loop - each post will be output using this markup }}
7 | {{#foreach posts}}
8 |
9 |
12 |
13 | {{excerpt words="26"}} »
14 |
15 |
16 | {{#if author.image}} {{/if}}
17 | {{author}}
18 | {{tags prefix=" on "}}
19 | {{date format="DD MMMM YYYY"}}
20 |
21 |
22 | {{/foreach}}
23 |
24 | {{! Previous/next page links - displayed on every page }}
25 | {{pagination}}
26 |
--------------------------------------------------------------------------------
/content/themes/casper/tag.hbs:
--------------------------------------------------------------------------------
1 | {{!< default}}
2 | {{! The tag above means - insert everything in this file into the {body} of the default.hbs template }}
3 |
4 | {{! The big featured header }}
5 |
6 |
7 | Home
8 | {{tag.name}}
9 |
10 |
11 |
12 |
{{tag.name}}
13 | A {{pagination.total}}-post collection
14 |
15 |
16 |
17 |
18 | {{! The main content area on the homepage }}
19 |
20 |
21 | {{! The tag below includes the post loop - partials/loop.hbs }}
22 | {{> "loop"}}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/core/server/helpers/url.js:
--------------------------------------------------------------------------------
1 | // # URL helper
2 | // Usage: `{{url}}`, `{{url absolute="true"}}`
3 | //
4 | // Returns the URL for the current object scope i.e. If inside a post scope will return post permalink
5 | // `absolute` flag outputs absolute URL, else URL is relative
6 |
7 | var Promise = require('bluebird'),
8 | config = require('../config'),
9 | api = require('../api'),
10 | schema = require('../data/schema').checks,
11 | url;
12 |
13 | url = function (options) {
14 | var absolute = options && options.hash.absolute;
15 |
16 | if (schema.isPost(this)) {
17 | return config.urlForPost(api.settings, this, absolute);
18 | }
19 |
20 | if (schema.isTag(this)) {
21 | return Promise.resolve(config.urlFor('tag', {tag: this}, absolute));
22 | }
23 |
24 | if (schema.isUser(this)) {
25 | return Promise.resolve(config.urlFor('author', {author: this}, absolute));
26 | }
27 |
28 | return Promise.resolve(config.urlFor(this, absolute));
29 | };
30 |
31 | module.exports = url;
32 |
--------------------------------------------------------------------------------
/core/shared/favicon.ico:
--------------------------------------------------------------------------------
1 | ( ( 0, @0, @0, @0, @ 0, @0, @0, @0, @ 0, @0, @0, @0, @ 0, @0, @0, @0, @ 0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @ 0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @ 0, @0, @0, @0, @0, @0, @ 0, @0, @ 0, @0, @0, @0, @0, @0, @ 0, @0, @
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013-2014 Ghost Foundation - Released under The MIT License.
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/core/server/helpers/date.js:
--------------------------------------------------------------------------------
1 | // # Date Helper
2 | // Usage: `{{date format="DD MM, YYYY"}}`, `{{date updated_at format="DD MM, YYYY"}}`
3 | //
4 | // Formats a date using moment.js. Formats published_at by default but will also take a date as a parameter
5 |
6 | var moment = require('moment'),
7 | date;
8 |
9 | date = function (context, options) {
10 | if (!options && context.hasOwnProperty('hash')) {
11 | options = context;
12 | context = undefined;
13 |
14 | // set to published_at by default, if it's available
15 | // otherwise, this will print the current date
16 | if (this.published_at) {
17 | context = this.published_at;
18 | }
19 | }
20 |
21 | // ensure that context is undefined, not null, as that can cause errors
22 | context = context === null ? undefined : context;
23 |
24 | var f = options.hash.format || 'MMM Do, YYYY',
25 | timeago = options.hash.timeago,
26 | date;
27 |
28 | if (timeago) {
29 | date = moment(context).fromNow();
30 | } else {
31 | date = moment(context).format(f);
32 | }
33 | return date;
34 | };
35 |
36 | module.exports = date;
37 |
--------------------------------------------------------------------------------
/content/themes/kevgriffin/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Ghost Foundation - Released under The MIT License.
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/content/themes/casper/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013-2014 Ghost Foundation - Released under The MIT License.
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/core/server/helpers/ghost_foot.js:
--------------------------------------------------------------------------------
1 | // # Ghost Foot Helper
2 | // Usage: `{{ghost_foot}}`
3 | //
4 | // Outputs scripts and other assets at the bottom of a Ghost theme
5 | //
6 | // We use the name ghost_foot to match the helper for consistency:
7 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
8 |
9 | var hbs = require('express-hbs'),
10 | _ = require('lodash'),
11 | config = require('../config'),
12 | filters = require('../filters'),
13 | utils = require('./utils'),
14 | ghost_foot;
15 |
16 | ghost_foot = function (options) {
17 | /*jshint unused:false*/
18 | var jquery = utils.isProduction ? 'jquery.min.js' : 'jquery.js',
19 | foot = [];
20 |
21 | foot.push(utils.scriptTemplate({
22 | source: config.paths.subdir + '/public/' + jquery,
23 | version: config.assetHash
24 | }));
25 |
26 | return filters.doFilter('ghost_foot', foot).then(function (foot) {
27 | var footString = _.reduce(foot, function (memo, item) { return memo + '\n' + item; }, '\n');
28 | return new hbs.handlebars.SafeString(footString.trim());
29 | });
30 | };
31 |
32 | module.exports = ghost_foot;
33 |
--------------------------------------------------------------------------------
/core/server/views/error.hbs:
--------------------------------------------------------------------------------
1 | {{!< default}}
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 | {{#if stack}}
15 |
16 | Stack Trace
17 | {{message}}
18 |
19 | {{#foreach stack}}
20 |
21 | at
22 | {{#if function}}{{function}} {{/if}}
23 | ({{at}})
24 |
25 | {{/foreach}}
26 |
27 |
28 | {{/if}}
29 |
--------------------------------------------------------------------------------
/content/themes/kevgriffin/README.md:
--------------------------------------------------------------------------------
1 | # Casper
2 |
3 | The default theme for [Ghost](http://github.com/tryghost/ghost/).
4 |
5 | ## Copyright & License
6 |
7 | Copyright (C) 2014 Ghost Foundation - Released under the MIT License.
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
14 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 |
--------------------------------------------------------------------------------
/core/server/helpers/excerpt.js:
--------------------------------------------------------------------------------
1 | // # Excerpt Helper
2 | // Usage: `{{excerpt}}`, `{{excerpt words="50"}}`, `{{excerpt characters="256"}}`
3 | //
4 | // Attempts to remove all HTML from the string, and then shortens the result according to the provided option.
5 | //
6 | // Defaults to words="50"
7 |
8 | var hbs = require('express-hbs'),
9 | _ = require('lodash'),
10 | downsize = require('downsize'),
11 | excerpt;
12 |
13 | excerpt = function (options) {
14 | var truncateOptions = (options || {}).hash || {},
15 | excerpt;
16 |
17 | truncateOptions = _.pick(truncateOptions, ['words', 'characters']);
18 | _.keys(truncateOptions).map(function (key) {
19 | truncateOptions[key] = parseInt(truncateOptions[key], 10);
20 | });
21 |
22 | /*jslint regexp:true */
23 | excerpt = String(this.html).replace(/<\/?[^>]+>/gi, '');
24 | excerpt = excerpt.replace(/(\r\n|\n|\r)+/gm, ' ');
25 | /*jslint regexp:false */
26 |
27 | if (!truncateOptions.words && !truncateOptions.characters) {
28 | truncateOptions.words = 50;
29 | }
30 |
31 | return new hbs.handlebars.SafeString(
32 | downsize(excerpt, truncateOptions)
33 | );
34 | };
35 |
36 | module.exports = excerpt;
37 |
--------------------------------------------------------------------------------
/core/server/data/utils/clients/sqlite3.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | config = require('../../../config/index'),
3 |
4 | // private
5 | doRaw,
6 |
7 | // public
8 | getTables,
9 | getIndexes,
10 | getColumns;
11 |
12 | doRaw = function doRaw(query, fn) {
13 | return config.database.knex.raw(query).then(function (response) {
14 | return fn(response);
15 | });
16 | };
17 |
18 | getTables = function getTables() {
19 | return doRaw('select * from sqlite_master where type = "table"', function (response) {
20 | return _.reject(_.pluck(response, 'tbl_name'), function (name) {
21 | return name === 'sqlite_sequence';
22 | });
23 | });
24 | };
25 |
26 | getIndexes = function getIndexes(table) {
27 | return doRaw('pragma index_list("' + table + '")', function (response) {
28 | return _.flatten(_.pluck(response, 'name'));
29 | });
30 | };
31 |
32 | getColumns = function getColumns(table) {
33 | return doRaw('pragma table_info("' + table + '")', function (response) {
34 | return _.flatten(_.pluck(response, 'name'));
35 | });
36 | };
37 |
38 | module.exports = {
39 | getTables: getTables,
40 | getIndexes: getIndexes,
41 | getColumns: getColumns
42 | };
43 |
--------------------------------------------------------------------------------
/core/server/helpers/asset.js:
--------------------------------------------------------------------------------
1 | // # Asset helper
2 | // Usage: `{{asset "css/screen.css"}}`, `{{asset "css/screen.css" ghost="true"}}`
3 | //
4 | // Returns the path to the specified asset. The ghost flag outputs the asset path for the Ghost admin
5 |
6 | var hbs = require('express-hbs'),
7 | config = require('../config'),
8 | utils = require('./utils'),
9 | asset;
10 |
11 | asset = function (context, options) {
12 | var output = '',
13 | isAdmin = options && options.hash && options.hash.ghost;
14 |
15 | output += config.paths.subdir + '/';
16 |
17 | if (!context.match(/^favicon\.ico$/) && !context.match(/^shared/) && !context.match(/^asset/)) {
18 | if (isAdmin) {
19 | output += 'ghost/';
20 | } else {
21 | output += 'assets/';
22 | }
23 | }
24 |
25 | // Get rid of any leading slash on the context
26 | context = context.replace(/^\//, '');
27 | output += context;
28 |
29 | if (!context.match(/^favicon\.ico$/)) {
30 | output = utils.assetTemplate({
31 | source: output,
32 | version: config.assetHash
33 | });
34 | }
35 |
36 | return new hbs.handlebars.SafeString(output);
37 | };
38 |
39 | module.exports = asset;
40 |
--------------------------------------------------------------------------------
/core/server/api/utils.js:
--------------------------------------------------------------------------------
1 | // # API Utils
2 | // Shared helpers for working with the API
3 | var Promise = require('bluebird'),
4 | _ = require('lodash'),
5 | errors = require('../errors'),
6 | utils;
7 |
8 | utils = {
9 | /**
10 | * ### Check Object
11 | * Check an object passed to the API is in the correct format
12 | *
13 | * @param {Object} object
14 | * @param {String} docName
15 | * @returns {Promise(Object)} resolves to the original object if it checks out
16 | */
17 | checkObject: function (object, docName) {
18 | if (_.isEmpty(object) || _.isEmpty(object[docName]) || _.isEmpty(object[docName][0])) {
19 | return errors.logAndRejectError(new errors.BadRequestError('No root key (\'' + docName + '\') provided.'));
20 | }
21 |
22 | // convert author property to author_id to match the name in the database
23 | // TODO: rename object in database
24 | if (docName === 'posts') {
25 | if (object.posts[0].hasOwnProperty('author')) {
26 | object.posts[0].author_id = object.posts[0].author;
27 | delete object.posts[0].author;
28 | }
29 | }
30 | return Promise.resolve(object);
31 | }
32 | };
33 |
34 | module.exports = utils;
35 |
--------------------------------------------------------------------------------
/content/themes/casper/README.md:
--------------------------------------------------------------------------------
1 | # Casper
2 |
3 | The default theme for [Ghost](http://github.com/tryghost/ghost/).
4 |
5 | To download, visit the [releases](https://github.com/TryGhost/Casper/releases) page.
6 |
7 | ## Copyright & License
8 |
9 | Copyright (c) 2013-2014 Ghost Foundation - Released under the MIT License.
10 |
11 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17 |
--------------------------------------------------------------------------------
/core/server/storage/base.js:
--------------------------------------------------------------------------------
1 | var moment = require('moment'),
2 | path = require('path');
3 |
4 | function StorageBase() {
5 | }
6 |
7 | StorageBase.prototype.getTargetDir = function (baseDir) {
8 | var m = moment(new Date().getTime()),
9 | month = m.format('MM'),
10 | year = m.format('YYYY');
11 |
12 | if (baseDir) {
13 | return path.join(baseDir, year, month);
14 | }
15 |
16 | return path.join(year, month);
17 | };
18 |
19 | StorageBase.prototype.generateUnique = function (store, dir, name, ext, i) {
20 | var self = this,
21 | filename,
22 | append = '';
23 |
24 | if (i) {
25 | append = '-' + i;
26 | }
27 |
28 | filename = path.join(dir, name + append + ext);
29 |
30 | return store.exists(filename).then(function (exists) {
31 | if (exists) {
32 | i = i + 1;
33 | return self.generateUnique(store, dir, name, ext, i);
34 | } else {
35 | return filename;
36 | }
37 | });
38 | };
39 |
40 | StorageBase.prototype.getUniqueFileName = function (store, image, targetDir) {
41 | var ext = path.extname(image.name),
42 | name = path.basename(image.name, ext).replace(/[\W]/gi, '-'),
43 | self = this;
44 |
45 | return self.generateUnique(store, targetDir, name, ext, 0);
46 | };
47 |
48 | module.exports = StorageBase;
49 |
--------------------------------------------------------------------------------
/content/themes/casper/post_edits.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
27 | Please enable JavaScript to view the comments powered by Disqus.
28 |
29 | comments powered by
30 |
--------------------------------------------------------------------------------
/core/server/helpers/meta_description.js:
--------------------------------------------------------------------------------
1 | // # Meta Description Helper
2 | // Usage: `{{meta_description}}`
3 | //
4 | // Page description used for sharing and SEO
5 | //
6 | // We use the name meta_description to match the helper for consistency:
7 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
8 |
9 | var _ = require('lodash'),
10 | config = require('../config'),
11 | filters = require('../filters'),
12 | meta_description;
13 |
14 | meta_description = function () {
15 | var description,
16 | blog;
17 |
18 | if (_.isString(this.relativeUrl)) {
19 | blog = config.theme;
20 | if (!this.relativeUrl || this.relativeUrl === '/' || this.relativeUrl === '') {
21 | description = blog.description;
22 | } else if (this.author) {
23 | description = /\/page\//.test(this.relativeUrl) ? '' : this.author.bio;
24 | } else if (this.tag || /\/page\//.test(this.relativeUrl)) {
25 | description = '';
26 | } else if (this.post) {
27 | description = _.isEmpty(this.post.meta_description) ? '' : this.post.meta_description;
28 | }
29 | }
30 |
31 | return filters.doFilter('meta_description', description).then(function (description) {
32 | description = description || '';
33 | return description.trim();
34 | });
35 | };
36 |
37 | module.exports = meta_description;
38 |
--------------------------------------------------------------------------------
/core/server/helpers/post_class.js:
--------------------------------------------------------------------------------
1 | // # Post Class Helper
2 | // Usage: `{{post_class}}`
3 | //
4 | // Output classes for the body element
5 | //
6 | // We use the name body_class to match the helper for consistency:
7 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
8 |
9 | var hbs = require('express-hbs'),
10 | _ = require('lodash'),
11 | filters = require('../filters'),
12 | post_class;
13 |
14 | post_class = function (options) {
15 | /*jshint unused:false*/
16 | var classes = ['post'],
17 | tags = this.post && this.post.tags ? this.post.tags : this.tags || [],
18 | featured = this.post && this.post.featured ? this.post.featured : this.featured || false,
19 | page = this.post && this.post.page ? this.post.page : this.page || false;
20 |
21 | if (tags) {
22 | classes = classes.concat(tags.map(function (tag) { return 'tag-' + tag.slug; }));
23 | }
24 |
25 | if (featured) {
26 | classes.push('featured');
27 | }
28 |
29 | if (page) {
30 | classes.push('page');
31 | }
32 |
33 | return filters.doFilter('post_class', classes).then(function (classes) {
34 | var classString = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, '');
35 | return new hbs.handlebars.SafeString(classString.trim());
36 | });
37 | };
38 |
39 | module.exports = post_class;
40 |
--------------------------------------------------------------------------------
/core/server/helpers/plural.js:
--------------------------------------------------------------------------------
1 | // # Plural Helper
2 | // Usage: `{{plural 0 empty='No posts' singular='% post' plural='% posts'}}`
3 | //
4 | // pluralises strings depending on item count
5 | //
6 | // The 1st argument is the numeric variable which the helper operates on
7 | // The 2nd argument is the string that will be output if the variable's value is 0
8 | // The 3rd argument is the string that will be output if the variable's value is 1
9 | // The 4th argument is the string that will be output if the variable's value is 2+
10 |
11 | var hbs = require('express-hbs'),
12 | errors = require('../errors'),
13 | _ = require('lodash'),
14 | plural;
15 |
16 | plural = function (context, options) {
17 | if (_.isUndefined(options.hash) || _.isUndefined(options.hash.empty) ||
18 | _.isUndefined(options.hash.singular) || _.isUndefined(options.hash.plural)) {
19 | return errors.logAndThrowError('All values must be defined for empty, singular and plural');
20 | }
21 |
22 | if (context === 0) {
23 | return new hbs.handlebars.SafeString(options.hash.empty);
24 | } else if (context === 1) {
25 | return new hbs.handlebars.SafeString(options.hash.singular.replace('%', context));
26 | } else if (context >= 2) {
27 | return new hbs.handlebars.SafeString(options.hash.plural.replace('%', context));
28 | }
29 | };
30 |
31 | module.exports = plural;
32 |
--------------------------------------------------------------------------------
/core/server/models/tag.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 |
3 | Tag,
4 | Tags;
5 |
6 | Tag = ghostBookshelf.Model.extend({
7 |
8 | tableName: 'tags',
9 |
10 | saving: function (newPage, attr, options) {
11 | /*jshint unused:false*/
12 |
13 | var self = this;
14 |
15 | ghostBookshelf.Model.prototype.saving.apply(this, arguments);
16 |
17 | if (this.hasChanged('slug') || !this.get('slug')) {
18 | // Pass the new slug through the generator to strip illegal characters, detect duplicates
19 | return ghostBookshelf.Model.generateSlug(Tag, this.get('slug') || this.get('name'),
20 | {transacting: options.transacting})
21 | .then(function (slug) {
22 | self.set({slug: slug});
23 | });
24 | }
25 | },
26 |
27 | posts: function () {
28 | return this.belongsToMany('Post');
29 | },
30 |
31 | toJSON: function (options) {
32 | var attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options);
33 |
34 | attrs.parent = attrs.parent || attrs.parent_id;
35 | delete attrs.parent_id;
36 |
37 | return attrs;
38 | }
39 | });
40 |
41 | Tags = ghostBookshelf.Collection.extend({
42 | model: Tag
43 | });
44 |
45 | module.exports = {
46 | Tag: ghostBookshelf.model('Tag', Tag),
47 | Tags: ghostBookshelf.collection('Tags', Tags)
48 | };
49 |
--------------------------------------------------------------------------------
/content/themes/casper/page.hbs:
--------------------------------------------------------------------------------
1 | {{!< default}}
2 | {{! This is a page template. A page outputs content just like any other post, and has all the same
3 | attributes by default, but you can also customise it to behave differently if you prefer. }}
4 |
5 | Home
6 | Subscribe
7 |
8 |
9 |
10 |
11 |
12 |
27 |
28 | {{! Everything inside the #post tags pulls data from the post }}
29 | {{#post}}
30 |
31 | {{title}}
32 |
33 |
36 |
37 | {{/post}}
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/core/server/helpers/content.js:
--------------------------------------------------------------------------------
1 | // # Content Helper
2 | // Usage: `{{content}}`, `{{content words="20"}}`, `{{content characters="256"}}`
3 | //
4 | // Turns content html into a safestring so that the user doesn't have to
5 | // escape it or tell handlebars to leave it alone with a triple-brace.
6 | //
7 | // Enables tag-safe truncation of content by characters or words.
8 |
9 | var hbs = require('express-hbs'),
10 | _ = require('lodash'),
11 | downsize = require('downsize'),
12 | downzero = require('../utils/downzero'),
13 | content;
14 |
15 | content = function (options) {
16 | var truncateOptions = (options || {}).hash || {};
17 | truncateOptions = _.pick(truncateOptions, ['words', 'characters']);
18 | _.keys(truncateOptions).map(function (key) {
19 | truncateOptions[key] = parseInt(truncateOptions[key], 10);
20 | });
21 |
22 | if (truncateOptions.hasOwnProperty('words') || truncateOptions.hasOwnProperty('characters')) {
23 | // Legacy function: {{content words="0"}} should return leading tags.
24 | if (truncateOptions.hasOwnProperty('words') && truncateOptions.words === 0) {
25 | return new hbs.handlebars.SafeString(
26 | downzero(this.html)
27 | );
28 | }
29 |
30 | return new hbs.handlebars.SafeString(
31 | downsize(this.html, truncateOptions)
32 | );
33 | }
34 |
35 | return new hbs.handlebars.SafeString(this.html);
36 | };
37 |
38 | module.exports = content;
39 |
--------------------------------------------------------------------------------
/core/server/helpers/author.js:
--------------------------------------------------------------------------------
1 | // # Author Helper
2 | // Usage: `{{author}}` OR `{{#author}}{{/author}}`
3 | //
4 | // Can be used as either an output or a block helper
5 | //
6 | // Output helper: `{{author}}`
7 | // Returns the full name of the author of a given post, or a blank string
8 | // if the author could not be determined.
9 | //
10 | // Block helper: `{{#author}}{{/author}}`
11 | // This is the default handlebars behaviour of dropping into the author object scope
12 |
13 | var hbs = require('express-hbs'),
14 | _ = require('lodash'),
15 | config = require('../config'),
16 | utils = require('./utils'),
17 | author;
18 |
19 | author = function (context, options) {
20 | if (_.isUndefined(options)) {
21 | options = context;
22 | }
23 |
24 | if (options.fn) {
25 | return hbs.handlebars.helpers['with'].call(this, this.author, options);
26 | }
27 |
28 | var autolink = _.isString(options.hash.autolink) && options.hash.autolink === 'false' ? false : true,
29 | output = '';
30 |
31 | if (this.author && this.author.name) {
32 | if (autolink) {
33 | output = utils.linkTemplate({
34 | url: config.urlFor('author', {author: this.author}),
35 | text: _.escape(this.author.name)
36 | });
37 | } else {
38 | output = _.escape(this.author.name);
39 | }
40 | }
41 |
42 | return new hbs.handlebars.SafeString(output);
43 | };
44 |
45 | module.exports = author;
46 |
--------------------------------------------------------------------------------
/core/server/data/utils/clients/pg.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | config = require('../../../config/index'),
3 |
4 | // private
5 | doRawFlattenAndPluck,
6 |
7 | // public
8 | getTables,
9 | getIndexes,
10 | getColumns;
11 |
12 | doRawFlattenAndPluck = function doRaw(query, name) {
13 | return config.database.knex.raw(query).then(function (response) {
14 | return _.flatten(_.pluck(response.rows, name));
15 | });
16 | };
17 |
18 | getTables = function getTables() {
19 | return doRawFlattenAndPluck(
20 | 'SELECT table_name FROM information_schema.tables WHERE table_schema = \'public\' and table_name not like \'pg_%\'', 'table_name'
21 | );
22 | };
23 |
24 | getIndexes = function getIndexes(table) {
25 | var selectIndexes = 'SELECT t.relname as table_name, i.relname as index_name, a.attname as column_name' +
26 | ' FROM pg_class t, pg_class i, pg_index ix, pg_attribute a' +
27 | ' WHERE t.oid = ix.indrelid and i.oid = ix.indexrelid and' +
28 | ' a.attrelid = t.oid and a.attnum = ANY(ix.indkey) and t.relname = \'' + table + '\'';
29 |
30 | return doRawFlattenAndPluck(selectIndexes, 'index_name');
31 | };
32 |
33 | getColumns = function getColumns(table) {
34 | var selectIndexes = 'SELECT column_name FROM information_schema.columns WHERE table_name = \'' + table + '\'';
35 |
36 | return doRawFlattenAndPluck(selectIndexes, 'column_name');
37 | };
38 |
39 | module.exports = {
40 | getTables: getTables,
41 | getIndexes: getIndexes,
42 | getColumns: getColumns
43 | };
44 |
--------------------------------------------------------------------------------
/content/themes/kevgriffin/index.hbs:
--------------------------------------------------------------------------------
1 | {{!< default}}
2 |
3 | {{! The comment above "< default" means - insert everything in this file into
4 | the {body} of the default.hbs template, which contains our header/footer. }}
5 |
6 | {{! The big featured header on the homepage, with the site logo and description }}
7 |
8 |
9 |
10 | {{#if @blog.logo}}
{{/if}}
11 |
{{@blog.title}}
12 |
{{@blog.description}}
13 |
14 |
15 |
16 |
17 | {{! The main content area on the homepage }}
18 |
19 |
20 | {{! Each post will be output using this markup }}
21 | {{#foreach posts}}
22 |
23 |
24 |
29 |
32 |
33 |
34 | {{/foreach}}
35 |
36 | {{!! After all the posts, we have the previous/next pagination links }}
37 | {{pagination}}
38 |
39 |
--------------------------------------------------------------------------------
/core/server/helpers/meta_title.js:
--------------------------------------------------------------------------------
1 | // # Meta Title Helper
2 | // Usage: `{{meta_title}}`
3 | //
4 | // Page title used for sharing and SEO
5 | //
6 | // We use the name meta_title to match the helper for consistency:
7 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
8 |
9 | var _ = require('lodash'),
10 | config = require('../config'),
11 | filters = require('../filters'),
12 | meta_title;
13 |
14 | meta_title = function (options) {
15 | /*jshint unused:false*/
16 | var title = '',
17 | blog,
18 | page,
19 | pageString = '';
20 |
21 | if (_.isString(this.relativeUrl)) {
22 | blog = config.theme;
23 |
24 | page = this.relativeUrl.match(/\/page\/(\d+)/);
25 |
26 | if (page) {
27 | pageString = ' - Page ' + page[1];
28 | }
29 |
30 | if (!this.relativeUrl || this.relativeUrl === '/' || this.relativeUrl === '') {
31 | title = blog.title;
32 | } else if (this.author) {
33 | title = this.author.name + pageString + ' - ' + blog.title;
34 | } else if (this.tag) {
35 | title = this.tag.name + pageString + ' - ' + blog.title;
36 | } else if (this.post) {
37 | title = _.isEmpty(this.post.meta_title) ? this.post.title : this.post.meta_title;
38 | } else {
39 | title = blog.title + pageString;
40 | }
41 | }
42 | return filters.doFilter('meta_title', title).then(function (title) {
43 | title = title || '';
44 | return title.trim();
45 | });
46 | };
47 |
48 | module.exports = meta_title;
49 |
--------------------------------------------------------------------------------
/core/client/assets/lib/touch-editor.js:
--------------------------------------------------------------------------------
1 | var createTouchEditor = function createTouchEditor() {
2 | var noop = function () {},
3 | TouchEditor;
4 |
5 | TouchEditor = function (el, options) {
6 | /*jshint unused:false*/
7 | this.textarea = el;
8 | this.win = { document : this.textarea };
9 | this.ready = true;
10 | this.wrapping = document.createElement('div');
11 |
12 | var textareaParent = this.textarea.parentNode;
13 | this.wrapping.appendChild(this.textarea);
14 | textareaParent.appendChild(this.wrapping);
15 |
16 | this.textarea.style.opacity = 1;
17 | };
18 |
19 | TouchEditor.prototype = {
20 | setOption: function (type, handler) {
21 | if (type === 'onChange') {
22 | $(this.textarea).change(handler);
23 | }
24 | },
25 | eachLine: function () {
26 | return [];
27 | },
28 | getValue: function () {
29 | return this.textarea.value;
30 | },
31 | setValue: function (code) {
32 | this.textarea.value = code;
33 | },
34 | focus: noop,
35 | getCursor: function () {
36 | return { line: 0, ch: 0 };
37 | },
38 | setCursor: noop,
39 | currentLine: function () {
40 | return 0;
41 | },
42 | cursorPosition: function () {
43 | return { character: 0 };
44 | },
45 | addMarkdown: noop,
46 | nthLine: noop,
47 | refresh: noop,
48 | selectLines: noop,
49 | on: noop,
50 | off: noop
51 | };
52 |
53 | return TouchEditor;
54 | };
55 |
56 | export default createTouchEditor;
57 |
--------------------------------------------------------------------------------
/content/themes/casper/author.hbs:
--------------------------------------------------------------------------------
1 | {{!< default}}
2 | {{! The tag above means - insert everything in this file into the {body} of the default.hbs template }}
3 |
4 | {{! The big featured header }}
5 |
6 | {{! Everything inside the #author tags pulls data from the author }}
7 | {{#author}}
8 |
9 |
10 | Home
11 | {{name}}
12 |
13 |
14 |
15 |
16 | {{#if image}}
17 |
18 | {{name}}'s Picture
19 |
20 | {{/if}}
21 | {{name}}
22 | {{bio}}
23 |
28 |
29 | {{/author}}
30 |
31 | {{! The main content area on the homepage }}
32 |
33 |
34 | {{! The tag below includes the post loop - partials/loop.hbs }}
35 | {{> "loop"}}
36 |
37 |
38 |
--------------------------------------------------------------------------------
/core/server/helpers/page_url.js:
--------------------------------------------------------------------------------
1 | // ### Page URL Helper
2 | //
3 | // *Usage example:*
4 | // `{{page_url 2}}`
5 | //
6 | // Returns the URL for the page specified in the current object
7 | // context.
8 | //
9 | // We use the name page_url to match the helper for consistency:
10 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
11 |
12 | var config = require('../config'),
13 | errors = require('../errors'),
14 | page_url,
15 | pageUrl;
16 |
17 | page_url = function (context, block) {
18 | /*jshint unused:false*/
19 | var url = config.paths.subdir;
20 |
21 | if (this.tagSlug !== undefined) {
22 | url += '/tag/' + this.tagSlug;
23 | }
24 |
25 | if (this.authorSlug !== undefined) {
26 | url += '/author/' + this.authorSlug;
27 | }
28 |
29 | if (context > 1) {
30 | url += '/page/' + context;
31 | }
32 |
33 | url += '/';
34 |
35 | return url;
36 | };
37 |
38 | // ### Page URL Helper: DEPRECATED
39 | //
40 | // *Usage example:*
41 | // `{{pageUrl 2}}`
42 | //
43 | // Returns the URL for the page specified in the current object
44 | // context. This helper is deprecated and will be removed in future versions.
45 | //
46 | pageUrl = function (context, block) {
47 | errors.logWarn('Warning: pageUrl is deprecated, please use page_url instead\n' +
48 | 'The helper pageUrl has been replaced with page_url in Ghost 0.4.2, and will be removed entirely in Ghost 0.6\n' +
49 | 'In your theme\'s pagination.hbs file, pageUrl should be renamed to page_url');
50 |
51 | /*jshint unused:false*/
52 | var self = this;
53 |
54 | return page_url.call(self, context, block);
55 | };
56 |
57 | module.exports = page_url;
58 | module.exports.deprecated = pageUrl;
59 |
--------------------------------------------------------------------------------
/core/server/helpers/tags.js:
--------------------------------------------------------------------------------
1 | // # Tags Helper
2 | // Usage: `{{tags}}`, `{{tags separator=' - '}}`
3 | //
4 | // Returns a string of the tags on the post.
5 | // By default, tags are separated by commas.
6 | //
7 | // Note that the standard {{#each tags}} implementation is unaffected by this helper
8 |
9 | var hbs = require('express-hbs'),
10 | _ = require('lodash'),
11 | config = require('../config'),
12 | utils = require('./utils'),
13 | tags;
14 |
15 | tags = function (options) {
16 | options = options || {};
17 | options.hash = options.hash || {};
18 |
19 | var autolink = options.hash && _.isString(options.hash.autolink) && options.hash.autolink === 'false' ? false : true,
20 | separator = options.hash && _.isString(options.hash.separator) ? options.hash.separator : ', ',
21 | prefix = options.hash && _.isString(options.hash.prefix) ? options.hash.prefix : '',
22 | suffix = options.hash && _.isString(options.hash.suffix) ? options.hash.suffix : '',
23 | output = '';
24 |
25 | function createTagList(tags) {
26 | var tagNames = _.pluck(tags, 'name');
27 |
28 | if (autolink) {
29 | return _.map(tags, function (tag) {
30 | return utils.linkTemplate({
31 | url: config.urlFor('tag', {tag: tag}),
32 | text: _.escape(tag.name)
33 | });
34 | }).join(separator);
35 | }
36 | return _.escape(tagNames.join(separator));
37 | }
38 |
39 | if (this.tags && this.tags.length) {
40 | output = prefix + createTagList(this.tags) + suffix;
41 | }
42 |
43 | return new hbs.handlebars.SafeString(output);
44 | };
45 |
46 | module.exports = tags;
47 |
--------------------------------------------------------------------------------
/core/server/apps/dependencies.js:
--------------------------------------------------------------------------------
1 |
2 | var _ = require('lodash'),
3 | fs = require('fs'),
4 | path = require('path'),
5 | Promise = require('bluebird'),
6 | spawn = require('child_process').spawn,
7 | win32 = process.platform === 'win32';
8 |
9 | function AppDependencies(appPath) {
10 | this.appPath = appPath;
11 | }
12 |
13 | AppDependencies.prototype.install = function installAppDependencies() {
14 | var spawnOpts,
15 | self = this;
16 |
17 | return new Promise(function (resolve, reject) {
18 | fs.exists(path.join(self.appPath, 'package.json'), function (exists) {
19 | if (!exists) {
20 | // Nothing to do, resolve right away?
21 | resolve();
22 | } else {
23 | // Run npm install in the app directory
24 | spawnOpts = {
25 | cwd: self.appPath
26 | };
27 |
28 | self.spawnCommand('npm', ['install', '--production'], spawnOpts)
29 | .on('error', reject)
30 | .on('exit', function (err) {
31 | if (err) {
32 | reject(err);
33 | }
34 |
35 | resolve();
36 | });
37 | }
38 | });
39 | });
40 | };
41 |
42 | // Normalize a command across OS and spawn it; taken from yeoman/generator
43 | AppDependencies.prototype.spawnCommand = function (command, args, opt) {
44 | var winCommand = win32 ? 'cmd' : command,
45 | winArgs = win32 ? ['/c'].concat(command, args) : args;
46 |
47 | opt = opt || {};
48 |
49 | return spawn(winCommand, winArgs, _.defaults({stdio: 'inherit'}, opt));
50 | };
51 |
52 | module.exports = AppDependencies;
53 |
--------------------------------------------------------------------------------
/core/server/helpers/pagination.js:
--------------------------------------------------------------------------------
1 | // ### Pagination Helper
2 | // `{{pagination}}`
3 | // Outputs previous and next buttons, along with info about the current page
4 |
5 | var _ = require('lodash'),
6 | errors = require('../errors'),
7 | template = require('./template'),
8 | pagination;
9 |
10 | pagination = function (options) {
11 | /*jshint unused:false*/
12 | if (!_.isObject(this.pagination) || _.isFunction(this.pagination)) {
13 | return errors.logAndThrowError('pagination data is not an object or is a function');
14 | }
15 |
16 | if (_.isUndefined(this.pagination.page) || _.isUndefined(this.pagination.pages) ||
17 | _.isUndefined(this.pagination.total) || _.isUndefined(this.pagination.limit)) {
18 | return errors.logAndThrowError('All values must be defined for page, pages, limit and total');
19 | }
20 |
21 | if ((!_.isNull(this.pagination.next) && !_.isNumber(this.pagination.next)) ||
22 | (!_.isNull(this.pagination.prev) && !_.isNumber(this.pagination.prev))) {
23 | return errors.logAndThrowError('Invalid value, Next/Prev must be a number');
24 | }
25 |
26 | if (!_.isNumber(this.pagination.page) || !_.isNumber(this.pagination.pages) ||
27 | !_.isNumber(this.pagination.total) || !_.isNumber(this.pagination.limit)) {
28 | return errors.logAndThrowError('Invalid value, check page, pages, limit and total are numbers');
29 | }
30 |
31 | var context = _.merge({}, this.pagination);
32 |
33 | if (this.tag !== undefined) {
34 | context.tagSlug = this.tag.slug;
35 | }
36 |
37 | if (this.author !== undefined) {
38 | context.authorSlug = this.author.slug;
39 | }
40 |
41 | return template.execute('pagination', context);
42 | };
43 |
44 | module.exports = pagination;
45 |
--------------------------------------------------------------------------------
/content/themes/casper/index.hbs:
--------------------------------------------------------------------------------
1 | {{!< default}}
2 | {{! The tag above means - insert everything in this file into the {body} of the default.hbs template }}
3 | {{! The big featured header }}
4 |
5 | {{#if @blog.logo}}
6 |
7 |
8 | {{/if}}
9 | Subscribe
10 |
11 |
12 |
13 |
{{@blog.title}}
14 | {{@blog.description}}
15 |
16 |
17 | Scroll Down
18 |
19 |
20 | {{! The main content area on the homepage }}
21 |
22 |
23 |
38 |
39 | {{! The tag below includes the post loop - partials/loop.hbs }}
40 | {{> "loop"}}
41 |
42 |
--------------------------------------------------------------------------------
/core/server/controllers/admin.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | api = require('../api'),
3 | errors = require('../errors'),
4 | updateCheck = require('../update-check'),
5 | config = require('../config'),
6 | adminControllers;
7 |
8 | adminControllers = {
9 | // Route: index
10 | // Path: /ghost/
11 | // Method: GET
12 | index: function (req, res) {
13 | /*jslint unparam:true*/
14 |
15 | function renderIndex() {
16 | res.render('default', {
17 | skip_google_fonts: config.isPrivacyDisabled('useGoogleFonts')
18 | });
19 | }
20 |
21 | updateCheck().then(function () {
22 | return updateCheck.showUpdateNotification();
23 | }).then(function (updateVersion) {
24 | if (!updateVersion) {
25 | return;
26 | }
27 |
28 | var notification = {
29 | type: 'success',
30 | location: 'top',
31 | dismissible: false,
32 | status: 'persistent',
33 | message: 'Ghost ' + updateVersion +
34 | ' is available! Hot Damn. Please upgrade now'
35 | };
36 |
37 | return api.notifications.browse({context: {internal: true}}).then(function (results) {
38 | if (!_.some(results.notifications, {message: notification.message})) {
39 | return api.notifications.add({notifications: [notification]}, {context: {internal: true}});
40 | }
41 | });
42 | }).finally(function () {
43 | renderIndex();
44 | }).catch(errors.logError);
45 | }
46 | };
47 |
48 | module.exports = adminControllers;
49 |
--------------------------------------------------------------------------------
/content/themes/casper/default.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{! Document Settings }}
5 |
6 |
7 |
8 | {{! Page Meta }}
9 | {{meta_title}}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{! Styles'n'Scripts }}
18 |
19 |
20 |
21 | {{! Ghost outputs important style and meta data with this tag }}
22 | {{ghost_head}}
23 |
24 |
25 |
26 | {{! Everything else gets inserted here }}
27 | {{{body}}}
28 |
29 |
33 |
34 | {{! Ghost outputs important scripts and data with this tag }}
35 | {{ghost_foot}}
36 |
37 | {{! The main JavaScript file for Casper }}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/core/server/helpers/has.js:
--------------------------------------------------------------------------------
1 | // # Has Helper
2 | // Usage: `{{#has tag="video, music"}}`, `{{#has author="sam, pat"}}`
3 | //
4 | // Checks if a post has a particular property
5 |
6 | var _ = require('lodash'),
7 | errors = require('../errors'),
8 | has;
9 |
10 | has = function (options) {
11 | options = options || {};
12 | options.hash = options.hash || {};
13 |
14 | var tags = _.pluck(this.tags, 'name'),
15 | author = this.author ? this.author.name : null,
16 | tagList = options.hash.tag || false,
17 | authorList = options.hash.author || false,
18 | tagsOk,
19 | authorOk;
20 |
21 | function evaluateTagList(expr, tags) {
22 | return expr.split(',').map(function (v) {
23 | return v.trim();
24 | }).reduce(function (p, c) {
25 | return p || (_.findIndex(tags, function (item) {
26 | // Escape regex special characters
27 | item = item.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, '\\$&');
28 | item = new RegExp(item, 'i');
29 | return item.test(c);
30 | }) !== -1);
31 | }, false);
32 | }
33 |
34 | function evaluateAuthorList(expr, author) {
35 | var authorList = expr.split(',').map(function (v) {
36 | return v.trim().toLocaleLowerCase();
37 | });
38 |
39 | return _.contains(authorList, author.toLocaleLowerCase());
40 | }
41 |
42 | if (!tagList && !authorList) {
43 | errors.logWarn('Invalid or no attribute given to has helper');
44 | return;
45 | }
46 |
47 | tagsOk = tagList && evaluateTagList(tagList, tags) || false;
48 | authorOk = authorList && evaluateAuthorList(authorList, author) || false;
49 |
50 | if (tagsOk || authorOk) {
51 | return options.fn(this);
52 | }
53 | return options.inverse(this);
54 | };
55 |
56 | module.exports = has;
57 |
--------------------------------------------------------------------------------
/core/server/permissions/effective.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | Models = require('../models'),
3 | errors = require('../errors'),
4 | effective;
5 |
6 | effective = {
7 | user: function (id) {
8 | return Models.User.findOne({id: id, status: 'all'}, {include: ['permissions', 'roles', 'roles.permissions']})
9 | .then(function (foundUser) {
10 | var seenPerms = {},
11 | rolePerms = _.map(foundUser.related('roles').models, function (role) {
12 | return role.related('permissions').models;
13 | }),
14 | allPerms = [],
15 | user = foundUser.toJSON();
16 |
17 | rolePerms.push(foundUser.related('permissions').models);
18 |
19 | _.each(rolePerms, function (rolePermGroup) {
20 | _.each(rolePermGroup, function (perm) {
21 | var key = perm.get('action_type') + '-' + perm.get('object_type') + '-' + perm.get('object_id');
22 |
23 | // Only add perms once
24 | if (seenPerms[key]) {
25 | return;
26 | }
27 |
28 | allPerms.push(perm);
29 | seenPerms[key] = true;
30 | });
31 | });
32 |
33 | return {permissions: allPerms, roles: user.roles};
34 | }, errors.logAndThrowError);
35 | },
36 |
37 | app: function (appName) {
38 | return Models.App.findOne({name: appName}, {withRelated: ['permissions']})
39 | .then(function (foundApp) {
40 | if (!foundApp) {
41 | return [];
42 | }
43 |
44 | return {permissions: foundApp.related('permissions').models};
45 | }, errors.logAndThrowError);
46 | }
47 | };
48 |
49 | module.exports = effective;
50 |
--------------------------------------------------------------------------------
/content/themes/kevgriffin/default.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{! Document Settings }}
5 |
6 |
7 |
8 | {{! Page Meta }}
9 | {{meta_title}}
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | {{! Styles'n'Scripts }}
19 |
20 |
21 |
22 | {{! Ghost outputs important style and meta data with this tag }}
23 | {{ghost_head}}
24 |
25 |
26 |
27 | {{! Everything else gets inserted here }}
28 | {{{body}}}
29 |
30 |
37 |
38 | {{! Ghost outputs important scripts and data with this tag }}
39 | {{ghost_foot}}
40 |
41 | {{! The main JavaScript file for Casper }}
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/core/server/api/upload.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | config = require('../config'),
3 | Promise = require('bluebird'),
4 | path = require('path'),
5 | fs = require('fs-extra'),
6 | storage = require('../storage'),
7 | errors = require('../errors'),
8 |
9 | upload;
10 |
11 | function isImage(type, ext) {
12 | if (_.contains(config.uploads.contentTypes, type) && _.contains(config.uploads.extensions, ext)) {
13 | return true;
14 | }
15 | return false;
16 | }
17 |
18 | /**
19 | * ## Upload API Methods
20 | *
21 | * **See:** [API Methods](index.js.html#api%20methods)
22 | */
23 | upload = {
24 |
25 | /**
26 | * ### Add Image
27 | *
28 | * @public
29 | * @param {{context}} options
30 | * @returns {Promise} Success
31 | */
32 | add: function (options) {
33 | var store = storage.getStorage(),
34 | type,
35 | ext,
36 | filepath;
37 |
38 | if (!options.uploadimage || !options.uploadimage.type || !options.uploadimage.path) {
39 | return Promise.reject(new errors.NoPermissionError('Please select an image.'));
40 | }
41 |
42 | type = options.uploadimage.type;
43 | ext = path.extname(options.uploadimage.name).toLowerCase();
44 | filepath = options.uploadimage.path;
45 |
46 | return Promise.resolve(isImage(type, ext)).then(function (result) {
47 | if (!result) {
48 | return Promise.reject(new errors.UnsupportedMediaTypeError('Please select a valid image.'));
49 | }
50 | }).then(function () {
51 | return store.save(options.uploadimage);
52 | }).then(function (url) {
53 | return url;
54 | }).finally(function () {
55 | // Remove uploaded file from tmp location
56 | return Promise.promisify(fs.unlink)(filepath);
57 | });
58 | }
59 | };
60 |
61 | module.exports = upload;
62 |
--------------------------------------------------------------------------------
/core/server/utils/index.js:
--------------------------------------------------------------------------------
1 | var unidecode = require('unidecode'),
2 |
3 | utils,
4 | getRandomInt;
5 |
6 | /**
7 | * Return a random int, used by `utils.uid()`
8 | *
9 | * @param {Number} min
10 | * @param {Number} max
11 | * @return {Number}
12 | * @api private
13 | */
14 | getRandomInt = function (min, max) {
15 | return Math.floor(Math.random() * (max - min + 1)) + min;
16 | };
17 |
18 | utils = {
19 | /**
20 | * Timespans in seconds and milliseconds for better readability
21 | */
22 | ONE_HOUR_S: 3600,
23 | ONE_DAY_S: 86400,
24 | ONE_YEAR_S: 31536000,
25 | ONE_HOUR_MS: 3600000,
26 | ONE_DAY_MS: 86400000,
27 | ONE_YEAR_MS: 31536000000,
28 |
29 | /**
30 | * Return a unique identifier with the given `len`.
31 | *
32 | * utils.uid(10);
33 | * // => "FDaS435D2z"
34 | *
35 | * @param {Number} len
36 | * @return {String}
37 | * @api private
38 | */
39 | uid: function (len) {
40 | var buf = [],
41 | chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
42 | charlen = chars.length,
43 | i;
44 |
45 | for (i = 1; i < len; i = i + 1) {
46 | buf.push(chars[getRandomInt(0, charlen - 1)]);
47 | }
48 |
49 | return buf.join('');
50 | },
51 | safeString: function (string) {
52 | string = string.trim();
53 |
54 | // Remove non ascii characters
55 | string = unidecode(string);
56 |
57 | // Remove URL reserved chars: `:/?#[]@!$&'()*+,;=` as well as `\%<>|^~£"`
58 | string = string.replace(/[:\/\?#\[\]@!$&'()*+,;=\\%<>\|\^~£"]/g, '')
59 | // Replace dots and spaces with a dash
60 | .replace(/(\s|\.)/g, '-')
61 | // Convert 2 or more dashes into a single dash
62 | .replace(/-+/g, '-')
63 | // Make the whole thing lowercase
64 | .toLowerCase();
65 |
66 | return string;
67 | }
68 | };
69 |
70 | module.exports = utils;
71 |
--------------------------------------------------------------------------------
/core/server/data/utils/clients/mysql.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | config = require('../../../config/index'),
3 |
4 | // private
5 | doRawAndFlatten,
6 |
7 | // public
8 | getTables,
9 | getIndexes,
10 | getColumns,
11 | checkPostTable;
12 |
13 | doRawAndFlatten = function doRaw(query, flattenFn) {
14 | return config.database.knex.raw(query).then(function (response) {
15 | return _.flatten(flattenFn(response));
16 | });
17 | };
18 |
19 | getTables = function getTables() {
20 | return doRawAndFlatten('show tables', function (response) {
21 | return _.map(response[0], function (entry) { return _.values(entry); });
22 | });
23 | };
24 |
25 | getIndexes = function getIndexes(table) {
26 | return doRawAndFlatten('SHOW INDEXES from ' + table, function (response) {
27 | return _.pluck(response[0], 'Key_name');
28 | });
29 | };
30 |
31 | getColumns = function getColumns(table) {
32 | return doRawAndFlatten('SHOW COLUMNS FROM ' + table, function (response) {
33 | return _.pluck(response[0], 'Field');
34 | });
35 | };
36 |
37 | // This function changes the type of posts.html and posts.markdown columns to mediumtext. Due to
38 | // a wrong datatype in schema.js some installations using mysql could have been created using the
39 | // data type text instead of mediumtext.
40 | // For details see: https://github.com/TryGhost/Ghost/issues/1947
41 | checkPostTable = function checkPostTable() {
42 | return config.database.knex.raw('SHOW FIELDS FROM posts where Field ="html" OR Field = "markdown"').then(function (response) {
43 | return _.flatten(_.map(response[0], function (entry) {
44 | if (entry.Type.toLowerCase() !== 'mediumtext') {
45 | return config.database.knex.raw('ALTER TABLE posts MODIFY ' + entry.Field + ' MEDIUMTEXT');
46 | }
47 | }));
48 | });
49 | };
50 |
51 | module.exports = {
52 | checkPostTable: checkPostTable,
53 | getTables: getTables,
54 | getIndexes: getIndexes,
55 | getColumns: getColumns
56 | };
57 |
--------------------------------------------------------------------------------
/core/server/api/slugs.js:
--------------------------------------------------------------------------------
1 | // # Slug API
2 | // RESTful API for the Slug resource
3 | var canThis = require('../permissions').canThis,
4 | dataProvider = require('../models'),
5 | errors = require('../errors'),
6 | Promise = require('bluebird'),
7 |
8 | slugs,
9 | allowedTypes;
10 |
11 | /**
12 | * ## Slugs API Methods
13 | *
14 | * **See:** [API Methods](index.js.html#api%20methods)
15 | */
16 | slugs = {
17 |
18 | /**
19 | * ## Generate Slug
20 | * Create a unique slug for the given type and its name
21 | *
22 | * @param {{type (required), name (required), context, transacting}} options
23 | * @returns {Promise(String)} Unique string
24 | */
25 | generate: function (options) {
26 | options = options || {};
27 |
28 | // `allowedTypes` is used to define allowed slug types and map them against its model class counterpart
29 | allowedTypes = {
30 | post: dataProvider.Post,
31 | tag: dataProvider.Tag,
32 | user: dataProvider.User,
33 | app: dataProvider.App
34 | };
35 |
36 | return canThis(options.context).generate.slug().then(function () {
37 | if (allowedTypes[options.type] === undefined) {
38 | return Promise.reject(new errors.BadRequestError('Unknown slug type \'' + options.type + '\'.'));
39 | }
40 |
41 | return dataProvider.Base.Model.generateSlug(allowedTypes[options.type], options.name, {status: 'all'}).then(function (slug) {
42 | if (!slug) {
43 | return Promise.reject(new errors.InternalServerError('Could not generate slug.'));
44 | }
45 |
46 | return {slugs: [{slug: slug}]};
47 | });
48 | }).catch(function (err) {
49 | if (err) {
50 | return Promise.reject(err);
51 | }
52 |
53 | return Promise.reject(new errors.NoPermissionError('You do not have permission to generate a slug.'));
54 | });
55 | }
56 |
57 | };
58 |
59 | module.exports = slugs;
60 |
--------------------------------------------------------------------------------
/core/server/models/app.js:
--------------------------------------------------------------------------------
1 | var ghostBookshelf = require('./base'),
2 | App,
3 | Apps;
4 |
5 | App = ghostBookshelf.Model.extend({
6 | tableName: 'apps',
7 |
8 | saving: function (newPage, attr, options) {
9 | /*jshint unused:false*/
10 | var self = this;
11 |
12 | ghostBookshelf.Model.prototype.saving.apply(this, arguments);
13 |
14 | if (this.hasChanged('slug') || !this.get('slug')) {
15 | // Pass the new slug through the generator to strip illegal characters, detect duplicates
16 | return ghostBookshelf.Model.generateSlug(App, this.get('slug') || this.get('name'),
17 | {transacting: options.transacting})
18 | .then(function (slug) {
19 | self.set({slug: slug});
20 | });
21 | }
22 | },
23 |
24 | permissions: function () {
25 | return this.belongsToMany('Permission', 'permissions_apps');
26 | },
27 |
28 | settings: function () {
29 | return this.belongsToMany('AppSetting', 'app_settings');
30 | }
31 | }, {
32 | /**
33 | * Returns an array of keys permitted in a method's `options` hash, depending on the current method.
34 | * @param {String} methodName The name of the method to check valid options for.
35 | * @return {Array} Keys allowed in the `options` hash of the model's method.
36 | */
37 | permittedOptions: function (methodName) {
38 | var options = ghostBookshelf.Model.permittedOptions(),
39 |
40 | // whitelists for the `options` hash argument on methods, by method name.
41 | // these are the only options that can be passed to Bookshelf / Knex.
42 | validOptions = {
43 | findOne: ['withRelated']
44 | };
45 |
46 | if (validOptions[methodName]) {
47 | options = options.concat(validOptions[methodName]);
48 | }
49 |
50 | return options;
51 | }
52 | });
53 |
54 | Apps = ghostBookshelf.Collection.extend({
55 | model: App
56 | });
57 |
58 | module.exports = {
59 | App: ghostBookshelf.model('App', App),
60 | Apps: ghostBookshelf.collection('Apps', Apps)
61 | };
62 |
--------------------------------------------------------------------------------
/core/server/data/default-settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "core": {
3 | "databaseVersion": {
4 | "defaultValue": "003"
5 | },
6 | "dbHash": {
7 | "defaultValue": null
8 | },
9 | "nextUpdateCheck": {
10 | "defaultValue": null
11 | },
12 | "displayUpdateNotification": {
13 | "defaultValue": null
14 | }
15 | },
16 | "blog": {
17 | "title": {
18 | "defaultValue": "Ghost"
19 | },
20 | "description": {
21 | "defaultValue": "Just a blogging platform."
22 | },
23 | "email": {
24 | "defaultValue": "ghost@example.com",
25 | "validations": {
26 | "isNull": false,
27 | "isEmail": true
28 | }
29 | },
30 | "logo": {
31 | "defaultValue": ""
32 | },
33 | "cover": {
34 | "defaultValue": ""
35 | },
36 | "defaultLang": {
37 | "defaultValue": "en_US",
38 | "validations": {
39 | "isNull": false
40 | }
41 | },
42 | "postsPerPage": {
43 | "defaultValue": "5",
44 | "validations": {
45 | "isNull": false,
46 | "isInt": true,
47 | "isLength": [1, 1000]
48 | }
49 | },
50 | "forceI18n": {
51 | "defaultValue": "true",
52 | "validations": {
53 | "isNull": false,
54 | "isIn": [["true", "false"]]
55 | }
56 | },
57 | "permalinks": {
58 | "defaultValue": "/:slug/",
59 | "validations": {
60 | "matches": "^(\/:?[a-z0-9_-]+){1,5}\/$",
61 | "matches": "(:id|:slug|:year|:month|:day)",
62 | "notContains": "/ghost/"
63 | }
64 | }
65 | },
66 | "theme": {
67 | "activeTheme": {
68 | "defaultValue": "casper"
69 | }
70 | },
71 | "app": {
72 | "activeApps": {
73 | "defaultValue": "[]"
74 | },
75 | "installedApps": {
76 | "defaultValue": "[]"
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/core/server/api/configuration.js:
--------------------------------------------------------------------------------
1 | // # Configuration API
2 | // RESTful API for browsing the configuration
3 | var _ = require('lodash'),
4 | config = require('../config'),
5 | errors = require('../errors'),
6 | parsePackageJson = require('../require-tree').parsePackageJson,
7 | Promise = require('bluebird'),
8 |
9 | configuration;
10 |
11 | function getValidKeys() {
12 | var validKeys = {
13 | fileStorage: config.fileStorage === false ? false : true,
14 | apps: config.apps === true ? true : false,
15 | version: false,
16 | environment: process.env.NODE_ENV,
17 | database: config.database.client,
18 | mail: _.isObject(config.mail) ? config.mail.transport : '',
19 | blogUrl: config.url
20 | };
21 |
22 | return parsePackageJson('package.json').then(function (json) {
23 | validKeys.version = json.version;
24 | return validKeys;
25 | });
26 | }
27 |
28 | /**
29 | * ## Configuration API Methods
30 | *
31 | * **See:** [API Methods](index.js.html#api%20methods)
32 | */
33 | configuration = {
34 |
35 | /**
36 | * ### Browse
37 | * Fetch all configuration keys
38 | * @returns {Promise(Configurations)}
39 | */
40 | browse: function browse() {
41 | return getValidKeys().then(function (result) {
42 | return Promise.resolve({configuration: _.map(result, function (value, key) {
43 | return {
44 | key: key,
45 | value: value
46 | };
47 | })});
48 | });
49 | },
50 |
51 | /**
52 | * ### Read
53 | *
54 | */
55 | read: function read(options) {
56 | return getValidKeys().then(function (result) {
57 | if (_.has(result, options.key)) {
58 | return Promise.resolve({configuration: [{
59 | key: options.key,
60 | value: result[options.key]
61 | }]});
62 | } else {
63 | return Promise.reject(new errors.NotFoundError('Invalid key'));
64 | }
65 | });
66 | }
67 | };
68 |
69 | module.exports = configuration;
70 |
--------------------------------------------------------------------------------
/core/server/apps/permissions.js:
--------------------------------------------------------------------------------
1 |
2 | var fs = require('fs'),
3 | Promise = require('bluebird'),
4 | path = require('path'),
5 | parsePackageJson = require('../require-tree').parsePackageJson;
6 |
7 | function AppPermissions(appPath) {
8 | this.appPath = appPath;
9 | this.packagePath = path.join(this.appPath, 'package.json');
10 | }
11 |
12 | AppPermissions.prototype.read = function () {
13 | var self = this;
14 |
15 | return this.checkPackageContentsExists().then(function (exists) {
16 | if (!exists) {
17 | // If no package.json, return default permissions
18 | return Promise.resolve(AppPermissions.DefaultPermissions);
19 | }
20 |
21 | // Read and parse the package.json
22 | return self.getPackageContents().then(function (parsed) {
23 | // If no permissions in the package.json then return the default permissions.
24 | if (!(parsed.ghost && parsed.ghost.permissions)) {
25 | return Promise.resolve(AppPermissions.DefaultPermissions);
26 | }
27 |
28 | // TODO: Validation on permissions object?
29 |
30 | return Promise.resolve(parsed.ghost.permissions);
31 | });
32 | });
33 | };
34 |
35 | AppPermissions.prototype.checkPackageContentsExists = function () {
36 | var self = this;
37 |
38 | // Mostly just broken out for stubbing in unit tests
39 | return new Promise(function (resolve) {
40 | fs.exists(self.packagePath, function (exists) {
41 | resolve(exists);
42 | });
43 | });
44 | };
45 |
46 | // Get the contents of the package.json in the appPath root
47 | AppPermissions.prototype.getPackageContents = function () {
48 | var messages = {
49 | errors: [],
50 | warns: []
51 | };
52 |
53 | return parsePackageJson(this.packagePath, messages)
54 | .then(function (parsed) {
55 | if (!parsed) {
56 | return Promise.reject(new Error(messages.errors[0].message));
57 | }
58 |
59 | return parsed;
60 | });
61 | };
62 |
63 | // Default permissions for an App.
64 | AppPermissions.DefaultPermissions = {
65 | posts: ['browse', 'read']
66 | };
67 |
68 | module.exports = AppPermissions;
69 |
--------------------------------------------------------------------------------
/core/server/data/export/index.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | Promise = require('bluebird'),
3 | versioning = require('../versioning'),
4 | config = require('../../config'),
5 | utils = require('../utils'),
6 | serverUtils = require('../../utils'),
7 | errors = require('../../errors'),
8 | settings = require('../../api/settings'),
9 |
10 | excludedTables = ['accesstokens', 'refreshtokens', 'clients'],
11 | exporter,
12 | exportFileName;
13 |
14 | exportFileName = function () {
15 | var datetime = (new Date()).toJSON().substring(0, 10),
16 | title = '';
17 |
18 | return settings.read({key: 'title', context: {internal: true}}).then(function (result) {
19 | if (result) {
20 | title = serverUtils.safeString(result.settings[0].value) + '.';
21 | }
22 | return title + 'ghost.' + datetime + '.json';
23 | }).catch(function (err) {
24 | errors.logError(err);
25 | return 'ghost.' + datetime + '.json';
26 | });
27 | };
28 |
29 | exporter = function () {
30 | return Promise.join(versioning.getDatabaseVersion(), utils.getTables()).then(function (results) {
31 | var version = results[0],
32 | tables = results[1],
33 | selectOps = _.map(tables, function (name) {
34 | if (excludedTables.indexOf(name) < 0) {
35 | return config.database.knex(name).select();
36 | }
37 | });
38 |
39 | return Promise.all(selectOps).then(function (tableData) {
40 | var exportData = {
41 | meta: {
42 | exported_on: new Date().getTime(),
43 | version: version
44 | },
45 | data: {
46 | // Filled below
47 | }
48 | };
49 |
50 | _.each(tables, function (name, i) {
51 | exportData.data[name] = tableData[i];
52 | });
53 |
54 | return exportData;
55 | }).catch(function (err) {
56 | errors.logAndThrowError(err, 'Error exporting data', '');
57 | });
58 | });
59 | };
60 |
61 | module.exports = exporter;
62 | module.exports.fileName = exportFileName;
63 |
--------------------------------------------------------------------------------
/core/server/models/index.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | Promise = require('bluebird'),
3 | requireTree = require('../require-tree'),
4 | models;
5 |
6 | models = {
7 | excludeFiles: ['_messages', 'basetoken.js', 'base.js', 'index.js'],
8 |
9 | // ### init
10 | // Scan all files in this directory and then require each one and cache
11 | // the objects exported onto this `models` object so that every other
12 | // module can safely access models without fear of introducing circular
13 | // dependency issues.
14 | // @returns {Promise}
15 | init: function () {
16 | var self = this;
17 |
18 | // One off inclusion of Base file.
19 | self.Base = require('./base');
20 |
21 | // Require all files in this directory
22 | return requireTree.readAll(__dirname, {followSymlinks: false}).then(function (modelFiles) {
23 | // For each found file, excluding those we don't want,
24 | // we will require it and cache it here.
25 | _.each(modelFiles, function (path, fileName) {
26 | // Return early if this fileName is one of the ones we want
27 | // to exclude.
28 | if (_.contains(self.excludeFiles, fileName)) {
29 | return;
30 | }
31 |
32 | // Require the file.
33 | var file = require(path);
34 |
35 | // Cache its `export` object onto this object.
36 | _.extend(self, file);
37 | });
38 |
39 | return;
40 | });
41 | },
42 | // ### deleteAllContent
43 | // Delete all content from the database (posts, tags, tags_posts)
44 | deleteAllContent: function () {
45 | var self = this;
46 |
47 | return self.Post.findAll().then(function (posts) {
48 | return Promise.all(_.map(posts.toJSON(), function (post) {
49 | return self.Post.destroy({id: post.id});
50 | }));
51 | }).then(function () {
52 | return self.Tag.findAll().then(function (tags) {
53 | return Promise.all(_.map(tags.toJSON(), function (tag) {
54 | return self.Tag.destroy({id: tag.id});
55 | }));
56 | });
57 | });
58 | }
59 | };
60 |
61 | module.exports = models;
62 |
--------------------------------------------------------------------------------
/core/server/storage/local-file-store.js:
--------------------------------------------------------------------------------
1 | // # Local File System Image Storage module
2 | // The (default) module for storing images, using the local file system
3 |
4 | var express = require('express'),
5 | fs = require('fs-extra'),
6 | path = require('path'),
7 | util = require('util'),
8 | Promise = require('bluebird'),
9 | errors = require('../errors'),
10 | config = require('../config'),
11 | utils = require('../utils'),
12 | baseStore = require('./base');
13 |
14 | function LocalFileStore() {
15 | }
16 | util.inherits(LocalFileStore, baseStore);
17 |
18 | // ### Save
19 | // Saves the image to storage (the file system)
20 | // - image is the express image object
21 | // - returns a promise which ultimately returns the full url to the uploaded image
22 | LocalFileStore.prototype.save = function (image) {
23 | var targetDir = this.getTargetDir(config.paths.imagesPath),
24 | targetFilename;
25 |
26 | return this.getUniqueFileName(this, image, targetDir).then(function (filename) {
27 | targetFilename = filename;
28 | return Promise.promisify(fs.mkdirs)(targetDir);
29 | }).then(function () {
30 | return Promise.promisify(fs.copy)(image.path, targetFilename);
31 | }).then(function () {
32 | // The src for the image must be in URI format, not a file system path, which in Windows uses \
33 | // For local file system storage can use relative path so add a slash
34 | var fullUrl = (config.paths.subdir + '/' + config.paths.imagesRelPath + '/' +
35 | path.relative(config.paths.imagesPath, targetFilename)).replace(new RegExp('\\' + path.sep, 'g'), '/');
36 | return fullUrl;
37 | }).catch(function (e) {
38 | errors.logError(e);
39 | return Promise.reject(e);
40 | });
41 | };
42 |
43 | LocalFileStore.prototype.exists = function (filename) {
44 | return new Promise(function (resolve) {
45 | fs.exists(filename, function (exists) {
46 | resolve(exists);
47 | });
48 | });
49 | };
50 |
51 | // middleware for serving the files
52 | LocalFileStore.prototype.serve = function () {
53 | // For some reason send divides the max age number by 1000
54 | return express['static'](config.paths.imagesPath, {maxAge: utils.ONE_YEAR_MS});
55 | };
56 |
57 | module.exports = LocalFileStore;
58 |
--------------------------------------------------------------------------------
/core/server/helpers/template.js:
--------------------------------------------------------------------------------
1 | var templates = {},
2 | hbs = require('express-hbs'),
3 | errors = require('../errors');
4 |
5 | // ## Template utils
6 |
7 | // Execute a template helper
8 | // All template helpers are register as partial view.
9 | templates.execute = function (name, context) {
10 | var partial = hbs.handlebars.partials[name];
11 |
12 | if (partial === undefined) {
13 | errors.logAndThrowError('Template ' + name + ' not found.');
14 | return;
15 | }
16 |
17 | // If the partial view is not compiled, it compiles and saves in handlebars
18 | if (typeof partial === 'string') {
19 | hbs.registerPartial(partial);
20 | }
21 |
22 | return new hbs.handlebars.SafeString(partial(context));
23 | };
24 |
25 | // Given a theme object and a post object this will return
26 | // which theme template page should be used.
27 | // If given a post object that is a regular post
28 | // it will return 'post'.
29 | // If given a static post object it will return 'page'.
30 | // If given a static post object and a custom page template
31 | // exits it will return that page.
32 | templates.getThemeViewForPost = function (themePaths, post) {
33 | var customPageView = 'page-' + post.slug,
34 | view = 'post';
35 |
36 | if (post.page) {
37 | if (themePaths.hasOwnProperty(customPageView + '.hbs')) {
38 | view = customPageView;
39 | } else if (themePaths.hasOwnProperty('page.hbs')) {
40 | view = 'page';
41 | }
42 | }
43 |
44 | return view;
45 | };
46 |
47 | // Given a theme object and a tag slug this will return
48 | // which theme template page should be used.
49 | // If no default or custom tag template exists then 'index'
50 | // will be returned
51 | // If no custom tag template exists but a default does then
52 | // 'tag' will be returned
53 | // If given a tag slug and a custom tag template
54 | // exits it will return that view.
55 | templates.getThemeViewForTag = function (themePaths, tag) {
56 | var customTagView = 'tag-' + tag,
57 | view = 'tag';
58 |
59 | if (themePaths.hasOwnProperty(customTagView + '.hbs')) {
60 | view = customTagView;
61 | } else if (!themePaths.hasOwnProperty('tag.hbs')) {
62 | view = 'index';
63 | }
64 |
65 | return view;
66 | };
67 |
68 | module.exports = templates;
69 |
--------------------------------------------------------------------------------
/core/server/views/default.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Ghost Admin
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | {{#unless skip_google_fonts}}
33 |
34 | {{/unless}}
35 |
36 |
37 |
38 |
39 | {{{ghost_script_tags}}}
40 |
41 |
42 |
--------------------------------------------------------------------------------
/core/server/middleware/ghost-busboy.js:
--------------------------------------------------------------------------------
1 | var BusBoy = require('busboy'),
2 | fs = require('fs-extra'),
3 | path = require('path'),
4 | os = require('os'),
5 | crypto = require('crypto');
6 |
7 | // ### ghostBusboy
8 | // Process multipart file streams
9 | function ghostBusBoy(req, res, next) {
10 | var busboy,
11 | stream,
12 | tmpDir;
13 |
14 | // busboy is only used for POST requests
15 | if (req.method && !/post/i.test(req.method)) {
16 | return next();
17 | }
18 |
19 | busboy = new BusBoy({headers: req.headers});
20 | tmpDir = os.tmpdir();
21 |
22 | req.files = req.files || {};
23 | req.body = req.body || {};
24 |
25 | busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
26 | var filePath,
27 | tmpFileName,
28 | md5 = crypto.createHash('md5');
29 |
30 | // If the filename is invalid, skip the stream
31 | if (!filename) {
32 | return file.resume();
33 | }
34 |
35 | // Create an MD5 hash of original filename
36 | md5.update(filename, 'utf8');
37 |
38 | tmpFileName = (new Date()).getTime() + md5.digest('hex');
39 |
40 | filePath = path.join(tmpDir, tmpFileName || 'temp.tmp');
41 |
42 | file.on('end', function () {
43 | req.files[fieldname] = {
44 | type: mimetype,
45 | encoding: encoding,
46 | name: filename,
47 | path: filePath
48 | };
49 | });
50 |
51 | file.on('error', function (error) {
52 | console.log('Error', 'Something went wrong uploading the file', error);
53 | });
54 |
55 | stream = fs.createWriteStream(filePath);
56 |
57 | stream.on('error', function (error) {
58 | console.log('Error', 'Something went wrong uploading the file', error);
59 | });
60 |
61 | file.pipe(stream);
62 | });
63 |
64 | busboy.on('error', function (error) {
65 | console.log('Error', 'Something went wrong parsing the form', error);
66 | res.status(500).send({code: 500, message: 'Could not parse upload completely.'});
67 | });
68 |
69 | busboy.on('field', function (fieldname, val) {
70 | req.body[fieldname] = val;
71 | });
72 |
73 | busboy.on('finish', function () {
74 | next();
75 | });
76 |
77 | req.pipe(busboy);
78 | }
79 |
80 | module.exports = ghostBusBoy;
81 |
--------------------------------------------------------------------------------
/core/server/middleware/auth-strategies.js:
--------------------------------------------------------------------------------
1 | var passport = require('passport'),
2 | BearerStrategy = require('passport-http-bearer').Strategy,
3 | ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy,
4 | models = require('../models');
5 |
6 | /**
7 | * ClientPasswordStrategy
8 | *
9 | * This strategy is used to authenticate registered OAuth clients. It is
10 | * employed to protect the `token` endpoint, which consumers use to obtain
11 | * access tokens. The OAuth 2.0 specification suggests that clients use the
12 | * HTTP Basic scheme to authenticate (not implemented yet).
13 |
14 | * Use of the client password strategy is implemented to support ember-simple-auth.
15 | */
16 | passport.use(new ClientPasswordStrategy(
17 | function (clientId, clientSecret, done) {
18 | models.Client.forge({slug: clientId})
19 | .fetch()
20 | .then(function (model) {
21 | if (model) {
22 | var client = model.toJSON();
23 | if (client.secret === clientSecret) {
24 | return done(null, client);
25 | }
26 | }
27 | return done(null, false);
28 | });
29 | }
30 | ));
31 |
32 | /**
33 | * BearerStrategy
34 | *
35 | * This strategy is used to authenticate users based on an access token (aka a
36 | * bearer token). The user must have previously authorized a client
37 | * application, which is issued an access token to make requests on behalf of
38 | * the authorizing user.
39 | */
40 | passport.use(new BearerStrategy(
41 | function (accessToken, done) {
42 | models.Accesstoken.forge({token: accessToken})
43 | .fetch()
44 | .then(function (model) {
45 | if (model) {
46 | var token = model.toJSON();
47 | if (token.expires > Date.now()) {
48 | models.User.forge({id: token.user_id})
49 | .fetch()
50 | .then(function (model) {
51 | if (model) {
52 | var user = model.toJSON(),
53 | info = {scope: '*'};
54 | return done(null, {id: user.id}, info);
55 | }
56 | return done(null, false);
57 | });
58 | } else {
59 | return done(null, false);
60 | }
61 | } else {
62 | return done(null, false);
63 | }
64 | });
65 | }
66 | ));
67 |
--------------------------------------------------------------------------------
/core/server/helpers/foreach.js:
--------------------------------------------------------------------------------
1 | // # Foreach Helper
2 | // Usage: `{{#foreach data}}{{/foreach}}`
3 | //
4 | // Block helper designed for looping through posts
5 |
6 | var hbs = require('express-hbs'),
7 | foreach;
8 |
9 | foreach = function (context, options) {
10 | var fn = options.fn,
11 | inverse = options.inverse,
12 | i = 0,
13 | j = 0,
14 | columns = options.hash.columns,
15 | key,
16 | ret = '',
17 | data;
18 |
19 | if (options.data) {
20 | data = hbs.handlebars.createFrame(options.data);
21 | }
22 |
23 | function setKeys(_data, _i, _j, _columns) {
24 | if (_i === 0) {
25 | _data.first = true;
26 | }
27 | if (_i === _j - 1) {
28 | _data.last = true;
29 | }
30 | // first post is index zero but still needs to be odd
31 | if (_i % 2 === 1) {
32 | _data.even = true;
33 | } else {
34 | _data.odd = true;
35 | }
36 | if (_i % _columns === 0) {
37 | _data.rowStart = true;
38 | } else if (_i % _columns === (_columns - 1)) {
39 | _data.rowEnd = true;
40 | }
41 | return _data;
42 | }
43 | if (context && typeof context === 'object') {
44 | if (context instanceof Array) {
45 | for (j = context.length; i < j; i += 1) {
46 | if (data) {
47 | data.index = i;
48 | data.first = data.rowEnd = data.rowStart = data.last = data.even = data.odd = false;
49 | data = setKeys(data, i, j, columns);
50 | }
51 | ret = ret + fn(context[i], {data: data});
52 | }
53 | } else {
54 | for (key in context) {
55 | if (context.hasOwnProperty(key)) {
56 | j += 1;
57 | }
58 | }
59 | for (key in context) {
60 | if (context.hasOwnProperty(key)) {
61 | if (data) {
62 | data.key = key;
63 | data.first = data.rowEnd = data.rowStart = data.last = data.even = data.odd = false;
64 | data = setKeys(data, i, j, columns);
65 | }
66 | ret = ret + fn(context[key], {data: data});
67 | i += 1;
68 | }
69 | }
70 | }
71 | }
72 |
73 | if (i === 0) {
74 | ret = inverse(this);
75 | }
76 |
77 | return ret;
78 | };
79 |
80 | module.exports = foreach;
81 |
--------------------------------------------------------------------------------
/core/server/routes/frontend.js:
--------------------------------------------------------------------------------
1 | var frontend = require('../controllers/frontend'),
2 | config = require('../config'),
3 | express = require('express'),
4 | utils = require('../utils'),
5 |
6 | frontendRoutes;
7 |
8 | frontendRoutes = function () {
9 | var router = express.Router(),
10 | subdir = config.paths.subdir;
11 |
12 | // ### www redirect
13 | router.get('/*', function (req, res, next) {
14 | if (req.headers.host.match(/^www/) !== null) {
15 | res.redirect(301, 'http://' + req.headers.host.replace(/^www\./, '') + req.url);
16 | } else {
17 | next();
18 | }
19 | });
20 |
21 | // ### Admin routes
22 | router.get(/^\/(logout|signout)\/$/, function redirect(req, res) {
23 | /*jslint unparam:true*/
24 | res.set({'Cache-Control': 'public, max-age=' + utils.ONE_YEAR_S});
25 | res.redirect(301, subdir + '/ghost/signout/');
26 | });
27 | router.get(/^\/signup\/$/, function redirect(req, res) {
28 | /*jslint unparam:true*/
29 | res.set({'Cache-Control': 'public, max-age=' + utils.ONE_YEAR_S});
30 | res.redirect(301, subdir + '/ghost/signup/');
31 | });
32 |
33 | // redirect to /ghost and let that do the authentication to prevent redirects to /ghost//admin etc.
34 | router.get(/^\/((ghost-admin|admin|wp-admin|dashboard|signin|login)\/?)$/, function (req, res) {
35 | /*jslint unparam:true*/
36 | res.redirect(subdir + '/ghost/');
37 | });
38 |
39 | // ### Frontend routes
40 | router.get('/rss/', frontend.rss);
41 | router.get('/rss/:page/', frontend.rss);
42 | router.get('/feed/', function redirect(req, res) {
43 | /*jshint unused:true*/
44 | res.set({'Cache-Control': 'public, max-age=' + utils.ONE_YEAR_S});
45 | res.redirect(301, subdir + '/rss/');
46 | });
47 |
48 | // Tags
49 | router.get('/tag/:slug/rss/', frontend.rss);
50 | router.get('/tag/:slug/rss/:page/', frontend.rss);
51 | router.get('/tag/:slug/page/:page/', frontend.tag);
52 | router.get('/tag/:slug/', frontend.tag);
53 |
54 | // Authors
55 | router.get('/author/:slug/rss/', frontend.rss);
56 | router.get('/author/:slug/rss/:page/', frontend.rss);
57 | router.get('/author/:slug/page/:page/', frontend.author);
58 | router.get('/author/:slug/', frontend.author);
59 |
60 | // Default
61 | router.get('/page/:page/', frontend.homepage);
62 | router.get('/', frontend.homepage);
63 | router.get('*', frontend.single);
64 |
65 | return router;
66 | };
67 |
68 | module.exports = frontendRoutes;
69 |
--------------------------------------------------------------------------------
/core/server/views/user-error.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{code}} — {{message}}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
32 |
33 |
38 |
39 |
40 | {{#if stack}}
41 |
42 | Stack Trace
43 | {{message}}
44 |
45 | {{#each stack}}
46 |
47 | at
48 | {{#if function}}{{function}} {{/if}}
49 | ({{at}})
50 |
51 | {{/each}}
52 |
53 |
54 | {{/if}}
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/content/themes/kevgriffin/post.hbs:
--------------------------------------------------------------------------------
1 | {{!< default}}
2 |
3 | {{! The comment above "< default" means - insert everything in this file into
4 | the {body} of the default.hbs template, which contains our header/footer. }}
5 |
6 |
7 |
8 |
9 |
10 | {{! Each post has the blog logo at the top, with a link back to the home page }}
11 |
20 |
21 | {{! Everything inside the #post tags pulls data from the post }}
22 | {{#post}}
23 |
24 | {{date format='DD MMM YYYY'}} {{tags prefix="on " separator=" | "}}
25 |
26 | {{{title}}}
27 |
28 |
31 |
32 |
56 |
57 | {{/post}}
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/core/server/api/roles.js:
--------------------------------------------------------------------------------
1 | // # Roles API
2 | // RESTful API for the Role resource
3 | var Promise = require('bluebird'),
4 | _ = require('lodash'),
5 | canThis = require('../permissions').canThis,
6 | dataProvider = require('../models'),
7 | errors = require('../errors'),
8 |
9 | roles;
10 |
11 | /**
12 | * ## Roles API Methods
13 | *
14 | * **See:** [API Methods](index.js.html#api%20methods)
15 | */
16 | roles = {
17 | /**
18 | * ### Browse
19 | * Find all roles
20 | *
21 | * If a 'permissions' property is passed in the options object then
22 | * the results will be filtered based on whether or not the context user has the given
23 | * permission on a role.
24 | *
25 | *
26 | * @public
27 | * @param {{context, permissions}} options (optional)
28 | * @returns {Promise(Roles)} Roles Collection
29 | */
30 | browse: function browse(options) {
31 | var permissionMap = [];
32 | options = options || {};
33 |
34 | return canThis(options.context).browse.role().then(function () {
35 | return dataProvider.Role.findAll(options).then(function (foundRoles) {
36 | if (options.permissions === 'assign') {
37 | // Hacky implementation of filtering because when.filter is only available in when 3.4.0,
38 | // but that's buggy and kills other tests and introduces Heisenbugs. Until we turn everything
39 | // to Bluebird, this works. Sorry.
40 | // TODO: replace with better filter when bluebird lands
41 | _.each(foundRoles.toJSON(), function (role) {
42 | permissionMap.push(canThis(options.context).assign.role(role).then(function () {
43 | if (role.name === 'Owner') {
44 | return null;
45 | }
46 | return role;
47 | }).catch(function () {
48 | return null;
49 | }));
50 | });
51 |
52 | return Promise.all(permissionMap).then(function (resolved) {
53 | return {roles: _.filter(resolved, function (role) {
54 | return role !== null;
55 | })};
56 | }).catch(errors.logAndThrowError);
57 | }
58 | return {roles: foundRoles.toJSON()};
59 | });
60 | })
61 | .catch(errors.logAndThrowError);
62 | }
63 | };
64 |
65 | module.exports = roles;
66 |
--------------------------------------------------------------------------------
/core/server/data/versioning/index.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | errors = require('../../errors'),
3 | config = require('../../config'),
4 |
5 | defaultSettings = require('../default-settings'),
6 |
7 | initialVersion = '000',
8 | defaultDatabaseVersion;
9 |
10 | // Default Database Version
11 | // The migration version number according to the hardcoded default settings
12 | // This is the version the database should be at or migrated to
13 | function getDefaultDatabaseVersion() {
14 | if (!defaultDatabaseVersion) {
15 | // This be the current version according to the software
16 | defaultDatabaseVersion = defaultSettings.core.databaseVersion.defaultValue;
17 | }
18 |
19 | return defaultDatabaseVersion;
20 | }
21 |
22 | // Database Current Version
23 | // The migration version number according to the database
24 | // This is what the database is currently at and may need to be updated
25 | function getDatabaseVersion() {
26 | var knex = config.database.knex;
27 |
28 | return knex.schema.hasTable('settings').then(function (exists) {
29 | // Check for the current version from the settings table
30 | if (exists) {
31 | // Temporary code to deal with old databases with currentVersion settings
32 | return knex('settings')
33 | .where('key', 'databaseVersion')
34 | .orWhere('key', 'currentVersion')
35 | .select('value')
36 | .then(function (versions) {
37 | var databaseVersion = _.reduce(versions, function (memo, version) {
38 | if (isNaN(version.value)) {
39 | errors.throwError('Database version is not recognised');
40 | }
41 | return parseInt(version.value, 10) > parseInt(memo, 10) ? version.value : memo;
42 | }, initialVersion);
43 |
44 | if (!databaseVersion || databaseVersion.length === 0) {
45 | // we didn't get a response we understood, assume initialVersion
46 | databaseVersion = initialVersion;
47 | }
48 |
49 | return databaseVersion;
50 | });
51 | }
52 | throw new Error('Settings table does not exist');
53 | });
54 | }
55 |
56 | function setDatabaseVersion() {
57 | return config.database.knex('settings')
58 | .where('key', 'databaseVersion')
59 | .update({value: defaultDatabaseVersion});
60 | }
61 |
62 | module.exports = {
63 | getDefaultDatabaseVersion: getDefaultDatabaseVersion,
64 | getDatabaseVersion: getDatabaseVersion,
65 | setDatabaseVersion: setDatabaseVersion
66 | };
67 |
--------------------------------------------------------------------------------
/core/server/xmlrpc.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | http = require('http'),
3 | xml = require('xml'),
4 | api = require('./api'),
5 | config = require('./config'),
6 | errors = require('./errors'),
7 | pingList;
8 |
9 | // ToDo: Make this configurable
10 | pingList = [{
11 | host: 'blogsearch.google.com',
12 | path: '/ping/RPC2'
13 | }, {
14 | host: 'rpc.pingomatic.com',
15 | path: '/'
16 | }];
17 |
18 | function ping(post) {
19 | var pingXML,
20 | title = post.title;
21 |
22 | // Only ping when in production and not a page
23 | if (process.env.NODE_ENV !== 'production' || post.page || config.isPrivacyDisabled('useRpcPing')) {
24 | return;
25 | }
26 |
27 | // Don't ping for the welcome to ghost post.
28 | // This also handles the case where during ghost's first run
29 | // models.init() inserts this post but permissions.init() hasn't
30 | // (can't) run yet.
31 | if (post.slug === 'welcome-to-ghost') {
32 | return;
33 | }
34 |
35 | // Need to require here because of circular dependency
36 | return config.urlForPost(api.settings, post, true).then(function (url) {
37 | // Build XML object.
38 | pingXML = xml({
39 | methodCall: [{
40 | methodName: 'weblogUpdate.ping'
41 | }, {
42 | params: [{
43 | param: [{
44 | value: [{
45 | string: title
46 | }]
47 | }]
48 | }, {
49 | param: [{
50 | value: [{
51 | string: url
52 | }]
53 | }]
54 | }]
55 | }]
56 | }, {declaration: true});
57 |
58 | // Ping each of the defined services.
59 | _.each(pingList, function (pingHost) {
60 | var options = {
61 | hostname: pingHost.host,
62 | path: pingHost.path,
63 | method: 'POST'
64 | },
65 | req;
66 |
67 | req = http.request(options);
68 | req.write(pingXML);
69 | req.on('error', function (error) {
70 | errors.logError(
71 | error,
72 | 'Pinging services for updates on your blog failed, your blog will continue to function.',
73 | 'If you get this error repeatedly, please seek help from https://ghost.org/forum.'
74 | );
75 | });
76 | req.end();
77 | });
78 | });
79 | }
80 |
81 | module.exports = {
82 | ping: ping
83 | };
84 |
--------------------------------------------------------------------------------
/core/server/data/migration/commands.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | errors = require('../../errors'),
3 | utils = require('../utils'),
4 | schema = require('../schema').tables,
5 |
6 | // private
7 | logInfo,
8 |
9 | // public
10 | getDeleteCommands,
11 | getAddCommands,
12 | addColumnCommands,
13 | modifyUniqueCommands;
14 |
15 | logInfo = function logInfo(message) {
16 | errors.logInfo('Migrations', message);
17 | };
18 |
19 | getDeleteCommands = function getDeleteCommands(oldTables, newTables) {
20 | var deleteTables = _.difference(oldTables, newTables);
21 | return _.map(deleteTables, function (table) {
22 | return function () {
23 | logInfo('Deleting table: ' + table);
24 | return utils.deleteTable(table);
25 | };
26 | });
27 | };
28 | getAddCommands = function getAddCommands(oldTables, newTables) {
29 | var addTables = _.difference(newTables, oldTables);
30 | return _.map(addTables, function (table) {
31 | return function () {
32 | logInfo('Creating table: ' + table);
33 | return utils.createTable(table);
34 | };
35 | });
36 | };
37 | addColumnCommands = function addColumnCommands(table, columns) {
38 | var columnKeys = _.keys(schema[table]),
39 | addColumns = _.difference(columnKeys, columns);
40 |
41 | return _.map(addColumns, function (column) {
42 | return function () {
43 | logInfo('Adding column: ' + table + '.' + column);
44 | return utils.addColumn(table, column);
45 | };
46 | });
47 | };
48 | modifyUniqueCommands = function modifyUniqueCommands(table, indexes) {
49 | var columnKeys = _.keys(schema[table]);
50 | return _.map(columnKeys, function (column) {
51 | if (schema[table][column].unique && schema[table][column].unique === true) {
52 | if (!_.contains(indexes, table + '_' + column + '_unique')) {
53 | return function () {
54 | logInfo('Adding unique on: ' + table + '.' + column);
55 | return utils.addUnique(table, column);
56 | };
57 | }
58 | } else if (!schema[table][column].unique) {
59 | if (_.contains(indexes, table + '_' + column + '_unique')) {
60 | return function () {
61 | logInfo('Dropping unique on: ' + table + '.' + column);
62 | return utils.dropUnique(table, column);
63 | };
64 | }
65 | }
66 | });
67 | };
68 |
69 | module.exports = {
70 | getDeleteCommands: getDeleteCommands,
71 | getAddCommands: getAddCommands,
72 | addColumnCommands: addColumnCommands,
73 | modifyUniqueCommands: modifyUniqueCommands
74 | };
75 |
--------------------------------------------------------------------------------
/core/server/models/basetoken.js:
--------------------------------------------------------------------------------
1 | var Promise = require('bluebird'),
2 | ghostBookshelf = require('./base'),
3 | errors = require('../errors'),
4 |
5 | Basetoken;
6 |
7 | Basetoken = ghostBookshelf.Model.extend({
8 |
9 | user: function () {
10 | return this.belongsTo('User');
11 | },
12 |
13 | client: function () {
14 | return this.belongsTo('Client');
15 | },
16 |
17 | // override for base function since we don't have
18 | // a created_by field for sessions
19 | creating: function (newObj, attr, options) {
20 | /*jshint unused:false*/
21 | },
22 |
23 | // override for base function since we don't have
24 | // a updated_by field for sessions
25 | saving: function (newObj, attr, options) {
26 | /*jshint unused:false*/
27 | // Remove any properties which don't belong on the model
28 | this.attributes = this.pick(this.permittedAttributes());
29 | }
30 |
31 | }, {
32 | destroyAllExpired: function (options) {
33 | options = this.filterOptions(options, 'destroyAll');
34 | return ghostBookshelf.Collection.forge([], {model: this})
35 | .query('where', 'expires', '<', Date.now())
36 | .fetch(options)
37 | .then(function (collection) {
38 | collection.invokeThen('destroy', options);
39 | });
40 | },
41 | /**
42 | * ### destroyByUser
43 | * @param {[type]} options has context and id. Context is the user doing the destroy, id is the user to destroy
44 | */
45 | destroyByUser: function (options) {
46 | var userId = options.id;
47 |
48 | options = this.filterOptions(options, 'destroyByUser');
49 |
50 | if (userId) {
51 | return ghostBookshelf.Collection.forge([], {model: this})
52 | .query('where', 'user_id', '=', userId)
53 | .fetch(options)
54 | .then(function (collection) {
55 | collection.invokeThen('destroy', options);
56 | });
57 | }
58 |
59 | return Promise.reject(new errors.NotFoundError('No user found'));
60 | },
61 |
62 | /**
63 | * ### destroyByToken
64 | * @param {[type]} options has token where token is the token to destroy
65 | */
66 | destroyByToken: function (options) {
67 | var token = options.token;
68 |
69 | options = this.filterOptions(options, 'destroyByUser');
70 |
71 | if (token) {
72 | return ghostBookshelf.Collection.forge([], {model: this})
73 | .query('where', 'token', '=', token)
74 | .fetch(options)
75 | .then(function (collection) {
76 | collection.invokeThen('destroy', options);
77 | });
78 | }
79 |
80 | return Promise.reject(new errors.NotFoundError('Token not found'));
81 | }
82 | });
83 |
84 | module.exports = Basetoken;
85 |
--------------------------------------------------------------------------------
/core/shared/lib/showdown/extensions/ghostimagepreview.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true, browser:true */
2 | /* global Ember */
3 |
4 | // Ghost Image Preview
5 | //
6 | // Manages the conversion of image markdown `![]()` from markdown into the HTML image preview
7 | // This provides a dropzone and other interface elements for adding images
8 | // Is only used in the admin client.
9 |
10 | var Ghost = Ghost || {};
11 | (function () {
12 | var ghostimagepreview = function () {
13 | return [
14 | // ![] image syntax
15 | {
16 | type: 'lang',
17 | filter: function (text) {
18 | var imageMarkdownRegex = /^(?:\{<(.*?)>\})?!(?:\[([^\n\]]*)\])(?:\(([^\n\]]*)\))?$/gim,
19 | /* regex from isURL in node-validator. Yum! */
20 | uriRegex = /^(?!mailto:)(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))|localhost)(?::\d{2,5})?(?:\/[^\s]*)?$/i,
21 | pathRegex = /^(\/)?([^\/\0]+(\/)?)+$/i;
22 |
23 | return text.replace(imageMarkdownRegex, function (match, key, alt, src) {
24 | var result = '',
25 | output;
26 |
27 | if (src && (src.match(uriRegex) || src.match(pathRegex))) {
28 | result = ' ';
29 | }
30 |
31 | if ((Ghost && Ghost.touchEditor) || (typeof window !== 'undefined' && Ember.touchEditor)) {
32 | output = '' +
33 | result + 'Mobile uploads coming soon
';
34 | } else {
35 | output = '';
39 | }
40 |
41 | return output;
42 | });
43 | }
44 | }
45 | ];
46 | };
47 |
48 | // Client-side export
49 | if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) {
50 | window.Showdown.extensions.ghostimagepreview = ghostimagepreview;
51 | }
52 | // Server-side export
53 | if (typeof module !== 'undefined') {
54 | module.exports = ghostimagepreview;
55 | }
56 | }());
57 |
--------------------------------------------------------------------------------
/content/themes/casper/assets/js/jquery.fitvids.js:
--------------------------------------------------------------------------------
1 | /*global jQuery */
2 | /*jshint browser:true */
3 | /*!
4 | * FitVids 1.1
5 | *
6 | * Copyright 2013, Chris Coyier - http://css-tricks.com + Dave Rupert - http://daverupert.com
7 | * Credit to Thierry Koblentz - http://www.alistapart.com/articles/creating-intrinsic-ratios-for-video/
8 | * Released under the WTFPL license - http://sam.zoy.org/wtfpl/
9 | *
10 | */
11 |
12 | (function( $ ){
13 |
14 | "use strict";
15 |
16 | $.fn.fitVids = function( options ) {
17 | var settings = {
18 | customSelector: null
19 | };
20 |
21 | if(!document.getElementById('fit-vids-style')) {
22 | // appendStyles: https://github.com/toddmotto/fluidvids/blob/master/dist/fluidvids.js
23 | var head = document.head || document.getElementsByTagName('head')[0];
24 | var css = '.fluid-width-video-wrapper{width:100%;position:relative;padding:0;}.fluid-width-video-wrapper iframe,.fluid-width-video-wrapper object,.fluid-width-video-wrapper embed {position:absolute;top:0;left:0;width:100%;height:100%;}';
25 | var div = document.createElement('div');
26 | div.innerHTML = 'x
';
27 | head.appendChild(div.childNodes[1]);
28 | }
29 |
30 | if ( options ) {
31 | $.extend( settings, options );
32 | }
33 |
34 | return this.each(function(){
35 | var selectors = [
36 | "iframe[src*='player.vimeo.com']",
37 | "iframe[src*='youtube.com']",
38 | "iframe[src*='youtube-nocookie.com']",
39 | "iframe[src*='kickstarter.com'][src*='video.html']",
40 | "object",
41 | "embed"
42 | ];
43 |
44 | if (settings.customSelector) {
45 | selectors.push(settings.customSelector);
46 | }
47 |
48 | var $allVideos = $(this).find(selectors.join(','));
49 | $allVideos = $allVideos.not("object object"); // SwfObj conflict patch
50 |
51 | $allVideos.each(function(){
52 | var $this = $(this);
53 | if (this.tagName.toLowerCase() === 'embed' && $this.parent('object').length || $this.parent('.fluid-width-video-wrapper').length) { return; }
54 | var height = ( this.tagName.toLowerCase() === 'object' || ($this.attr('height') && !isNaN(parseInt($this.attr('height'), 10))) ) ? parseInt($this.attr('height'), 10) : $this.height(),
55 | width = !isNaN(parseInt($this.attr('width'), 10)) ? parseInt($this.attr('width'), 10) : $this.width(),
56 | aspectRatio = height / width;
57 | if(!$this.attr('id')){
58 | var videoID = 'fitvid' + Math.floor(Math.random()*999999);
59 | $this.attr('id', videoID);
60 | }
61 | $this.wrap('
').parent('.fluid-width-video-wrapper').css('padding-top', (aspectRatio * 100)+"%");
62 | $this.removeAttr('height').removeAttr('width');
63 | });
64 | });
65 | };
66 | // Works with either jQuery or Zepto
67 | })( window.jQuery || window.Zepto );
68 |
--------------------------------------------------------------------------------
/content/themes/kevgriffin/assets/js/jquery.fitvids.js:
--------------------------------------------------------------------------------
1 | /*global jQuery */
2 | /*jshint multistr:true browser:true */
3 | /*!
4 | * FitVids 1.0.3
5 | *
6 | * Copyright 2013, Chris Coyier - http://css-tricks.com + Dave Rupert - http://daverupert.com
7 | * Credit to Thierry Koblentz - http://www.alistapart.com/articles/creating-intrinsic-ratios-for-video/
8 | * Released under the WTFPL license - http://sam.zoy.org/wtfpl/
9 | *
10 | * Date: Thu Sept 01 18:00:00 2011 -0500
11 | */
12 |
13 | (function( $ ){
14 |
15 | "use strict";
16 |
17 | $.fn.fitVids = function( options ) {
18 | var settings = {
19 | customSelector: null
20 | };
21 |
22 | if(!document.getElementById('fit-vids-style')) {
23 |
24 | var div = document.createElement('div'),
25 | ref = document.getElementsByTagName('base')[0] || document.getElementsByTagName('script')[0],
26 | cssStyles = '';
27 |
28 | div.className = 'fit-vids-style';
29 | div.id = 'fit-vids-style';
30 | div.style.display = 'none';
31 | div.innerHTML = cssStyles;
32 |
33 | ref.parentNode.insertBefore(div,ref);
34 |
35 | }
36 |
37 | if ( options ) {
38 | $.extend( settings, options );
39 | }
40 |
41 | return this.each(function(){
42 | var selectors = [
43 | "iframe[src*='player.vimeo.com']",
44 | "iframe[src*='youtube.com']",
45 | "iframe[src*='youtube-nocookie.com']",
46 | "iframe[src*='kickstarter.com'][src*='video.html']",
47 | "object",
48 | "embed"
49 | ];
50 |
51 | if (settings.customSelector) {
52 | selectors.push(settings.customSelector);
53 | }
54 |
55 | var $allVideos = $(this).find(selectors.join(','));
56 | $allVideos = $allVideos.not("object object"); // SwfObj conflict patch
57 |
58 | $allVideos.each(function(){
59 | var $this = $(this);
60 | if (this.tagName.toLowerCase() === 'embed' && $this.parent('object').length || $this.parent('.fluid-width-video-wrapper').length) { return; }
61 | var height = ( this.tagName.toLowerCase() === 'object' || ($this.attr('height') && !isNaN(parseInt($this.attr('height'), 10))) ) ? parseInt($this.attr('height'), 10) : $this.height(),
62 | width = !isNaN(parseInt($this.attr('width'), 10)) ? parseInt($this.attr('width'), 10) : $this.width(),
63 | aspectRatio = height / width;
64 | if(!$this.attr('id')){
65 | var videoID = 'fitvid' + Math.floor(Math.random()*999999);
66 | $this.attr('id', videoID);
67 | }
68 | $this.wrap('
').parent('.fluid-width-video-wrapper').css('padding-top', (aspectRatio * 100)+"%");
69 | $this.removeAttr('height').removeAttr('width');
70 | });
71 | });
72 | };
73 | // Works with either jQuery or Zepto
74 | })( window.jQuery || window.Zepto );
75 |
--------------------------------------------------------------------------------
/core/server/helpers/body_class.js:
--------------------------------------------------------------------------------
1 | // # Body Class Helper
2 | // Usage: `{{body_class}}`
3 | //
4 | // Output classes for the body element
5 | //
6 | // We use the name body_class to match the helper for consistency:
7 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
8 |
9 | var hbs = require('express-hbs'),
10 | _ = require('lodash'),
11 | api = require('../api'),
12 | config = require('../config'),
13 | filters = require('../filters'),
14 | template = require('./template'),
15 | body_class;
16 |
17 | body_class = function () {
18 | var classes = [],
19 | post = this.post,
20 | tags = this.post && this.post.tags ? this.post.tags : this.tags || [],
21 | page = this.post && this.post.page ? this.post.page : this.page || false;
22 |
23 | if (this.tag !== undefined) {
24 | classes.push('tag-template');
25 | classes.push('tag-' + this.tag.slug);
26 | }
27 |
28 | if (this.author !== undefined) {
29 | classes.push('author-template');
30 | classes.push('author-' + this.author.slug);
31 | }
32 |
33 | if (_.isString(this.relativeUrl) && this.relativeUrl.match(/\/(page\/\d)/)) {
34 | classes.push('paged');
35 | // To be removed from pages by #2597 when we're ready to deprecate this
36 | classes.push('archive-template');
37 | } else if (!this.relativeUrl || this.relativeUrl === '/' || this.relativeUrl === '') {
38 | classes.push('home-template');
39 | } else if (post) {
40 | // To be removed from pages by #2597 when we're ready to deprecate this
41 | // i.e. this should be if (post && !page) { ... }
42 | classes.push('post-template');
43 | }
44 |
45 | if (page) {
46 | classes.push('page-template');
47 | // To be removed by #2597 when we're ready to deprecate this
48 | classes.push('page');
49 | }
50 |
51 | if (tags) {
52 | classes = classes.concat(tags.map(function (tag) { return 'tag-' + tag.slug; }));
53 | }
54 |
55 | return api.settings.read({context: {internal: true}, key: 'activeTheme'}).then(function (response) {
56 | var activeTheme = response.settings[0],
57 | paths = config.paths.availableThemes[activeTheme.value],
58 | view;
59 |
60 | if (post && page) {
61 | view = template.getThemeViewForPost(paths, post).split('-');
62 |
63 | if (view[0] === 'page' && view.length > 1) {
64 | classes.push(view.join('-'));
65 | // To be removed by #2597 when we're ready to deprecate this
66 | view.splice(1, 0, 'template');
67 | classes.push(view.join('-'));
68 | }
69 | }
70 |
71 | return filters.doFilter('body_class', classes).then(function (classes) {
72 | var classString = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, '');
73 | return new hbs.handlebars.SafeString(classString.trim());
74 | });
75 | });
76 | };
77 |
78 | module.exports = body_class;
79 |
--------------------------------------------------------------------------------
/core/server/apps/sandbox.js:
--------------------------------------------------------------------------------
1 |
2 | var path = require('path'),
3 | Module = require('module'),
4 | _ = require('lodash');
5 |
6 | function AppSandbox(opts) {
7 | this.opts = _.defaults(opts || {}, AppSandbox.defaults);
8 | }
9 |
10 | AppSandbox.prototype.loadApp = function loadAppSandboxed(appPath) {
11 | var appFile = require.resolve(appPath),
12 | appBase = path.dirname(appFile);
13 |
14 | this.opts.appRoot = appBase;
15 |
16 | return this.loadModule(appPath);
17 | };
18 |
19 | AppSandbox.prototype.loadModule = function loadModuleSandboxed(modulePath) {
20 | // Set loaded modules parent to this
21 | var self = this,
22 | moduleDir = path.dirname(modulePath),
23 | parentModulePath = self.opts.parent || module.parent,
24 | appRoot = self.opts.appRoot || moduleDir,
25 | currentModule,
26 | nodeRequire;
27 |
28 | // Resolve the modules path
29 | modulePath = Module._resolveFilename(modulePath, parentModulePath);
30 |
31 | // Instantiate a Node Module class
32 | currentModule = new Module(modulePath, parentModulePath);
33 |
34 | // Grab the original modules require function
35 | nodeRequire = currentModule.require;
36 |
37 | // Set a new proxy require function
38 | currentModule.require = function requireProxy(module) {
39 | // check whitelist, plugin config, etc.
40 | if (_.contains(self.opts.blacklist, module)) {
41 | throw new Error('Unsafe App require: ' + module);
42 | }
43 |
44 | var firstTwo = module.slice(0, 2),
45 | resolvedPath,
46 | relPath,
47 | innerBox,
48 | newOpts;
49 |
50 | // Load relative modules with their own sandbox
51 | if (firstTwo === './' || firstTwo === '..') {
52 | // Get the path relative to the modules directory
53 | resolvedPath = path.resolve(moduleDir, module);
54 |
55 | // Check relative path from the appRoot for outside requires
56 | relPath = path.relative(appRoot, resolvedPath);
57 | if (relPath.slice(0, 2) === '..') {
58 | throw new Error('Unsafe App require: ' + relPath);
59 | }
60 |
61 | // Assign as new module path
62 | module = resolvedPath;
63 |
64 | // Pass down the same options
65 | newOpts = _.extend({}, self.opts);
66 |
67 | // Make sure the appRoot and parent are appropriate
68 | newOpts.appRoot = appRoot;
69 | newOpts.parent = currentModule.parent;
70 |
71 | // Create the inner sandbox for loading this module.
72 | innerBox = new AppSandbox(newOpts);
73 |
74 | return innerBox.loadModule(module);
75 | }
76 |
77 | // Call the original require method for white listed named modules
78 | return nodeRequire.call(currentModule, module);
79 | };
80 |
81 | currentModule.load(currentModule.id);
82 |
83 | return currentModule.exports;
84 | };
85 |
86 | AppSandbox.defaults = {
87 | blacklist: ['knex', 'fs', 'http', 'sqlite3', 'pg', 'mysql', 'ghost']
88 | };
89 |
90 | module.exports = AppSandbox;
91 |
--------------------------------------------------------------------------------
/core/server/filters.js:
--------------------------------------------------------------------------------
1 | var Promise = require('bluebird'),
2 | pipeline = require('./utils/pipeline'),
3 | _ = require('lodash'),
4 | defaults;
5 |
6 | // ## Default values
7 | /**
8 | * A hash of default values to use instead of 'magic' numbers/strings.
9 | * @type {Object}
10 | */
11 | defaults = {
12 | filterPriority: 5,
13 | maxPriority: 9
14 | };
15 |
16 | function Filters() {
17 | // Holds the filters
18 | this.filterCallbacks = [];
19 |
20 | // Holds the filter hooks (that are built in to Ghost Core)
21 | this.filters = [];
22 | }
23 |
24 | // Register a new filter callback function
25 | Filters.prototype.registerFilter = function (name, priority, fn) {
26 | // Carry the priority optional parameter to a default of 5
27 | if (_.isFunction(priority)) {
28 | fn = priority;
29 | priority = null;
30 | }
31 |
32 | // Null priority should be set to default
33 | if (priority === null) {
34 | priority = defaults.filterPriority;
35 | }
36 |
37 | this.filterCallbacks[name] = this.filterCallbacks[name] || {};
38 | this.filterCallbacks[name][priority] = this.filterCallbacks[name][priority] || [];
39 |
40 | this.filterCallbacks[name][priority].push(fn);
41 | };
42 |
43 | // Unregister a filter callback function
44 | Filters.prototype.deregisterFilter = function (name, priority, fn) {
45 | // Curry the priority optional parameter to a default of 5
46 | if (_.isFunction(priority)) {
47 | fn = priority;
48 | priority = defaults.filterPriority;
49 | }
50 |
51 | // Check if it even exists
52 | if (this.filterCallbacks[name] && this.filterCallbacks[name][priority]) {
53 | // Remove the function from the list of filter funcs
54 | this.filterCallbacks[name][priority] = _.without(this.filterCallbacks[name][priority], fn);
55 | }
56 | };
57 |
58 | // Execute filter functions in priority order
59 | Filters.prototype.doFilter = function (name, args, context) {
60 | var callbacks = this.filterCallbacks[name],
61 | priorityCallbacks = [];
62 |
63 | // Bug out early if no callbacks by that name
64 | if (!callbacks) {
65 | return Promise.resolve(args);
66 | }
67 |
68 | // For each priorityLevel
69 | _.times(defaults.maxPriority + 1, function (priority) {
70 | // Add a function that runs its priority level callbacks in a pipeline
71 | priorityCallbacks.push(function (currentArgs) {
72 | var callables;
73 |
74 | // Bug out if no handlers on this priority
75 | if (!_.isArray(callbacks[priority])) {
76 | return Promise.resolve(currentArgs);
77 | }
78 |
79 | callables = _.map(callbacks[priority], function (callback) {
80 | return function (args) {
81 | return callback(args, context);
82 | };
83 | });
84 | // Call each handler for this priority level, allowing for promises or values
85 | return pipeline(callables, currentArgs);
86 | });
87 | });
88 |
89 | return pipeline(priorityCallbacks, args);
90 | };
91 |
92 | module.exports = new Filters();
93 | module.exports.Filters = Filters;
94 |
--------------------------------------------------------------------------------
/core/server/models/role.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | errors = require('../errors'),
3 | ghostBookshelf = require('./base'),
4 | Promise = require('bluebird'),
5 |
6 | Role,
7 | Roles;
8 |
9 | Role = ghostBookshelf.Model.extend({
10 |
11 | tableName: 'roles',
12 |
13 | users: function () {
14 | return this.belongsToMany('User');
15 | },
16 |
17 | permissions: function () {
18 | return this.belongsToMany('Permission');
19 | }
20 | }, {
21 | /**
22 | * Returns an array of keys permitted in a method's `options` hash, depending on the current method.
23 | * @param {String} methodName The name of the method to check valid options for.
24 | * @return {Array} Keys allowed in the `options` hash of the model's method.
25 | */
26 | permittedOptions: function (methodName) {
27 | var options = ghostBookshelf.Model.permittedOptions(),
28 |
29 | // whitelists for the `options` hash argument on methods, by method name.
30 | // these are the only options that can be passed to Bookshelf / Knex.
31 | validOptions = {
32 | findOne: ['withRelated']
33 | };
34 |
35 | if (validOptions[methodName]) {
36 | options = options.concat(validOptions[methodName]);
37 | }
38 |
39 | return options;
40 | },
41 |
42 | permissible: function (roleModelOrId, action, context, loadedPermissions, hasUserPermission, hasAppPermission) {
43 | var self = this,
44 | checkAgainst = [],
45 | origArgs;
46 |
47 | // If we passed in an id instead of a model, get the model
48 | // then check the permissions
49 | if (_.isNumber(roleModelOrId) || _.isString(roleModelOrId)) {
50 | // Grab the original args without the first one
51 | origArgs = _.toArray(arguments).slice(1);
52 | // Get the actual post model
53 | return this.findOne({id: roleModelOrId, status: 'all'}).then(function (foundRoleModel) {
54 | // Build up the original args but substitute with actual model
55 | var newArgs = [foundRoleModel].concat(origArgs);
56 |
57 | return self.permissible.apply(self, newArgs);
58 | }, errors.logAndThrowError);
59 | }
60 |
61 | if (action === 'assign' && loadedPermissions.user) {
62 | if (_.any(loadedPermissions.user.roles, {name: 'Owner'})) {
63 | checkAgainst = ['Owner', 'Administrator', 'Editor', 'Author'];
64 | } else if (_.any(loadedPermissions.user.roles, {name: 'Administrator'})) {
65 | checkAgainst = ['Administrator', 'Editor', 'Author'];
66 | } else if (_.any(loadedPermissions.user.roles, {name: 'Editor'})) {
67 | checkAgainst = ['Author'];
68 | }
69 |
70 | // Role in the list of permissible roles
71 | hasUserPermission = roleModelOrId && _.contains(checkAgainst, roleModelOrId.get('name'));
72 | }
73 |
74 | if (hasUserPermission && hasAppPermission) {
75 | return Promise.resolve();
76 | }
77 |
78 | return Promise.reject();
79 | }
80 | });
81 |
82 | Roles = ghostBookshelf.Collection.extend({
83 | model: Role
84 | });
85 |
86 | module.exports = {
87 | Role: ghostBookshelf.model('Role', Role),
88 | Roles: ghostBookshelf.collection('Roles', Roles)
89 | };
90 |
--------------------------------------------------------------------------------
/content/themes/casper/assets/js/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Main JS file for Casper behaviours
3 | */
4 |
5 | /* globals jQuery, document */
6 | (function ($, sr, undefined) {
7 | "use strict";
8 |
9 | var $document = $(document),
10 |
11 | // debouncing function from John Hann
12 | // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
13 | debounce = function (func, threshold, execAsap) {
14 | var timeout;
15 |
16 | return function debounced () {
17 | var obj = this, args = arguments;
18 | function delayed () {
19 | if (!execAsap) {
20 | func.apply(obj, args);
21 | }
22 | timeout = null;
23 | }
24 |
25 | if (timeout) {
26 | clearTimeout(timeout);
27 | } else if (execAsap) {
28 | func.apply(obj, args);
29 | }
30 |
31 | timeout = setTimeout(delayed, threshold || 100);
32 | };
33 | };
34 |
35 | $document.ready(function () {
36 |
37 | var $postContent = $(".post-content");
38 | $postContent.fitVids();
39 |
40 | function updateImageWidth() {
41 | var $this = $(this),
42 | contentWidth = $postContent.outerWidth(), // Width of the content
43 | imageWidth = this.naturalWidth; // Original image resolution
44 |
45 | if (imageWidth >= contentWidth) {
46 | $this.addClass('full-img');
47 | } else {
48 | $this.removeClass('full-img');
49 | }
50 | }
51 |
52 | var $img = $("img").on('load', updateImageWidth);
53 | function casperFullImg() {
54 | $img.each(updateImageWidth);
55 | }
56 |
57 | casperFullImg();
58 | $(window).smartresize(casperFullImg);
59 |
60 | $(".scroll-down").arctic_scroll();
61 |
62 | });
63 |
64 | // smartresize
65 | jQuery.fn[sr] = function(fn) { return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); };
66 |
67 | // Arctic Scroll by Paul Adam Davis
68 | // https://github.com/PaulAdamDavis/Arctic-Scroll
69 | $.fn.arctic_scroll = function (options) {
70 |
71 | var defaults = {
72 | elem: $(this),
73 | speed: 500
74 | },
75 |
76 | allOptions = $.extend(defaults, options);
77 |
78 | allOptions.elem.click(function (event) {
79 | event.preventDefault();
80 | var $this = $(this),
81 | $htmlBody = $('html, body'),
82 | offset = ($this.attr('data-offset')) ? $this.attr('data-offset') : false,
83 | position = ($this.attr('data-position')) ? $this.attr('data-position') : false,
84 | toMove;
85 |
86 | if (offset) {
87 | toMove = parseInt(offset);
88 | $htmlBody.stop(true, false).animate({scrollTop: ($(this.hash).offset().top + toMove) }, allOptions.speed);
89 | } else if (position) {
90 | toMove = parseInt(position);
91 | $htmlBody.stop(true, false).animate({scrollTop: toMove }, allOptions.speed);
92 | } else {
93 | $htmlBody.stop(true, false).animate({scrollTop: ($(this.hash).offset().top) }, allOptions.speed);
94 | }
95 | });
96 |
97 | };
98 | })(jQuery, 'smartresize');
99 |
--------------------------------------------------------------------------------
/core/server/email-templates/reset-password.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Hello!
25 | A request has been made to reset your password on {{ siteUrl }} .
26 | Please follow the link below to reset your password: Click here to reset your password
27 | Ghost
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "ghost",
3 | "version" : "0.5.3",
4 | "description" : "Just a blogging platform.",
5 | "author" : "Ghost Foundation",
6 | "homepage" : "http://ghost.org",
7 | "keywords" : [
8 | "ghost",
9 | "blog",
10 | "cms"
11 | ],
12 | "repository" : {
13 | "type": "git",
14 | "url": "git://github.com/TryGhost/Ghost.git"
15 | },
16 | "bugs" : "https://github.com/TryGhost/Ghost/issues",
17 | "contributors": "https://github.com/TryGhost/Ghost/graphs/contributors",
18 | "licenses" : [
19 | {
20 | "type": "MIT",
21 | "url": "https://raw.github.com/TryGhost/Ghost/master/LICENSE"
22 | }
23 | ],
24 | "main": "./core/index",
25 | "scripts": {
26 | "start": "node index",
27 | "test": "./node_modules/.bin/grunt validate --verbose"
28 | },
29 | "engines": {
30 | "node": "~0.10.0"
31 | },
32 | "engineStrict": true,
33 | "dependencies": {
34 | "bcryptjs": "0.7.10",
35 | "bluebird": "2.3.0",
36 | "body-parser": "1.8.2",
37 | "bookshelf": "0.7.6",
38 | "busboy": "0.2.8",
39 | "cheerio": "0.17.0",
40 | "colors": "0.6.2",
41 | "compression": "1.1.0",
42 | "connect-slashes": "1.2.0",
43 | "downsize": "0.0.5",
44 | "express": "4.9.2",
45 | "express-hbs": "0.7.11",
46 | "fs-extra": "0.8.1",
47 | "html-to-text": "^0.1.0",
48 | "knex": "0.6.21",
49 | "lodash": "2.4.1",
50 | "moment": "2.4.0",
51 | "morgan": "1.3.1",
52 | "node-uuid": "1.4.1",
53 | "nodemailer": "0.7.1",
54 | "oauth2orize": "1.0.1",
55 | "passport": "0.2.0",
56 | "passport-http-bearer": "1.0.1",
57 | "passport-oauth2-client-password": "0.1.1",
58 | "rss": "0.2.1",
59 | "semver": "2.2.1",
60 | "showdown": "https://github.com/ErisDS/showdown/archive/v0.3.2-ghost.tar.gz",
61 | "sqlite3": "2.2.7",
62 | "unidecode": "0.1.3",
63 | "validator": "3.4.0",
64 | "xml": "0.0.12"
65 | },
66 | "optionalDependencies": {
67 | "mysql": "2.1.1"
68 | },
69 | "devDependencies": {
70 | "blanket": "~1.1.6",
71 | "bower": "~1.3.10",
72 | "ember-template-compiler": "1.7.0",
73 | "grunt": "~0.4.5",
74 | "grunt-cli": "~0.1.13",
75 | "grunt-autoprefixer": "1.0.1",
76 | "grunt-concat-sourcemap": "~0.4.3",
77 | "grunt-contrib-clean": "~0.6.0",
78 | "grunt-contrib-compress": "~0.11.0",
79 | "grunt-contrib-concat": "~0.5.0",
80 | "grunt-contrib-copy": "~0.5.0",
81 | "grunt-contrib-jshint": "~0.10.0",
82 | "grunt-contrib-uglify": "~0.5.1",
83 | "grunt-contrib-watch": "~0.6.1",
84 | "grunt-docker": "~0.0.8",
85 | "grunt-ember-templates": "~0.4.21",
86 | "grunt-es6-module-transpiler": "~0.6.0",
87 | "grunt-express-server": "~0.4.19",
88 | "grunt-jscs": "~0.7.1",
89 | "grunt-mocha-cli": "~1.10.0",
90 | "grunt-sass": "~0.16.0",
91 | "grunt-shell": "~1.1.1",
92 | "grunt-update-submodules": "~0.4.1",
93 | "matchdep": "~0.3.0",
94 | "mocha": "~1.21.4",
95 | "nock": "0.47.0",
96 | "request": "~2.42.0",
97 | "require-dir": "~0.1.0",
98 | "rewire": "~2.1.0",
99 | "should": "~4.0.4",
100 | "sinon": "~1.10.3",
101 | "supertest": "~0.13.0",
102 | "top-gh-contribs": "0.0.2"
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/core/server/apps/proxy.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash'),
2 | api = require('../api'),
3 | helpers = require('../helpers'),
4 | filters = require('../filters'),
5 | generateProxyFunctions;
6 |
7 | generateProxyFunctions = function (name, permissions) {
8 | var getPermission = function (perm) {
9 | return permissions[perm];
10 | },
11 | getPermissionToMethod = function (perm, method) {
12 | var perms = getPermission(perm);
13 |
14 | if (!perms) {
15 | return false;
16 | }
17 |
18 | return _.find(perms, function (name) {
19 | return name === method;
20 | });
21 | },
22 | runIfPermissionToMethod = function (perm, method, wrappedFunc, context, args) {
23 | var permValue = getPermissionToMethod(perm, method);
24 |
25 | if (!permValue) {
26 | throw new Error('The App "' + name + '" attempted to perform an action or access a resource (' + perm + '.' + method + ') without permission.');
27 | }
28 |
29 | return wrappedFunc.apply(context, args);
30 | },
31 | checkRegisterPermissions = function (perm, registerMethod) {
32 | return _.wrap(registerMethod, function (origRegister, name) {
33 | return runIfPermissionToMethod(perm, name, origRegister, this, _.toArray(arguments).slice(1));
34 | });
35 | },
36 | passThruAppContextToApi = function (perm, apiMethods) {
37 | var appContext = {
38 | app: name
39 | };
40 |
41 | return _.reduce(apiMethods, function (memo, apiMethod, methodName) {
42 | memo[methodName] = function () {
43 | var args = _.toArray(arguments),
44 | options = args[args.length - 1];
45 |
46 | if (_.isObject(options)) {
47 | options.context = _.clone(appContext);
48 | }
49 | return apiMethod.apply({}, args);
50 | };
51 |
52 | return memo;
53 | }, {});
54 | },
55 | proxy;
56 |
57 | proxy = {
58 | filters: {
59 | register: checkRegisterPermissions('filters', filters.registerFilter.bind(filters)),
60 | deregister: checkRegisterPermissions('filters', filters.deregisterFilter.bind(filters))
61 | },
62 | helpers: {
63 | register: checkRegisterPermissions('helpers', helpers.registerThemeHelper.bind(helpers)),
64 | registerAsync: checkRegisterPermissions('helpers', helpers.registerAsyncThemeHelper.bind(helpers))
65 | },
66 | api: {
67 | posts: passThruAppContextToApi('posts',
68 | _.pick(api.posts, 'browse', 'read', 'edit', 'add', 'destroy')
69 | ),
70 | tags: passThruAppContextToApi('tags',
71 | _.pick(api.tags, 'browse')
72 | ),
73 | notifications: passThruAppContextToApi('notifications',
74 | _.pick(api.notifications, 'browse', 'add', 'destroy')
75 | ),
76 | settings: passThruAppContextToApi('settings',
77 | _.pick(api.settings, 'browse', 'read', 'edit')
78 | )
79 | }
80 | };
81 |
82 | return proxy;
83 | };
84 |
85 | function AppProxy(options) {
86 | if (!options.name) {
87 | throw new Error('Must provide an app name for api context');
88 | }
89 |
90 | if (!options.permissions) {
91 | throw new Error('Must provide app permissions');
92 | }
93 |
94 | _.extend(this, generateProxyFunctions(options.name, options.permissions));
95 | }
96 |
97 | module.exports = AppProxy;
98 |
--------------------------------------------------------------------------------
/core/server/email-templates/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Hello there!
25 | Excellent!
26 | You've successfully setup your email config for your Ghost blog over on {{ siteUrl }}
27 | If you hadn't, you wouldn't be reading this email, but you are, so it looks like all is well :)
28 | xoxo
29 | Team Ghost
30 | https://ghost.org
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/content/themes/casper/assets/fonts/icons.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Generated by IcoMoon
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/core/server/routes/api.js:
--------------------------------------------------------------------------------
1 | // # API routes
2 | var express = require('express'),
3 | api = require('../api'),
4 | apiRoutes;
5 |
6 | apiRoutes = function (middleware) {
7 | var router = express.Router();
8 | // alias delete with del
9 | router.del = router.delete;
10 |
11 | // ## Configuration
12 | router.get('/configuration', api.http(api.configuration.browse));
13 | router.get('/configuration/:key', api.http(api.configuration.read));
14 |
15 | // ## Posts
16 | router.get('/posts', api.http(api.posts.browse));
17 | router.post('/posts', api.http(api.posts.add));
18 | router.get('/posts/:id', api.http(api.posts.read));
19 | router.get('/posts/slug/:slug', api.http(api.posts.read));
20 | router.put('/posts/:id', api.http(api.posts.edit));
21 | router.del('/posts/:id', api.http(api.posts.destroy));
22 |
23 | // ## Settings
24 | router.get('/settings', api.http(api.settings.browse));
25 | router.get('/settings/:key', api.http(api.settings.read));
26 | router.put('/settings', api.http(api.settings.edit));
27 |
28 | // ## Users
29 | router.get('/users', api.http(api.users.browse));
30 | router.get('/users/:id', api.http(api.users.read));
31 | router.get('/users/slug/:slug', api.http(api.users.read));
32 | router.get('/users/email/:email', api.http(api.users.read));
33 | router.put('/users/password', api.http(api.users.changePassword));
34 | router.put('/users/owner', api.http(api.users.transferOwnership));
35 | router.put('/users/:id', api.http(api.users.edit));
36 | router.post('/users', api.http(api.users.add));
37 | router.del('/users/:id', api.http(api.users.destroy));
38 |
39 | // ## Tags
40 | router.get('/tags', api.http(api.tags.browse));
41 |
42 | // ## Roles
43 | router.get('/roles/', api.http(api.roles.browse));
44 |
45 | // ## Slugs
46 | router.get('/slugs/:type/:name', api.http(api.slugs.generate));
47 |
48 | // ## Themes
49 | router.get('/themes', api.http(api.themes.browse));
50 | router.put('/themes/:name', api.http(api.themes.edit));
51 |
52 | // ## Notifications
53 | router.get('/notifications', api.http(api.notifications.browse));
54 | router.post('/notifications', api.http(api.notifications.add));
55 | router.del('/notifications/:id', api.http(api.notifications.destroy));
56 |
57 | // ## DB
58 | router.get('/db', api.http(api.db.exportContent));
59 | router.post('/db', middleware.busboy, api.http(api.db.importContent));
60 | router.del('/db', api.http(api.db.deleteAllContent));
61 |
62 | // ## Mail
63 | router.post('/mail', api.http(api.mail.send));
64 | router.post('/mail/test', function (req, res) {
65 | api.http(api.mail.sendTest)(req, res);
66 | });
67 |
68 | // ## Authentication
69 | router.post('/authentication/passwordreset',
70 | middleware.spamForgottenPrevention,
71 | api.http(api.authentication.generateResetToken)
72 | );
73 | router.put('/authentication/passwordreset', api.http(api.authentication.resetPassword));
74 | router.post('/authentication/invitation', api.http(api.authentication.acceptInvitation));
75 | router.get('/authentication/invitation', api.http(api.authentication.isInvitation));
76 | router.post('/authentication/setup', api.http(api.authentication.setup));
77 | router.get('/authentication/setup', api.http(api.authentication.isSetup));
78 | router.post('/authentication/token',
79 | middleware.spamSigninPrevention,
80 | middleware.addClientSecret,
81 | middleware.authenticateClient,
82 | middleware.generateAccessToken
83 | );
84 | router.post('/authentication/revoke', api.http(api.authentication.revoke));
85 |
86 | // ## Uploads
87 | router.post('/uploads', middleware.busboy, api.http(api.uploads.add));
88 |
89 | return router;
90 | };
91 |
92 | module.exports = apiRoutes;
93 |
--------------------------------------------------------------------------------
/core/server/data/fixtures/permissions/index.js:
--------------------------------------------------------------------------------
1 | // # Permissions Fixtures
2 | // Sets up the permissions, and the default permissions_roles relationships
3 | var Promise = require('bluebird'),
4 | sequence = require('../../../utils/sequence'),
5 | _ = require('lodash'),
6 | errors = require('../../../errors'),
7 | models = require('../../../models'),
8 | fixtures = require('./permissions'),
9 |
10 | // private
11 | logInfo,
12 | addAllPermissions,
13 | addAllRolesPermissions,
14 | addRolesPermissionsForRole,
15 |
16 | // public
17 | populate,
18 | to003;
19 |
20 | logInfo = function logInfo(message) {
21 | errors.logInfo('Migrations', message);
22 | };
23 |
24 | addRolesPermissionsForRole = function (roleName) {
25 | var fixturesForRole = fixtures.permissions_roles[roleName],
26 | permissionsToAdd;
27 |
28 | return models.Role.forge({name: roleName}).fetch({withRelated: ['permissions']}).then(function (role) {
29 | return models.Permissions.forge().fetch().then(function (permissions) {
30 | if (_.isObject(fixturesForRole)) {
31 | permissionsToAdd = _.map(permissions.toJSON(), function (permission) {
32 | var objectPermissions = fixturesForRole[permission.object_type];
33 | if (objectPermissions === 'all') {
34 | return permission.id;
35 | } else if (_.isArray(objectPermissions) && _.contains(objectPermissions, permission.action_type)) {
36 | return permission.id;
37 | }
38 | return null;
39 | });
40 | }
41 |
42 | return role.permissions().attach(_.compact(permissionsToAdd));
43 | });
44 | });
45 | };
46 |
47 | addAllRolesPermissions = function () {
48 | var roleNames = _.keys(fixtures.permissions_roles),
49 | ops = [];
50 |
51 | _.each(roleNames, function (roleName) {
52 | ops.push(addRolesPermissionsForRole(roleName));
53 | });
54 |
55 | return Promise.all(ops);
56 | };
57 |
58 | addAllPermissions = function (options) {
59 | var ops = [];
60 | _.each(fixtures.permissions, function (permissions, objectType) {
61 | _.each(permissions, function (permission) {
62 | ops.push(function () {
63 | permission.object_type = objectType;
64 | return models.Permission.add(permission, options);
65 | });
66 | });
67 | });
68 |
69 | return sequence(ops);
70 | };
71 |
72 | // ## Populate
73 | populate = function (options) {
74 | logInfo('Populating permissions');
75 | // ### Ensure all permissions are added
76 | return addAllPermissions(options).then(function () {
77 | // ### Ensure all roles_permissions are added
78 | return addAllRolesPermissions();
79 | });
80 | };
81 |
82 | // ## Update
83 | // Update permissions to 003
84 | // Need to rename old permissions, and then add all of the missing ones
85 | to003 = function (options) {
86 | var ops = [];
87 |
88 | logInfo('Upgrading permissions');
89 |
90 | // To safely upgrade, we need to clear up the existing permissions and permissions_roles before recreating the new
91 | // full set of permissions defined as of version 003
92 | models.Permissions.forge().fetch().then(function (permissions) {
93 | logInfo('Removing old permissions');
94 | permissions.each(function (permission) {
95 | ops.push(permission.related('roles').detach().then(function () {
96 | return permission.destroy();
97 | }));
98 | });
99 | });
100 |
101 | // Now we can perfom the normal populate
102 | return Promise.all(ops).then(function () {
103 | return populate(options);
104 | });
105 | };
106 |
107 | module.exports = {
108 | populate: populate,
109 | to003: to003
110 | };
111 |
--------------------------------------------------------------------------------
/core/server/apps/index.js:
--------------------------------------------------------------------------------
1 |
2 | var _ = require('lodash'),
3 | Promise = require('bluebird'),
4 | errors = require('../errors'),
5 | api = require('../api'),
6 | loader = require('./loader'),
7 | // Holds the available apps
8 | availableApps = {};
9 |
10 | function getInstalledApps() {
11 | return api.settings.read({context: {internal: true}, key: 'installedApps'}).then(function (response) {
12 | var installed = response.settings[0];
13 |
14 | installed.value = installed.value || '[]';
15 |
16 | try {
17 | installed = JSON.parse(installed.value);
18 | } catch (e) {
19 | return Promise.reject(e);
20 | }
21 |
22 | return installed;
23 | });
24 | }
25 |
26 | function saveInstalledApps(installedApps) {
27 | return getInstalledApps().then(function (currentInstalledApps) {
28 | var updatedAppsInstalled = _.uniq(installedApps.concat(currentInstalledApps));
29 |
30 | return api.settings.edit({settings: [{key: 'installedApps', value: updatedAppsInstalled}]}, {context: {internal: true}});
31 | });
32 | }
33 |
34 | module.exports = {
35 | init: function () {
36 | var appsToLoad;
37 |
38 | try {
39 | // We have to parse the value because it's a string
40 | api.settings.read({context: {internal: true}, key: 'activeApps'}).then(function (response) {
41 | var aApps = response.settings[0];
42 |
43 | appsToLoad = JSON.parse(aApps.value) || [];
44 | });
45 | } catch (e) {
46 | errors.logError(
47 | 'Failed to parse activeApps setting value: ' + e.message,
48 | 'Your apps will not be loaded.',
49 | 'Check your settings table for typos in the activeApps value. It should look like: ["app-1", "app2"] (double quotes required).'
50 | );
51 |
52 | return Promise.resolve();
53 | }
54 |
55 | // Grab all installed apps, install any not already installed that are in appsToLoad.
56 | return getInstalledApps().then(function (installedApps) {
57 | var loadedApps = {},
58 | recordLoadedApp = function (name, loadedApp) {
59 | // After loading the app, add it to our hash of loaded apps
60 | loadedApps[name] = loadedApp;
61 |
62 | return Promise.resolve(loadedApp);
63 | },
64 | loadPromises = _.map(appsToLoad, function (app) {
65 | // If already installed, just activate the app
66 | if (_.contains(installedApps, app)) {
67 | return loader.activateAppByName(app).then(function (loadedApp) {
68 | return recordLoadedApp(app, loadedApp);
69 | });
70 | }
71 |
72 | // Install, then activate the app
73 | return loader.installAppByName(app).then(function () {
74 | return loader.activateAppByName(app);
75 | }).then(function (loadedApp) {
76 | return recordLoadedApp(app, loadedApp);
77 | });
78 | });
79 |
80 | return Promise.all(loadPromises).then(function () {
81 | // Save our installed apps to settings
82 | return saveInstalledApps(_.keys(loadedApps));
83 | }).then(function () {
84 | // Extend the loadedApps onto the available apps
85 | _.extend(availableApps, loadedApps);
86 | }).catch(function (err) {
87 | errors.logError(
88 | err.message || err,
89 | 'The app will not be loaded',
90 | 'Check with the app creator, or read the app documentation for more details on app requirements'
91 | );
92 | });
93 | });
94 | },
95 | availableApps: availableApps
96 | };
97 |
--------------------------------------------------------------------------------
/core/server/api/themes.js:
--------------------------------------------------------------------------------
1 | // # Themes API
2 | // RESTful API for Themes
3 | var Promise = require('bluebird'),
4 | _ = require('lodash'),
5 | canThis = require('../permissions').canThis,
6 | config = require('../config'),
7 | errors = require('../errors'),
8 | settings = require('./settings'),
9 | themes;
10 |
11 | /**
12 | * ## Themes API Methods
13 | *
14 | * **See:** [API Methods](index.js.html#api%20methods)
15 | */
16 | themes = {
17 | /**
18 | * ### Browse
19 | * Get a list of all the available themes
20 | * @param {{context}} options
21 | * @returns {Promise(Themes)}
22 | */
23 | browse: function browse(options) {
24 | options = options || {};
25 |
26 | return canThis(options.context).browse.theme().then(function () {
27 | return Promise.all([
28 | settings.read({key: 'activeTheme', context: {internal: true}}),
29 | config.paths.availableThemes
30 | ]).then(function (result) {
31 | var activeTheme = result[0].settings[0].value,
32 | availableThemes = result[1],
33 | themes = [],
34 | themeKeys = Object.keys(availableThemes);
35 |
36 | _.each(themeKeys, function (key) {
37 | if (key.indexOf('.') !== 0
38 | && key !== '_messages'
39 | && key !== 'README.md'
40 | ) {
41 | var item = {
42 | uuid: key
43 | };
44 |
45 | if (availableThemes[key].hasOwnProperty('package.json')) {
46 | item = _.merge(item, availableThemes[key]['package.json']);
47 | }
48 |
49 | item.active = item.uuid === activeTheme;
50 |
51 | themes.push(item);
52 | }
53 | });
54 |
55 | return {themes: themes};
56 | });
57 | }, function () {
58 | return Promise.reject(new errors.NoPermissionError('You do not have permission to browse themes.'));
59 | });
60 | },
61 |
62 | /**
63 | * ### Edit
64 | * Change the active theme
65 | * @param {Theme} object
66 | * @param {{context}} options
67 | * @returns {Promise(Theme)}
68 | */
69 | edit: function edit(object, options) {
70 | var themeName;
71 |
72 | // Check whether the request is properly formatted.
73 | if (!_.isArray(object.themes)) {
74 | return Promise.reject({type: 'BadRequest', message: 'Invalid request.'});
75 | }
76 |
77 | themeName = object.themes[0].uuid;
78 |
79 | return canThis(options.context).edit.theme().then(function () {
80 | return themes.browse(options).then(function (availableThemes) {
81 | var theme;
82 |
83 | // Check if the theme exists
84 | theme = _.find(availableThemes.themes, function (currentTheme) {
85 | return currentTheme.uuid === themeName;
86 | });
87 |
88 | if (!theme) {
89 | return Promise.reject(new errors.BadRequestError('Theme does not exist.'));
90 | }
91 |
92 | // Activate the theme
93 | return settings.edit(
94 | {settings: [{key: 'activeTheme', value: themeName}]}, {context: {internal: true}}
95 | ).then(function () {
96 | theme.active = true;
97 | return {themes: [theme]};
98 | });
99 | });
100 | }, function () {
101 | return Promise.reject(new errors.NoPermissionError('You do not have permission to edit themes.'));
102 | });
103 | }
104 | };
105 |
106 | module.exports = themes;
107 |
--------------------------------------------------------------------------------