├── .gitignore
├── Gruntfile.js
├── LICENSE
├── PRIVACY.md
├── README.md
├── azuredeploy.json
├── bower.json
├── config.example.js
├── config.js
├── content
├── apps
│ └── README.md
├── data
│ └── README.md
├── images
│ └── README.md
└── themes
│ └── casper
│ ├── LICENSE
│ ├── README.md
│ ├── assets
│ ├── css
│ │ └── screen.css
│ ├── fonts
│ │ ├── casper-icons.eot
│ │ ├── casper-icons.svg
│ │ ├── casper-icons.ttf
│ │ └── casper-icons.woff
│ └── js
│ │ ├── index.js
│ │ └── jquery.fitvids.js
│ ├── author.hbs
│ ├── default.hbs
│ ├── index.hbs
│ ├── package.json
│ ├── page.hbs
│ ├── partials
│ ├── loop.hbs
│ └── navigation.hbs
│ ├── post.hbs
│ └── tag.hbs
├── core
├── built
│ └── assets
│ │ ├── codemirror
│ │ ├── codemirror.css
│ │ └── codemirror.js
│ │ ├── fonts
│ │ ├── ghosticons.eot
│ │ ├── ghosticons.svg
│ │ ├── ghosticons.ttf
│ │ └── ghosticons.woff
│ │ ├── ghost.css
│ │ ├── ghost.js
│ │ ├── ghost.min.css
│ │ ├── ghost.min.js
│ │ ├── img
│ │ ├── 404-ghost.png
│ │ ├── 404-ghost@2x.png
│ │ ├── contributors
│ │ │ ├── AileenCGN
│ │ │ ├── ErisDS
│ │ │ ├── acburdine
│ │ │ ├── cobbspur
│ │ │ ├── dbalders
│ │ │ ├── eexit
│ │ │ ├── felixrieseberg
│ │ │ ├── gergelyke
│ │ │ ├── imbrian
│ │ │ ├── jaswilli
│ │ │ ├── juanpaco
│ │ │ ├── kevinansfield
│ │ │ ├── kirrg001
│ │ │ ├── sakulstra
│ │ │ ├── sebgie
│ │ │ ├── starcwl
│ │ │ ├── vkandy
│ │ │ └── zhenkyle
│ │ ├── ghost-logo.png
│ │ ├── ghosticon.jpg
│ │ ├── install-welcome.png
│ │ ├── invite-placeholder.png
│ │ ├── large.png
│ │ ├── loadingcat.gif
│ │ ├── medium.png
│ │ ├── slackicon.png
│ │ ├── small.png
│ │ ├── touch-icon-ipad.png
│ │ ├── touch-icon-iphone.png
│ │ ├── user-cover.png
│ │ ├── user-image.png
│ │ └── users.png
│ │ ├── tests.js
│ │ ├── vendor.css
│ │ ├── vendor.js
│ │ ├── vendor.min.css
│ │ └── vendor.min.js
├── index.js
├── server
│ ├── api
│ │ ├── authentication.js
│ │ ├── clients.js
│ │ ├── configuration.js
│ │ ├── db.js
│ │ ├── index.js
│ │ ├── mail.js
│ │ ├── notifications.js
│ │ ├── posts.js
│ │ ├── roles.js
│ │ ├── schedules.js
│ │ ├── settings.js
│ │ ├── slack.js
│ │ ├── slugs.js
│ │ ├── subscribers.js
│ │ ├── tags.js
│ │ ├── themes.js
│ │ ├── upload.js
│ │ ├── users.js
│ │ └── utils.js
│ ├── apps
│ │ ├── dependencies.js
│ │ ├── index.js
│ │ ├── loader.js
│ │ ├── permissions.js
│ │ ├── private-blogging
│ │ │ ├── index.js
│ │ │ ├── lib
│ │ │ │ ├── middleware.js
│ │ │ │ ├── router.js
│ │ │ │ └── views
│ │ │ │ │ └── private.hbs
│ │ │ ├── robots.txt
│ │ │ └── tests
│ │ │ │ ├── controller_spec.js
│ │ │ │ └── middleware_spec.js
│ │ ├── proxy.js
│ │ ├── sandbox.js
│ │ └── subscribers
│ │ │ ├── index.js
│ │ │ └── lib
│ │ │ ├── router.js
│ │ │ └── views
│ │ │ └── subscribe.hbs
│ ├── config
│ │ ├── index.js
│ │ └── url.js
│ ├── controllers
│ │ ├── admin.js
│ │ └── frontend
│ │ │ ├── channel-config.js
│ │ │ ├── channels.js
│ │ │ ├── context.js
│ │ │ ├── error.js
│ │ │ ├── fetch-data.js
│ │ │ ├── format-response.js
│ │ │ ├── index.js
│ │ │ ├── post-lookup.js
│ │ │ ├── render-channel.js
│ │ │ ├── secure.js
│ │ │ └── templates.js
│ ├── data
│ │ ├── db
│ │ │ ├── connection.js
│ │ │ └── index.js
│ │ ├── export
│ │ │ └── index.js
│ │ ├── import
│ │ │ ├── data-importer.js
│ │ │ ├── index.js
│ │ │ └── utils.js
│ │ ├── importer
│ │ │ ├── handlers
│ │ │ │ ├── image.js
│ │ │ │ ├── json.js
│ │ │ │ └── markdown.js
│ │ │ ├── importers
│ │ │ │ ├── data.js
│ │ │ │ └── image.js
│ │ │ └── index.js
│ │ ├── meta
│ │ │ ├── asset_url.js
│ │ │ ├── author_fb_url.js
│ │ │ ├── author_image.js
│ │ │ ├── author_url.js
│ │ │ ├── canonical_url.js
│ │ │ ├── context_object.js
│ │ │ ├── cover_image.js
│ │ │ ├── creator_url.js
│ │ │ ├── description.js
│ │ │ ├── excerpt.js
│ │ │ ├── index.js
│ │ │ ├── keywords.js
│ │ │ ├── modified_date.js
│ │ │ ├── og_type.js
│ │ │ ├── paginated_url.js
│ │ │ ├── published_date.js
│ │ │ ├── rss_url.js
│ │ │ ├── schema.js
│ │ │ ├── structured_data.js
│ │ │ ├── title.js
│ │ │ └── url.js
│ │ ├── migration
│ │ │ ├── 004
│ │ │ │ ├── 01-add-tour-column-to-users.js
│ │ │ │ ├── 02-add-sortorder-column-to-poststags.js
│ │ │ │ ├── 03-add-many-columns-to-clients.js
│ │ │ │ ├── 04-add-clienttrusteddomains-table.js
│ │ │ │ ├── 05-drop-unique-on-clients-secret.js
│ │ │ │ └── index.js
│ │ │ ├── 005
│ │ │ │ ├── 01-drop-hidden-column-from-tags.js
│ │ │ │ ├── 02-add-visibility-column-to-key-tables.js
│ │ │ │ ├── 03-add-mobiledoc-column-to-posts.js
│ │ │ │ ├── 04-add-social-media-columns-to-users.js
│ │ │ │ ├── 05-add-subscribers-table.js
│ │ │ │ └── index.js
│ │ │ ├── 006
│ │ │ │ └── index.js
│ │ │ ├── backup.js
│ │ │ ├── fixtures
│ │ │ │ ├── 004
│ │ │ │ │ ├── 01-move-jquery-with-alert.js
│ │ │ │ │ ├── 02-update-private-setting-type.js
│ │ │ │ │ ├── 03-update-password-setting-type.js
│ │ │ │ │ ├── 04-update-ghost-admin-client.js
│ │ │ │ │ ├── 05-add-ghost-frontend-client.js
│ │ │ │ │ ├── 06-clean-broken-tags.js
│ │ │ │ │ ├── 07-add-post-tag-order.js
│ │ │ │ │ ├── 08-add-post-fixture.js
│ │ │ │ │ └── index.js
│ │ │ │ ├── 005
│ │ │ │ │ ├── 01-update-ghost-client-secrets.js
│ │ │ │ │ ├── 02-add-ghost-scheduler-client.js
│ │ │ │ │ ├── 03-add-client-permissions.js
│ │ │ │ │ ├── 04-add-subscriber-permissions.js
│ │ │ │ │ └── index.js
│ │ │ │ ├── 006
│ │ │ │ │ ├── 01-transform-dates-into-utc.js
│ │ │ │ │ └── index.js
│ │ │ │ ├── fixtures.json
│ │ │ │ ├── index.js
│ │ │ │ ├── populate.js
│ │ │ │ ├── update.js
│ │ │ │ └── utils.js
│ │ │ ├── index.js
│ │ │ ├── populate.js
│ │ │ ├── reset.js
│ │ │ └── update.js
│ │ ├── schema
│ │ │ ├── checks.js
│ │ │ ├── clients
│ │ │ │ ├── index.js
│ │ │ │ ├── mysql.js
│ │ │ │ ├── pg.js
│ │ │ │ └── sqlite3.js
│ │ │ ├── commands.js
│ │ │ ├── default-settings.json
│ │ │ ├── index.js
│ │ │ ├── schema.js
│ │ │ └── versioning.js
│ │ ├── slack
│ │ │ └── index.js
│ │ ├── timezones.json
│ │ ├── validation
│ │ │ └── index.js
│ │ └── xml
│ │ │ ├── rss
│ │ │ └── index.js
│ │ │ ├── sitemap
│ │ │ ├── base-generator.js
│ │ │ ├── handler.js
│ │ │ ├── index-generator.js
│ │ │ ├── index.js
│ │ │ ├── manager.js
│ │ │ ├── page-generator.js
│ │ │ ├── post-generator.js
│ │ │ ├── tag-generator.js
│ │ │ ├── user-generator.js
│ │ │ └── utils.js
│ │ │ └── xmlrpc.js
│ ├── errors
│ │ ├── bad-request-error.js
│ │ ├── data-import-error.js
│ │ ├── database-not-populated.js
│ │ ├── database-version.js
│ │ ├── email-error.js
│ │ ├── incorrect-usage.js
│ │ ├── index.js
│ │ ├── internal-server-error.js
│ │ ├── maintenance.js
│ │ ├── method-not-allowed-error.js
│ │ ├── no-permission-error.js
│ │ ├── not-found-error.js
│ │ ├── request-too-large-error.js
│ │ ├── token-revocation-error.js
│ │ ├── too-many-requests-error.js
│ │ ├── unauthorized-error.js
│ │ ├── unsupported-media-type-error.js
│ │ ├── validation-error.js
│ │ └── version-mismatch-error.js
│ ├── events
│ │ └── index.js
│ ├── filters.js
│ ├── ghost-server.js
│ ├── helpers
│ │ ├── asset.js
│ │ ├── author.js
│ │ ├── body_class.js
│ │ ├── content.js
│ │ ├── date.js
│ │ ├── encode.js
│ │ ├── excerpt.js
│ │ ├── facebook_url.js
│ │ ├── foreach.js
│ │ ├── get.js
│ │ ├── ghost_foot.js
│ │ ├── ghost_head.js
│ │ ├── has.js
│ │ ├── image.js
│ │ ├── index.js
│ │ ├── input_email.js
│ │ ├── input_password.js
│ │ ├── is.js
│ │ ├── meta_description.js
│ │ ├── meta_title.js
│ │ ├── navigation.js
│ │ ├── page_url.js
│ │ ├── pagination.js
│ │ ├── plural.js
│ │ ├── post_class.js
│ │ ├── prev_next.js
│ │ ├── tags.js
│ │ ├── template.js
│ │ ├── title.js
│ │ ├── tpl
│ │ │ ├── navigation.hbs
│ │ │ ├── pagination.hbs
│ │ │ └── subscribe_form.hbs
│ │ ├── twitter_url.js
│ │ ├── url.js
│ │ └── utils.js
│ ├── i18n.js
│ ├── index.js
│ ├── mail
│ │ ├── GhostMailer.js
│ │ ├── index.js
│ │ ├── templates
│ │ │ ├── invite-user.html
│ │ │ ├── newsletter.html
│ │ │ ├── raw
│ │ │ │ ├── invite-user.html
│ │ │ │ ├── reset-password.html
│ │ │ │ ├── test.html
│ │ │ │ └── welcome.html
│ │ │ ├── reset-password.html
│ │ │ ├── test.html
│ │ │ └── welcome.html
│ │ └── utils.js
│ ├── middleware
│ │ ├── api
│ │ │ └── version-match.js
│ │ ├── auth-strategies.js
│ │ ├── auth.js
│ │ ├── cache-control.js
│ │ ├── check-ssl.js
│ │ ├── cors.js
│ │ ├── decide-is-admin.js
│ │ ├── index.js
│ │ ├── labs.js
│ │ ├── maintenance.js
│ │ ├── oauth.js
│ │ ├── redirect-to-setup.js
│ │ ├── serve-shared-file.js
│ │ ├── spam-prevention.js
│ │ ├── static-theme.js
│ │ ├── theme-handler.js
│ │ └── uncapitalise.js
│ ├── models
│ │ ├── accesstoken.js
│ │ ├── app-field.js
│ │ ├── app-setting.js
│ │ ├── app.js
│ │ ├── base
│ │ │ ├── index.js
│ │ │ ├── listeners.js
│ │ │ ├── token.js
│ │ │ └── utils.js
│ │ ├── client-trusted-domain.js
│ │ ├── client.js
│ │ ├── index.js
│ │ ├── permission.js
│ │ ├── plugins
│ │ │ ├── access-rules.js
│ │ │ ├── filter.js
│ │ │ ├── include-count.js
│ │ │ ├── index.js
│ │ │ └── pagination.js
│ │ ├── post.js
│ │ ├── refreshtoken.js
│ │ ├── role.js
│ │ ├── settings.js
│ │ ├── subscriber.js
│ │ ├── tag.js
│ │ └── user.js
│ ├── overrides.js
│ ├── permissions
│ │ ├── effective.js
│ │ └── index.js
│ ├── routes
│ │ ├── admin.js
│ │ ├── api.js
│ │ ├── frontend.js
│ │ └── index.js
│ ├── scheduling
│ │ ├── SchedulingBase.js
│ │ ├── SchedulingDefault.js
│ │ ├── index.js
│ │ ├── post-scheduling
│ │ │ └── index.js
│ │ └── utils.js
│ ├── storage
│ │ ├── base.js
│ │ ├── index.js
│ │ └── local-file-store.js
│ ├── translations
│ │ └── en.json
│ ├── update-check.js
│ ├── utils
│ │ ├── downzero.js
│ │ ├── gravatar.js
│ │ ├── index.js
│ │ ├── labs.js
│ │ ├── npm
│ │ │ └── preinstall.js
│ │ ├── parse-package-json.js
│ │ ├── pipeline.js
│ │ ├── read-csv.js
│ │ ├── read-directory.js
│ │ ├── read-themes.js
│ │ ├── sequence.js
│ │ ├── social-urls.js
│ │ ├── startup-check.js
│ │ └── validate-themes.js
│ └── views
│ │ ├── default.hbs
│ │ └── user-error.hbs
└── shared
│ ├── favicon.ico
│ ├── ghost-url.js
│ ├── ghost-url.min.js
│ ├── robots.txt
│ └── sitemap.xsl
├── docs
└── update.png
├── iisnode.yml
├── index.js
├── npm-shrinkwrap.json
├── package.json
└── web.config
/.gitignore:
--------------------------------------------------------------------------------
1 | b-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 |
10 | pids
11 | logs
12 | results
13 |
14 | npm-debug.log
15 | node_modules
16 | bower_components
17 | .bowerrc
18 | .idea/*
19 | *.iml
20 | *.sublime-*
21 | projectFilesBackup
22 |
23 | .DS_Store
24 |
25 | # vim-related
26 | [._]*.s[a-w][a-z]
27 | [._]s[a-w][a-z]
28 | *.un~
29 | Session.vim
30 | .netrwhist
31 | .vimrc
32 | *~
33 |
34 | # TernJS
35 | .tern-project
36 |
37 | # Ghost DB file
38 | *.db
39 | *.db-journal
40 |
41 | .build
42 | .dist
43 | .tmp
44 |
45 | # Changelog, which is autogenerated, not committed
46 | CHANGELOG.md
47 |
48 | # Casper generated files
49 | /core/test/functional/*.png
50 |
51 | # Coverage reports
52 | coverage.html
53 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013-2016 Ghost Foundation
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 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ghost",
3 | "dependencies": {
4 | "codemirror": "4.0.1",
5 | "Countable": "2.0.2",
6 | "device": "git://github.com/matthewhudson/device.js#5347a275b66020a0d4dfe9aad81a488f8cce448d",
7 | "ember": "1.10.0",
8 | "ember-data": "1.0.0-beta.14.1",
9 | "ember-load-initializers": "git://github.com/stefanpenner/ember-load-initializers.git#0.0.1",
10 | "ember-resolver": "git://github.com/stefanpenner/ember-jj-abrams-resolver.git#181251821cf513bb58d3e192faa13245a816f75e",
11 | "ember-simple-auth": "0.7.2",
12 | "fastclick": "1.0.0",
13 | "handlebars": "2.0.0",
14 | "ic-ajax": "1.0.1",
15 | "jquery": "1.11.0",
16 | "jquery-file-upload": "9.5.6",
17 | "jquery-hammerjs": "1.0.1",
18 | "jquery-ui": "1.10.4",
19 | "jqueryui-touch-punch": "furf/jquery-ui-touch-punch",
20 | "keymaster": "git://github.com/madrobby/keymaster#564ea42e07de40da8113a571f17ceae8802672ff",
21 | "loader.js": "git://github.com/stefanpenner/loader.js#1.0.0",
22 | "moment": "2.8.3",
23 | "nanoscroller": "0.8.4",
24 | "normalize-scss": "~3.0.1",
25 | "nprogress": "0.1.2",
26 | "showdown-ghost": "0.3.4",
27 | "validator-js": "3.28.0",
28 | "google-caja": "5669.0.0"
29 | },
30 | "devDependencies": {
31 | "ember-mocha": "~0.3.0",
32 | "ember-cli-test-loader": "dgeb/ember-cli-test-loader#test-agnostic",
33 | "ember-cli-shims": "stefanpenner/ember-cli-shims#~0.0.3"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/content/apps/README.md:
--------------------------------------------------------------------------------
1 | # Content / Apps
2 |
3 | Coming soon, Ghost apps will appear here.
--------------------------------------------------------------------------------
/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/README.md:
--------------------------------------------------------------------------------
1 | # Content / Images
2 |
3 | If using the standard file storage, Ghost will upload images to this directory.
--------------------------------------------------------------------------------
/content/themes/casper/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013-2016 Ghost Foundation
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/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-2016 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 |
--------------------------------------------------------------------------------
/content/themes/casper/assets/fonts/casper-icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AzureWebApps/Ghost-Azure/1d2954261f87216a769ed91d2fe82cdedd2bdadb/content/themes/casper/assets/fonts/casper-icons.eot
--------------------------------------------------------------------------------
/content/themes/casper/assets/fonts/casper-icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AzureWebApps/Ghost-Azure/1d2954261f87216a769ed91d2fe82cdedd2bdadb/content/themes/casper/assets/fonts/casper-icons.ttf
--------------------------------------------------------------------------------
/content/themes/casper/assets/fonts/casper-icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AzureWebApps/Ghost-Azure/1d2954261f87216a769ed91d2fe82cdedd2bdadb/content/themes/casper/assets/fonts/casper-icons.woff
--------------------------------------------------------------------------------
/content/themes/casper/assets/js/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Main JS file for Casper behaviours
3 | */
4 |
5 | /* globals jQuery, document */
6 | (function ($, undefined) {
7 | "use strict";
8 |
9 | var $document = $(document);
10 |
11 | $document.ready(function () {
12 |
13 | var $postContent = $(".post-content");
14 | $postContent.fitVids();
15 |
16 | $(".scroll-down").arctic_scroll();
17 |
18 | $(".menu-button, .nav-cover, .nav-close").on("click", function(e){
19 | e.preventDefault();
20 | $("body").toggleClass("nav-opened nav-closed");
21 | });
22 |
23 | });
24 |
25 | // Arctic Scroll by Paul Adam Davis
26 | // https://github.com/PaulAdamDavis/Arctic-Scroll
27 | $.fn.arctic_scroll = function (options) {
28 |
29 | var defaults = {
30 | elem: $(this),
31 | speed: 500
32 | },
33 |
34 | allOptions = $.extend(defaults, options);
35 |
36 | allOptions.elem.click(function (event) {
37 | event.preventDefault();
38 | var $this = $(this),
39 | $htmlBody = $('html, body'),
40 | offset = ($this.attr('data-offset')) ? $this.attr('data-offset') : false,
41 | position = ($this.attr('data-position')) ? $this.attr('data-position') : false,
42 | toMove;
43 |
44 | if (offset) {
45 | toMove = parseInt(offset);
46 | $htmlBody.stop(true, false).animate({scrollTop: ($(this.hash).offset().top + toMove) }, allOptions.speed);
47 | } else if (position) {
48 | toMove = parseInt(position);
49 | $htmlBody.stop(true, false).animate({scrollTop: toMove }, allOptions.speed);
50 | } else {
51 | $htmlBody.stop(true, false).animate({scrollTop: ($(this.hash).offset().top) }, allOptions.speed);
52 | }
53 | });
54 |
55 | };
56 | })(jQuery);
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 |
{{excerpt words="26"}} »
14 |{{{error.message}}}
15 | {{/if}} 16 | -------------------------------------------------------------------------------- /core/server/helpers/twitter_url.js: -------------------------------------------------------------------------------- 1 | // # Twitter URL Helper 2 | // Usage: `{{twitter_url}}` or `{{twitter_url author.twitter}}` 3 | // 4 | // Output a url for a twitter username 5 | // 6 | // We use the name twitter_url to match the helper for consistency: 7 | // jscs:disable requireCamelCaseOrUpperCaseIdentifiers 8 | 9 | var socialUrls = require('../utils/social-urls'), 10 | findKey = require('./utils').findKey, 11 | twitter_url; 12 | 13 | twitter_url = function twitter_url(username, options) { 14 | if (!options) { 15 | options = username; 16 | username = findKey('twitter', this, options.data.blog); 17 | } 18 | 19 | if (username) { 20 | return socialUrls.twitterUrl(username); 21 | } 22 | 23 | return null; 24 | }; 25 | 26 | module.exports = twitter_url; 27 | -------------------------------------------------------------------------------- /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 getMetaDataUrl = require('../data/meta/url'); 8 | 9 | function url(options) { 10 | var absolute = options && options.hash.absolute; 11 | 12 | return getMetaDataUrl(this, absolute); 13 | } 14 | 15 | module.exports = url; 16 | -------------------------------------------------------------------------------- /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 | inputTemplate: _.template(' />'), 9 | isProduction: process.env.NODE_ENV === 'production', 10 | // @TODO this can probably be made more generic and used in more places 11 | findKey: function findKey(key, object, data) { 12 | if (object && _.has(object, key) && !_.isEmpty(object[key])) { 13 | return object[key]; 14 | } 15 | 16 | if (data && _.has(data, key) && !_.isEmpty(data[key])) { 17 | return data[key]; 18 | } 19 | 20 | return null; 21 | }, 22 | parseVisibility: function parseVisibility(options) { 23 | if (!options.hash.visibility) { 24 | return ['public']; 25 | } 26 | 27 | return _.map(options.hash.visibility.split(','), _.trim); 28 | } 29 | }; 30 | 31 | module.exports = utils; 32 | -------------------------------------------------------------------------------- /core/server/mail/index.js: -------------------------------------------------------------------------------- 1 | exports.GhostMailer = require('./GhostMailer'); 2 | exports.utils = require('./utils'); 3 | -------------------------------------------------------------------------------- /core/server/mail/utils.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash').runInContext(), 2 | fs = require('fs'), 3 | Promise = require('bluebird'), 4 | path = require('path'), 5 | htmlToText = require('html-to-text'), 6 | config = require('../config'), 7 | templatesDir = path.resolve(__dirname, '..', 'mail', 'templates'); 8 | 9 | _.templateSettings.interpolate = /{{([\s\S]+?)}}/g; 10 | 11 | exports.generateContent = function generateContent(options) { 12 | var defaults, 13 | data; 14 | 15 | defaults = { 16 | siteUrl: config.forceAdminSSL ? (config.urlSSL || config.url) : config.url 17 | }; 18 | 19 | data = _.defaults(defaults, options.data); 20 | 21 | // read the proper email body template 22 | return Promise.promisify(fs.readFile)(path.join(templatesDir, options.template + '.html'), 'utf8') 23 | .then(function (content) { 24 | var compiled, 25 | htmlContent, 26 | textContent; 27 | 28 | // insert user-specific data into the email 29 | compiled = _.template(content); 30 | htmlContent = compiled(data); 31 | 32 | // generate a plain-text version of the same email 33 | textContent = htmlToText.fromString(htmlContent); 34 | 35 | return { 36 | html: htmlContent, 37 | text: textContent 38 | }; 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /core/server/middleware/api/version-match.js: -------------------------------------------------------------------------------- 1 | var errors = require('../../errors'), 2 | i18n = require('../../i18n'); 3 | 4 | function checkVersionMatch(req, res, next) { 5 | var requestVersion = req.get('X-Ghost-Version'), 6 | currentVersion = res.locals.safeVersion; 7 | 8 | if (requestVersion && requestVersion !== currentVersion) { 9 | return next(new errors.VersionMismatchError( 10 | i18n.t( 11 | 'errors.middleware.api.versionMismatch', 12 | {requestVersion: requestVersion, currentVersion: currentVersion} 13 | ) 14 | )); 15 | } 16 | 17 | next(); 18 | } 19 | 20 | module.exports = checkVersionMatch; 21 | -------------------------------------------------------------------------------- /core/server/middleware/cache-control.js: -------------------------------------------------------------------------------- 1 | // # CacheControl Middleware 2 | // Usage: cacheControl(profile), where profile is one of 'public' or 'private' 3 | // After: checkIsPrivate 4 | // Before: routes 5 | // App: Admin|Blog|API 6 | // 7 | // Allows each app to declare its own default caching rules 8 | 9 | var _ = require('lodash'), 10 | cacheControl; 11 | 12 | cacheControl = function cacheControl(options) { 13 | /*jslint unparam:true*/ 14 | var profiles = { 15 | public: 'public, max-age=0', 16 | private: 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0' 17 | }, 18 | output; 19 | 20 | if (_.isString(options) && profiles.hasOwnProperty(options)) { 21 | output = profiles[options]; 22 | } 23 | 24 | return function cacheControlHeaders(req, res, next) { 25 | if (output) { 26 | if (res.isPrivateBlog) { 27 | res.set({'Cache-Control': profiles.private}); 28 | } else { 29 | res.set({'Cache-Control': output}); 30 | } 31 | } 32 | next(); 33 | }; 34 | }; 35 | 36 | module.exports = cacheControl; 37 | -------------------------------------------------------------------------------- /core/server/middleware/check-ssl.js: -------------------------------------------------------------------------------- 1 | var config = require('../config'), 2 | url = require('url'), 3 | checkSSL; 4 | 5 | function isSSLrequired(isAdmin, configUrl, forceAdminSSL) { 6 | var forceSSL = url.parse(configUrl).protocol === 'https:' ? true : false; 7 | if (forceSSL || (isAdmin && forceAdminSSL)) { 8 | return true; 9 | } 10 | return false; 11 | } 12 | 13 | // The guts of checkSSL. Indicate forbidden or redirect according to configuration. 14 | // Required args: forceAdminSSL, url and urlSSL should be passed from config. reqURL from req.url 15 | function sslForbiddenOrRedirect(opt) { 16 | var forceAdminSSL = opt.forceAdminSSL, 17 | reqUrl = url.parse(opt.reqUrl), // expected to be relative-to-root 18 | baseUrl = url.parse(opt.configUrlSSL || opt.configUrl), 19 | response = { 20 | // Check if forceAdminSSL: { redirect: false } is set, which means 21 | // we should just deny non-SSL access rather than redirect 22 | isForbidden: (forceAdminSSL && forceAdminSSL.redirect !== undefined && !forceAdminSSL.redirect), 23 | 24 | redirectUrl: function redirectUrl(query) { 25 | return url.format({ 26 | protocol: 'https:', 27 | hostname: baseUrl.hostname, 28 | port: baseUrl.port, 29 | pathname: reqUrl.pathname, 30 | query: query 31 | }); 32 | } 33 | }; 34 | 35 | return response; 36 | } 37 | 38 | // Check to see if we should use SSL 39 | // and redirect if needed 40 | checkSSL = function checkSSL(req, res, next) { 41 | if (isSSLrequired(res.isAdmin, config.url, config.forceAdminSSL)) { 42 | if (!req.secure) { 43 | var response = sslForbiddenOrRedirect({ 44 | forceAdminSSL: config.forceAdminSSL, 45 | configUrlSSL: config.urlSSL, 46 | configUrl: config.url, 47 | reqUrl: req.url 48 | }); 49 | 50 | if (response.isForbidden) { 51 | return res.sendStatus(403); 52 | } else { 53 | return res.redirect(301, response.redirectUrl(req.query)); 54 | } 55 | } 56 | } 57 | next(); 58 | }; 59 | 60 | module.exports = checkSSL; 61 | -------------------------------------------------------------------------------- /core/server/middleware/decide-is-admin.js: -------------------------------------------------------------------------------- 1 | // # DecideIsAdmin Middleware 2 | // Usage: decideIsAdmin(request, result, next) 3 | // After: 4 | // Before: 5 | // App: Blog 6 | // 7 | // Helper function to determine if its an admin page. 8 | 9 | var decideIsAdmin; 10 | 11 | decideIsAdmin = function decideIsAdmin(req, res, next) { 12 | /*jslint unparam:true*/ 13 | res.isAdmin = req.url.lastIndexOf('/ghost/', 0) === 0; 14 | next(); 15 | }; 16 | 17 | module.exports = decideIsAdmin; 18 | -------------------------------------------------------------------------------- /core/server/middleware/labs.js: -------------------------------------------------------------------------------- 1 | var errors = require('../errors'), 2 | labsUtil = require('../utils/labs'), 3 | labs; 4 | 5 | labs = { 6 | subscribers: function subscribers(req, res, next) { 7 | if (labsUtil.isSet('subscribers') === true) { 8 | return next(); 9 | } else { 10 | return errors.handleAPIError(new errors.NotFoundError(), req, res, next); 11 | } 12 | } 13 | }; 14 | 15 | module.exports = labs; 16 | -------------------------------------------------------------------------------- /core/server/middleware/maintenance.js: -------------------------------------------------------------------------------- 1 | var config = require('../config'), 2 | i18n = require('../i18n'), 3 | errors = require('../errors'); 4 | 5 | module.exports = function (req, res, next) { 6 | if (config.maintenance.enabled) { 7 | return next(new errors.Maintenance( 8 | i18n.t('errors.general.maintenance') 9 | )); 10 | } 11 | 12 | next(); 13 | }; 14 | -------------------------------------------------------------------------------- /core/server/middleware/redirect-to-setup.js: -------------------------------------------------------------------------------- 1 | var api = require('../api'), 2 | config = require('../config'); 3 | 4 | // Redirect to setup if no user exists 5 | function redirectToSetup(req, res, next) { 6 | api.authentication.isSetup().then(function then(exists) { 7 | if (!exists.setup[0].status && !req.path.match(/\/setup\//)) { 8 | return res.redirect(config.paths.subdir + '/ghost/setup/'); 9 | } 10 | next(); 11 | }).catch(function handleError(err) { 12 | return next(new Error(err)); 13 | }); 14 | } 15 | 16 | module.exports = redirectToSetup; 17 | -------------------------------------------------------------------------------- /core/server/middleware/serve-shared-file.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'), 2 | fs = require('fs'), 3 | path = require('path'), 4 | config = require('../config'); 5 | 6 | // ### ServeSharedFile Middleware 7 | // Handles requests to robots.txt and favicon.ico (and caches them) 8 | function serveSharedFile(file, type, maxAge) { 9 | var content, 10 | corePath = config.paths.corePath, 11 | filePath, 12 | blogRegex = /(\{\{blog-url\}\})/g, 13 | apiRegex = /(\{\{api-url\}\})/g; 14 | 15 | filePath = file.match(/^shared/) ? path.join(corePath, file) : path.join(corePath, 'shared', file); 16 | 17 | return function serveSharedFile(req, res, next) { 18 | if (req.path === '/' + file) { 19 | if (content) { 20 | res.writeHead(200, content.headers); 21 | res.end(content.body); 22 | } else { 23 | fs.readFile(filePath, function readFile(err, buf) { 24 | if (err) { 25 | return next(err); 26 | } 27 | 28 | if (type === 'text/xsl' || type === 'text/plain' || type === 'application/javascript') { 29 | buf = buf.toString().replace(blogRegex, config.url.replace(/\/$/, '')); 30 | buf = buf.toString().replace(apiRegex, config.apiUrl()); 31 | } 32 | content = { 33 | headers: { 34 | 'Content-Type': type, 35 | 'Content-Length': buf.length, 36 | ETag: '"' + crypto.createHash('md5').update(buf, 'utf8').digest('hex') + '"', 37 | 'Cache-Control': 'public, max-age=' + maxAge 38 | }, 39 | body: buf 40 | }; 41 | res.writeHead(200, content.headers); 42 | res.end(content.body); 43 | }); 44 | } 45 | } else { 46 | next(); 47 | } 48 | }; 49 | } 50 | 51 | module.exports = serveSharedFile; 52 | -------------------------------------------------------------------------------- /core/server/middleware/static-theme.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'), 2 | express = require('express'), 3 | path = require('path'), 4 | config = require('../config'), 5 | utils = require('../utils'); 6 | 7 | function isBlackListedFileType(file) { 8 | var blackListedFileTypes = ['.hbs', '.md', '.json'], 9 | ext = path.extname(file); 10 | return _.includes(blackListedFileTypes, ext); 11 | } 12 | 13 | function isWhiteListedFile(file) { 14 | var whiteListedFiles = ['manifest.json'], 15 | base = path.basename(file); 16 | return _.includes(whiteListedFiles, base); 17 | } 18 | 19 | function forwardToExpressStatic(req, res, next) { 20 | if (!req.app.get('activeTheme')) { 21 | next(); 22 | } else { 23 | express.static( 24 | path.join(config.paths.themePath, req.app.get('activeTheme')), 25 | {maxAge: utils.ONE_YEAR_MS} 26 | )(req, res, next); 27 | } 28 | } 29 | 30 | function staticTheme() { 31 | return function blackListStatic(req, res, next) { 32 | if (!isWhiteListedFile(req.path) && isBlackListedFileType(req.path)) { 33 | return next(); 34 | } 35 | return forwardToExpressStatic(req, res, next); 36 | }; 37 | } 38 | 39 | module.exports = staticTheme; 40 | -------------------------------------------------------------------------------- /core/server/middleware/uncapitalise.js: -------------------------------------------------------------------------------- 1 | // # uncapitalise Middleware 2 | // Usage: uncapitalise(req, res, next) 3 | // After: 4 | // Before: 5 | // App: Admin|Blog|API 6 | // 7 | // Detect upper case in req.path. 8 | 9 | var utils = require('../utils'), 10 | uncapitalise; 11 | 12 | uncapitalise = function uncapitalise(req, res, next) { 13 | /*jslint unparam:true*/ 14 | var pathToTest = req.path, 15 | isSignupOrReset = req.path.match(/(\/ghost\/(signup|reset)\/)/i), 16 | isAPI = req.path.match(/(\/ghost\/api\/v[\d\.]+\/.*?\/)/i); 17 | 18 | if (isSignupOrReset) { 19 | pathToTest = isSignupOrReset[1]; 20 | } 21 | 22 | // Do not lowercase anything after /api/v0.1/ to protect :key/:slug 23 | if (isAPI) { 24 | pathToTest = isAPI[1]; 25 | } 26 | 27 | /** 28 | * In node < 0.11.1 req.path is not encoded, afterwards, it is always encoded such that | becomes %7C etc. 29 | * That encoding isn't useful here, as it triggers an extra uncapitalise redirect, so we decode the path first 30 | */ 31 | if (/[A-Z]/.test(decodeURIComponent(pathToTest))) { 32 | res.set('Cache-Control', 'public, max-age=' + utils.ONE_YEAR_S); 33 | // Adding baseUrl ensures subdirectories are kept 34 | res.redirect(301, (req.baseUrl ? req.baseUrl : '') + req.url.replace(pathToTest, pathToTest.toLowerCase())); 35 | } else { 36 | next(); 37 | } 38 | }; 39 | 40 | module.exports = uncapitalise; 41 | -------------------------------------------------------------------------------- /core/server/models/accesstoken.js: -------------------------------------------------------------------------------- 1 | var ghostBookshelf = require('./base'), 2 | Basetoken = require('./base/token'), 3 | events = require('../events'), 4 | 5 | Accesstoken, 6 | Accesstokens; 7 | 8 | Accesstoken = Basetoken.extend({ 9 | tableName: 'accesstokens', 10 | 11 | emitChange: function emitChange(event) { 12 | // Event named 'token' as access and refresh token will be merged in future, see #6626 13 | events.emit('token' + '.' + event, this); 14 | }, 15 | 16 | initialize: function initialize() { 17 | ghostBookshelf.Model.prototype.initialize.apply(this, arguments); 18 | 19 | this.on('created', function onCreated(model) { 20 | model.emitChange('added'); 21 | }); 22 | } 23 | }); 24 | 25 | Accesstokens = ghostBookshelf.Collection.extend({ 26 | model: Accesstoken 27 | }); 28 | 29 | module.exports = { 30 | Accesstoken: ghostBookshelf.model('Accesstoken', Accesstoken), 31 | Accesstokens: ghostBookshelf.collection('Accesstokens', Accesstokens) 32 | }; 33 | -------------------------------------------------------------------------------- /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 post() { 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 app() { 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/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 saving(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 then(slug) { 19 | self.set({slug: slug}); 20 | }); 21 | } 22 | }, 23 | 24 | permissions: function permissions() { 25 | return this.belongsToMany('Permission', 'permissions_apps'); 26 | }, 27 | 28 | settings: function settings() { 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 permittedOptions(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/models/client-trusted-domain.js: -------------------------------------------------------------------------------- 1 | var ghostBookshelf = require('./base'), 2 | 3 | ClientTrustedDomain, 4 | ClientTrustedDomains; 5 | 6 | ClientTrustedDomain = ghostBookshelf.Model.extend({ 7 | tableName: 'client_trusted_domains' 8 | }); 9 | 10 | ClientTrustedDomains = ghostBookshelf.Collection.extend({ 11 | model: ClientTrustedDomain 12 | }); 13 | 14 | module.exports = { 15 | ClientTrustedDomain: ghostBookshelf.model('ClientTrustedDomain', ClientTrustedDomain), 16 | ClientTrustedDomains: ghostBookshelf.collection('ClientTrustedDomains', ClientTrustedDomains) 17 | }; 18 | -------------------------------------------------------------------------------- /core/server/models/client.js: -------------------------------------------------------------------------------- 1 | var ghostBookshelf = require('./base'), 2 | crypto = require('crypto'), 3 | uuid = require('node-uuid'), 4 | 5 | Client, 6 | Clients; 7 | 8 | Client = ghostBookshelf.Model.extend({ 9 | 10 | tableName: 'clients', 11 | 12 | defaults: function defaults() { 13 | var env = process.env.NODE_ENV, 14 | secret = env.indexOf('testing') !== 0 ? crypto.randomBytes(6).toString('hex') : 'not_available'; 15 | 16 | return { 17 | uuid: uuid.v4(), 18 | secret: secret, 19 | status: 'development', 20 | type: 'ua' 21 | }; 22 | }, 23 | 24 | trustedDomains: function trustedDomains() { 25 | return this.hasMany('ClientTrustedDomain', 'client_id'); 26 | } 27 | }, { 28 | /** 29 | * Returns an array of keys permitted in a method's `options` hash, depending on the current method. 30 | * @param {String} methodName The name of the method to check valid options for. 31 | * @return {Array} Keys allowed in the `options` hash of the model's method. 32 | */ 33 | permittedOptions: function permittedOptions(methodName) { 34 | var options = ghostBookshelf.Model.permittedOptions(), 35 | 36 | // whitelists for the `options` hash argument on methods, by method name. 37 | // these are the only options that can be passed to Bookshelf / Knex. 38 | validOptions = { 39 | findOne: ['columns', 'withRelated'] 40 | }; 41 | 42 | if (validOptions[methodName]) { 43 | options = options.concat(validOptions[methodName]); 44 | } 45 | 46 | return options; 47 | } 48 | }); 49 | 50 | Clients = ghostBookshelf.Collection.extend({ 51 | model: Client 52 | }); 53 | 54 | module.exports = { 55 | Client: ghostBookshelf.model('Client', Client), 56 | Clients: ghostBookshelf.collection('Clients', Clients) 57 | }; 58 | -------------------------------------------------------------------------------- /core/server/models/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies 3 | */ 4 | 5 | var _ = require('lodash'), 6 | exports, 7 | models; 8 | 9 | // enable event listeners 10 | require('./base/listeners'); 11 | 12 | /** 13 | * Expose all models 14 | */ 15 | exports = module.exports; 16 | 17 | models = [ 18 | 'accesstoken', 19 | 'app-field', 20 | 'app-setting', 21 | 'app', 22 | 'client-trusted-domain', 23 | 'client', 24 | 'permission', 25 | 'post', 26 | 'refreshtoken', 27 | 'role', 28 | 'settings', 29 | 'subscriber', 30 | 'tag', 31 | 'user' 32 | ]; 33 | 34 | function init() { 35 | exports.Base = require('./base'); 36 | 37 | models.forEach(function (name) { 38 | _.extend(exports, require('./' + name)); 39 | }); 40 | } 41 | 42 | /** 43 | * Expose `init` 44 | */ 45 | 46 | exports.init = init; 47 | -------------------------------------------------------------------------------- /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 roles() { 11 | return this.belongsToMany('Role'); 12 | }, 13 | 14 | users: function users() { 15 | return this.belongsToMany('User'); 16 | }, 17 | 18 | apps: function apps() { 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 | -------------------------------------------------------------------------------- /core/server/models/plugins/access-rules.js: -------------------------------------------------------------------------------- 1 | // # Access Rules 2 | // 3 | // Extends Bookshelf.Model.force to take a 'context' option which provides information on how this query should 4 | // be treated in terms of data access rules - currently just detecting public requests 5 | module.exports = function (Bookshelf) { 6 | var model = Bookshelf.Model, 7 | Model; 8 | 9 | Model = Bookshelf.Model.extend({ 10 | /** 11 | * Cached copy of the context setup for this model instance 12 | */ 13 | _context: null, 14 | 15 | /** 16 | * ## Is Public Context? 17 | * A helper to determine if this is a public request or not 18 | * @returns {boolean} 19 | */ 20 | isPublicContext: function isPublicContext() { 21 | return !!(this._context && this._context.public); 22 | }, 23 | 24 | isInternalContext: function isInternalContext() { 25 | return !!(this._context && this._context.internal); 26 | } 27 | }, 28 | { 29 | /** 30 | * ## Forge 31 | * Ensure that context gets set as part of the forge 32 | * 33 | * @param {object} attributes 34 | * @param {object} options 35 | * @returns {Bookshelf.Model} model 36 | */ 37 | forge: function forge(attributes, options) { 38 | var self = model.forge.apply(this, arguments); 39 | 40 | if (options && options.context) { 41 | self._context = options.context; 42 | delete options.context; 43 | } 44 | 45 | return self; 46 | } 47 | }); 48 | 49 | Bookshelf.Model = Model; 50 | }; 51 | -------------------------------------------------------------------------------- /core/server/models/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | accessRules: require('./access-rules'), 3 | filter: require('./filter'), 4 | includeCount: require('./include-count'), 5 | pagination: require('./pagination') 6 | }; 7 | -------------------------------------------------------------------------------- /core/server/models/refreshtoken.js: -------------------------------------------------------------------------------- 1 | var ghostBookshelf = require('./base'), 2 | Basetoken = require('./base/token'), 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/overrides.js: -------------------------------------------------------------------------------- 1 | var moment = require('moment-timezone'); 2 | 3 | /** 4 | * force UTC 5 | * - you can require moment or moment-timezone, both is configured to UTC 6 | * - you are allowed to use new Date() to instantiate datetime values for models, because they are transformed into UTC in the model layer 7 | * - be careful when not working with models, every value from the native JS Date is local TZ 8 | * - be careful when you work with date operations, therefor always wrap a date into moment 9 | */ 10 | moment.tz.setDefault('UTC'); 11 | -------------------------------------------------------------------------------- /core/server/permissions/effective.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'), 2 | Promise = require('bluebird'), 3 | Models = require('../models'), 4 | errors = require('../errors'), 5 | i18n = require('../i18n'), 6 | effective; 7 | 8 | effective = { 9 | user: function (id) { 10 | return Models.User.findOne({id: id, status: 'all'}, {include: ['permissions', 'roles', 'roles.permissions']}) 11 | .then(function (foundUser) { 12 | // CASE: {context: {user: id}} where the id is not in our database 13 | if (!foundUser) { 14 | return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.user.userNotFound'))); 15 | } 16 | 17 | var seenPerms = {}, 18 | rolePerms = _.map(foundUser.related('roles').models, function (role) { 19 | return role.related('permissions').models; 20 | }), 21 | allPerms = [], 22 | user = foundUser.toJSON(); 23 | 24 | rolePerms.push(foundUser.related('permissions').models); 25 | 26 | _.each(rolePerms, function (rolePermGroup) { 27 | _.each(rolePermGroup, function (perm) { 28 | var key = perm.get('action_type') + '-' + perm.get('object_type') + '-' + perm.get('object_id'); 29 | 30 | // Only add perms once 31 | if (seenPerms[key]) { 32 | return; 33 | } 34 | 35 | allPerms.push(perm); 36 | seenPerms[key] = true; 37 | }); 38 | }); 39 | 40 | return {permissions: allPerms, roles: user.roles}; 41 | }, errors.logAndThrowError); 42 | }, 43 | 44 | app: function (appName) { 45 | return Models.App.findOne({name: appName}, {withRelated: ['permissions']}) 46 | .then(function (foundApp) { 47 | if (!foundApp) { 48 | return []; 49 | } 50 | 51 | return {permissions: foundApp.related('permissions').models}; 52 | }, errors.logAndThrowError); 53 | } 54 | }; 55 | 56 | module.exports = effective; 57 | -------------------------------------------------------------------------------- /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/routes/frontend.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | path = require('path'), 3 | config = require('../config'), 4 | frontend = require('../controllers/frontend'), 5 | channels = require('../controllers/frontend/channels'), 6 | utils = require('../utils'), 7 | 8 | frontendRoutes; 9 | 10 | frontendRoutes = function frontendRoutes() { 11 | var router = express.Router(), 12 | subdir = config.paths.subdir, 13 | routeKeywords = config.routeKeywords; 14 | 15 | // ### Admin routes 16 | router.get(/^\/(logout|signout)\/$/, function redirectToSignout(req, res) { 17 | utils.redirect301(res, subdir + '/ghost/signout/'); 18 | }); 19 | router.get(/^\/signup\/$/, function redirectToSignup(req, res) { 20 | utils.redirect301(res, subdir + '/ghost/signup/'); 21 | }); 22 | 23 | // redirect to /ghost and let that do the authentication to prevent redirects to /ghost//admin etc. 24 | router.get(/^\/((ghost-admin|admin|wp-admin|dashboard|signin|login)\/?)$/, function redirectToAdmin(req, res) { 25 | utils.redirect301(res, subdir + '/ghost/'); 26 | }); 27 | 28 | // Post Live Preview 29 | router.get('/' + routeKeywords.preview + '/:uuid', frontend.preview); 30 | 31 | // Channels 32 | router.use(channels.router()); 33 | 34 | // Default 35 | router.get('*', frontend.single); 36 | 37 | // setup routes for internal apps 38 | // @TODO: refactor this to be a proper app route hook for internal & external apps 39 | config.internalApps.forEach(function (appName) { 40 | var app = require(path.join(config.paths.internalAppPath, appName)); 41 | if (app.hasOwnProperty('setupRoutes')) { 42 | app.setupRoutes(router); 43 | } 44 | }); 45 | 46 | return router; 47 | }; 48 | 49 | module.exports = frontendRoutes; 50 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /core/server/scheduling/SchedulingBase.js: -------------------------------------------------------------------------------- 1 | function SchedulingBase() { 2 | Object.defineProperty(this, 'requiredFns', { 3 | value: ['schedule', 'unschedule', 'reschedule', 'run'], 4 | writable: false 5 | }); 6 | } 7 | 8 | module.exports = SchedulingBase; 9 | -------------------------------------------------------------------------------- /core/server/scheduling/index.js: -------------------------------------------------------------------------------- 1 | var postScheduling = require(__dirname + '/post-scheduling'); 2 | 3 | /** 4 | * scheduling modules: 5 | * - post scheduling: publish posts/pages when scheduled 6 | */ 7 | exports.init = function init(options) { 8 | options = options || {}; 9 | 10 | return postScheduling.init(options); 11 | }; 12 | -------------------------------------------------------------------------------- /core/server/scheduling/utils.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'), 2 | Promise = require('bluebird'), 3 | SchedulingBase = require(__dirname + '/SchedulingBase'), 4 | errors = require(__dirname + '/../errors'); 5 | 6 | exports.createAdapter = function (options) { 7 | options = options || {}; 8 | 9 | var adapter = null, 10 | activeAdapter = options.active, 11 | path = options.path; 12 | 13 | if (!activeAdapter) { 14 | return Promise.reject(new errors.IncorrectUsage('Please provide an active adapter.')); 15 | } 16 | 17 | /** 18 | * CASE: active adapter is a npm module 19 | */ 20 | try { 21 | adapter = new (require(activeAdapter))(options); 22 | } catch (err) { 23 | if (err.code !== 'MODULE_NOT_FOUND') { 24 | return Promise.reject(new errors.IncorrectUsage(err.message)); 25 | } 26 | } 27 | 28 | /** 29 | * CASE: active adapter is located in specific ghost path 30 | */ 31 | try { 32 | adapter = adapter || new (require(path + activeAdapter))(options); 33 | } catch (err) { 34 | if (err.code === 'MODULE_NOT_FOUND') { 35 | return Promise.reject(new errors.IncorrectUsage('MODULE_NOT_FOUND', activeAdapter)); 36 | } 37 | 38 | return Promise.reject(new errors.IncorrectUsage(err.message)); 39 | } 40 | 41 | if (!(adapter instanceof SchedulingBase)) { 42 | return Promise.reject(new errors.IncorrectUsage('Your adapter does not inherit from the SchedulingBase.')); 43 | } 44 | 45 | if (!adapter.requiredFns) { 46 | return Promise.reject(new errors.IncorrectUsage('Your adapter does not provide the minimum required functions.')); 47 | } 48 | 49 | if (_.xor(adapter.requiredFns, Object.keys(_.pick(Object.getPrototypeOf(adapter), adapter.requiredFns))).length) { 50 | return Promise.reject(new errors.IncorrectUsage('Your adapter does not provide the minimum required functions.')); 51 | } 52 | 53 | return Promise.resolve(adapter); 54 | }; 55 | -------------------------------------------------------------------------------- /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(), 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 | -------------------------------------------------------------------------------- /core/server/storage/index.js: -------------------------------------------------------------------------------- 1 | var errors = require('../errors'), 2 | config = require('../config'), 3 | storage = {}; 4 | 5 | function getStorage(storageChoice) { 6 | var storagePath, 7 | storageConfig; 8 | 9 | storageChoice = config.storage.active; 10 | storagePath = config.paths.storage; 11 | storageConfig = config.storage[storageChoice]; 12 | 13 | if (storage[storageChoice]) { 14 | return storage[storageChoice]; 15 | } 16 | 17 | try { 18 | // TODO: determine if storage has all the necessary methods. 19 | storage[storageChoice] = require(storagePath); 20 | } catch (e) { 21 | errors.logError(e); 22 | } 23 | 24 | // Instantiate and cache the storage module instance. 25 | storage[storageChoice] = new storage[storageChoice](storageConfig); 26 | 27 | return storage[storageChoice]; 28 | } 29 | 30 | module.exports.getStorage = getStorage; 31 | -------------------------------------------------------------------------------- /core/server/utils/gravatar.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'), 2 | config = require('../config'), 3 | crypto = require('crypto'), 4 | https = require('https'); 5 | 6 | module.exports.lookup = function lookup(userData, timeout) { 7 | var gravatarUrl = '//www.gravatar.com/avatar/' + 8 | crypto.createHash('md5').update(userData.email.toLowerCase().trim()).digest('hex') + 9 | '?s=250'; 10 | 11 | return new Promise(function gravatarRequest(resolve) { 12 | if (config.isPrivacyDisabled('useGravatar') || process.env.NODE_ENV.indexOf('testing') > -1) { 13 | return resolve(userData); 14 | } 15 | 16 | var request, timer, timerEnded = false; 17 | 18 | request = https.get('https:' + gravatarUrl + '&d=404&r=x', function (response) { 19 | clearTimeout(timer); 20 | if (response.statusCode !== 404 && !timerEnded) { 21 | gravatarUrl += '&d=mm&r=x'; 22 | userData.image = gravatarUrl; 23 | } 24 | 25 | resolve(userData); 26 | }); 27 | 28 | request.on('error', function () { 29 | clearTimeout(timer); 30 | // just resolve with no image url 31 | if (!timerEnded) { 32 | return resolve(userData); 33 | } 34 | }); 35 | 36 | timer = setTimeout(function () { 37 | timerEnded = true; 38 | request.abort(); 39 | return resolve(userData); 40 | }, timeout || 2000); 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /core/server/utils/labs.js: -------------------------------------------------------------------------------- 1 | var config = require('../config'), 2 | flagIsSet; 3 | 4 | flagIsSet = function flagIsSet(flag) { 5 | var labsConfig = config.labs; 6 | 7 | return labsConfig && labsConfig[flag] && labsConfig[flag] === true; 8 | }; 9 | 10 | module.exports.isSet = flagIsSet; 11 | -------------------------------------------------------------------------------- /core/server/utils/npm/preinstall.js: -------------------------------------------------------------------------------- 1 | var validVersions = process.env.npm_package_engines_node.split(' || '), 2 | currentVersion = process.versions.node, 3 | foundMatch = false, 4 | majMinRegex = /(\d+\.\d+)/, 5 | majorRegex = /^\d+/, 6 | minorRegex = /\d+$/, 7 | exitCodes = { 8 | NODE_VERSION_UNSUPPORTED: 231 9 | }; 10 | 11 | function doError() { 12 | console.error('\x1B[31mERROR: Unsupported version of Node'); 13 | console.error('\x1B[37mGhost supports LTS Node versions: ' + process.env.npm_package_engines_node); 14 | console.error('You are currently using version: ' + process.versions.node + '\033[0m'); 15 | console.error('\x1B[32mThis check can be overridden, see http://support.ghost.org/supported-node-versions/ for more info\033[0m'); 16 | 17 | process.exit(exitCodes.NODE_VERSION_UNSUPPORTED); 18 | } 19 | 20 | if (process.env.GHOST_NODE_VERSION_CHECK === 'false') { 21 | console.log('\x1B[33mSkipping Node version check\033[0m'); 22 | } else { 23 | try { 24 | currentVersion = currentVersion.match(majMinRegex)[0]; 25 | 26 | validVersions.forEach(function (version) { 27 | var matchChar = version.charAt(0), 28 | versionString = version.match(majMinRegex)[0]; 29 | 30 | if ( 31 | (matchChar === '~' && currentVersion === versionString) 32 | || (matchChar === '^' 33 | && currentVersion.match(majorRegex)[0] === versionString.match(majorRegex)[0] 34 | && currentVersion.match(minorRegex)[0] >= versionString.match(minorRegex)[0] 35 | ) 36 | ) { 37 | foundMatch = true; 38 | } 39 | }); 40 | 41 | if (foundMatch !== true) { 42 | doError(); 43 | } 44 | } catch (e) { 45 | doError(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core/server/utils/parse-package-json.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies 3 | */ 4 | 5 | var Promise = require('bluebird'), 6 | fs = require('fs'), 7 | i18n = require('../i18n'), 8 | 9 | readFile = Promise.promisify(fs.readFile); 10 | 11 | /** 12 | * Parse package.json and validate it has 13 | * all the required fields 14 | */ 15 | 16 | function parsePackageJson(path) { 17 | return readFile(path) 18 | .catch(function () { 19 | var err = new Error(i18n.t('errors.utils.parsepackagejson.couldNotReadPackage')); 20 | err.context = path; 21 | 22 | return Promise.reject(err); 23 | }) 24 | .then(function (source) { 25 | var hasRequiredKeys, json, err; 26 | 27 | try { 28 | json = JSON.parse(source); 29 | 30 | hasRequiredKeys = json.name && json.version; 31 | 32 | if (!hasRequiredKeys) { 33 | err = new Error(i18n.t('errors.utils.parsepackagejson.nameOrVersionMissing')); 34 | err.context = path; 35 | err.help = i18n.t('errors.utils.parsepackagejson.willBeRequired', {url: 'http://docs.ghost.org/themes/'}); 36 | 37 | return Promise.reject(err); 38 | } 39 | 40 | return json; 41 | } catch (parseError) { 42 | err = new Error(i18n.t('errors.utils.parsepackagejson.themeFileIsMalformed')); 43 | err.context = path; 44 | err.help = i18n.t('errors.utils.parsepackagejson.willBeRequired', {url: 'http://docs.ghost.org/themes/'}); 45 | 46 | return Promise.reject(err); 47 | } 48 | }); 49 | } 50 | 51 | /** 52 | * Expose `parsePackageJson` 53 | */ 54 | 55 | module.exports = parsePackageJson; 56 | -------------------------------------------------------------------------------- /core/server/utils/pipeline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Pipeline Utility 3 | * 4 | * Based on pipeline.js from when.js: 5 | * https://github.com/cujojs/when/blob/3.7.4/pipeline.js 6 | */ 7 | var Promise = require('bluebird'); 8 | 9 | function pipeline(tasks /* initial arguments */) { 10 | var args = Array.prototype.slice.call(arguments, 1), 11 | 12 | runTask = function (task, args) { 13 | // Self-optimizing function to run first task with multiple 14 | // args using apply, but subsequent tasks via direct invocation 15 | runTask = function (task, arg) { 16 | return task(arg); 17 | }; 18 | 19 | return task.apply(null, args); 20 | }; 21 | 22 | // Resolve any promises for the arguments passed in first 23 | return Promise.all(args).then(function (args) { 24 | // Iterate through the tasks passing args from one into the next 25 | return Promise.reduce(tasks, function (arg, task) { 26 | return runTask(task, arg); 27 | }, args); 28 | }); 29 | } 30 | 31 | module.exports = pipeline; 32 | -------------------------------------------------------------------------------- /core/server/utils/read-csv.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'), 2 | csvParser = require('csv-parser'), 3 | _ = require('lodash'), 4 | fs = require('fs'); 5 | 6 | function readCSV(options) { 7 | var columnsToExtract = options.columnsToExtract || [], 8 | results = [], rows = []; 9 | 10 | return new Promise(function (resolve, reject) { 11 | var readFile = fs.createReadStream(options.path); 12 | 13 | readFile.on('err', function (err) { 14 | reject(err); 15 | }) 16 | .pipe(csvParser()) 17 | .on('data', function (row) { 18 | rows.push(row); 19 | }) 20 | .on('end', function () { 21 | // If CSV is single column - return all values including header 22 | var headers = _.keys(rows[0]), result = {}, columnMap = {}; 23 | if (columnsToExtract.length === 1 && headers.length === 1) { 24 | results = _.map(rows, function (value) { 25 | result = {}; 26 | result[columnsToExtract[0].name] = value[headers[0]]; 27 | return result; 28 | }); 29 | 30 | // Add first row 31 | result = {}; 32 | result[columnsToExtract[0].name] = headers[0]; 33 | results = [result].concat(results); 34 | } else { 35 | // If there are multiple columns in csv file 36 | // try to match headers using lookup value 37 | 38 | _.map(columnsToExtract, function findMatches(column) { 39 | _.each(headers, function checkheader(header) { 40 | if (column.lookup.test(header)) { 41 | columnMap[column.name] = header; 42 | } 43 | }); 44 | }); 45 | 46 | results = _.map(rows, function evaluateRow(row) { 47 | var result = {}; 48 | _.each(columnMap, function returnMatches(value, key) { 49 | result[key] = row[value]; 50 | }); 51 | return result; 52 | }); 53 | } 54 | resolve(results); 55 | }); 56 | }); 57 | } 58 | 59 | module.exports = readCSV; 60 | -------------------------------------------------------------------------------- /core/server/utils/read-themes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies 3 | */ 4 | 5 | var readDirectory = require('./read-directory'), 6 | Promise = require('bluebird'), 7 | join = require('path').join, 8 | fs = require('fs'), 9 | 10 | statFile = Promise.promisify(fs.stat); 11 | 12 | /** 13 | * Read themes 14 | */ 15 | 16 | function readThemes(dir) { 17 | var originalTree; 18 | 19 | return readDirectory(dir) 20 | .tap(function (tree) { 21 | originalTree = tree; 22 | }) 23 | .then(Object.keys) 24 | .filter(function (file) { 25 | var path = join(dir, file); 26 | 27 | return statFile(path).then(function (stat) { 28 | return stat.isDirectory(); 29 | }); 30 | }) 31 | .then(function (directories) { 32 | var themes = {}; 33 | 34 | directories.forEach(function (name) { 35 | themes[name] = originalTree[name]; 36 | }); 37 | 38 | return themes; 39 | }); 40 | } 41 | 42 | /** 43 | * Expose `read-themes` 44 | */ 45 | 46 | module.exports = readThemes; 47 | -------------------------------------------------------------------------------- /core/server/utils/sequence.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'); 2 | 3 | /** 4 | * expects an array of functions returning a promise 5 | */ 6 | function sequence(tasks /* Any Arguments */) { 7 | var args = Array.prototype.slice.call(arguments, 1); 8 | 9 | return Promise.reduce(tasks, function (results, task) { 10 | return task.apply(this, args).then(function (result) { 11 | results.push(result); 12 | return results; 13 | }); 14 | }, []); 15 | } 16 | 17 | module.exports = sequence; 18 | -------------------------------------------------------------------------------- /core/server/utils/social-urls.js: -------------------------------------------------------------------------------- 1 | module.exports.twitterUrl = function twitterUrl(username) { 2 | // Creates the canonical twitter URL without the '@' 3 | return 'https://twitter.com/' + username.replace(/^@/, ''); 4 | }; 5 | 6 | module.exports.facebookUrl = function facebookUrl(username) { 7 | // Handles a starting slash, this shouldn't happen, but just in case 8 | return 'https://www.facebook.com/' + username.replace(/^\//, ''); 9 | }; 10 | -------------------------------------------------------------------------------- /core/server/utils/validate-themes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies 3 | */ 4 | 5 | var readThemes = require('./read-themes'), 6 | Promise = require('bluebird'), 7 | _ = require('lodash'), 8 | i18n = require('../i18n'); 9 | 10 | /** 11 | * Validate themes: 12 | * 13 | * 1. Check if theme has package.json 14 | */ 15 | 16 | function validateThemes(dir) { 17 | var result = { 18 | warnings: [], 19 | errors: [] 20 | }; 21 | 22 | return readThemes(dir) 23 | .tap(function (themes) { 24 | _.each(themes, function (theme, name) { 25 | var hasPackageJson, warning; 26 | 27 | hasPackageJson = theme['package.json'] !== undefined; 28 | 29 | if (!hasPackageJson) { 30 | warning = { 31 | message: i18n.t('errors.utils.validatethemes.themeWithNoPackage.message'), 32 | context: i18n.t('errors.utils.validatethemes.themeWithNoPackage.context', {name: name}), 33 | help: i18n.t('errors.utils.validatethemes.themeWithNoPackage.help', {url: 'http://docs.ghost.org/themes/'}) 34 | }; 35 | 36 | result.warnings.push(warning); 37 | } 38 | 39 | // if package.json is `null`, it means that it exists 40 | // but JSON.parse failed (invalid json syntax) 41 | if (hasPackageJson && theme['package.json'] === null) { 42 | warning = { 43 | message: i18n.t('errors.utils.validatethemes.malformedPackage.message'), 44 | context: i18n.t('errors.utils.validatethemes.malformedPackage.context', {name: name}), 45 | help: i18n.t('errors.utils.validatethemes.malformedPackage.help', {url: 'http://docs.ghost.org/themes/'}) 46 | }; 47 | 48 | result.warnings.push(warning); 49 | } 50 | }); 51 | }) 52 | .then(function () { 53 | var hasNotifications = result.warnings.length || result.errors.length; 54 | 55 | if (hasNotifications) { 56 | return Promise.reject(result); 57 | } 58 | }); 59 | } 60 | 61 | /** 62 | * Expose `validateThemes` 63 | */ 64 | 65 | module.exports = validateThemes; 66 | -------------------------------------------------------------------------------- /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, @ -------------------------------------------------------------------------------- /core/shared/ghost-url.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var apiUrl = '{{api-url}}', 5 | clientId, 6 | clientSecret, 7 | url, 8 | init; 9 | 10 | function generateQueryString(object) { 11 | var queries = [], 12 | i; 13 | 14 | if (!object) { 15 | return ''; 16 | } 17 | 18 | for (i in object) { 19 | if (object.hasOwnProperty(i) && (!!object[i] || object[i] === false)) { 20 | queries.push(i + '=' + encodeURIComponent(object[i])); 21 | } 22 | } 23 | 24 | if (queries.length) { 25 | return '?' + queries.join('&'); 26 | } 27 | return ''; 28 | } 29 | 30 | url = { 31 | api: function () { 32 | var args = Array.prototype.slice.call(arguments), 33 | queryOptions, 34 | requestUrl = apiUrl; 35 | 36 | queryOptions = args.pop(); 37 | 38 | if (queryOptions && typeof queryOptions !== 'object') { 39 | args.push(queryOptions); 40 | queryOptions = {}; 41 | } 42 | 43 | queryOptions = queryOptions || {}; 44 | 45 | queryOptions.client_id = clientId; 46 | queryOptions.client_secret = clientSecret; 47 | 48 | if (args.length) { 49 | args.forEach(function (el) { 50 | requestUrl += el.replace(/^\/|\/$/g, '') + '/'; 51 | }); 52 | } 53 | 54 | return requestUrl + generateQueryString(queryOptions); 55 | } 56 | }; 57 | 58 | init = function (options) { 59 | clientId = options.clientId ? options.clientId : ''; 60 | clientSecret = options.clientSecret ? options.clientSecret : ''; 61 | apiUrl = options.url ? options.url : (apiUrl.match(/{\{api-url}}/) ? '' : apiUrl); 62 | }; 63 | 64 | if (typeof window !== 'undefined') { 65 | window.ghost = window.ghost || {}; 66 | window.ghost.url = url; 67 | window.ghost.init = init; 68 | } 69 | 70 | if (typeof module !== 'undefined') { 71 | module.exports = { 72 | url: url, 73 | init: init 74 | }; 75 | } 76 | })(); 77 | -------------------------------------------------------------------------------- /core/shared/ghost-url.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";function a(a){var b,c=[];if(!a)return"";for(b in a)a.hasOwnProperty(b)&&(a[b]||a[b]===!1)&&c.push(b+"="+encodeURIComponent(a[b]));return c.length?"?"+c.join("&"):""}var b,c,d,e,f="{{api-url}}";d={api:function(){var d,e=Array.prototype.slice.call(arguments),g=f;return d=e.pop(),d&&"object"!=typeof d&&(e.push(d),d={}),d=d||{},d.client_id=b,d.client_secret=c,e.length&&e.forEach(function(a){g+=a.replace(/^\/|\/$/g,"")+"/"}),g+a(d)}},e=function(a){b=a.clientId?a.clientId:"",c=a.clientSecret?a.clientSecret:"",f=a.url?a.url:f.match(/{\{api-url}}/)?"":f},"undefined"!=typeof window&&(window.ghost=window.ghost||{},window.ghost.url=d,window.ghost.init=e),"undefined"!=typeof module&&(module.exports={url:d,init:e})}(); -------------------------------------------------------------------------------- /core/shared/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Sitemap: {{blog-url}}/sitemap.xml 3 | Disallow: /ghost/ 4 | -------------------------------------------------------------------------------- /docs/update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureWebApps/Ghost-Azure/1d2954261f87216a769ed91d2fe82cdedd2bdadb/docs/update.png -------------------------------------------------------------------------------- /iisnode.yml: -------------------------------------------------------------------------------- 1 | node_env: production 2 | loggingEnabled: true 3 | enableXFF: true 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // # Ghost Startup 2 | // Orchestrates the startup of Ghost when run from command line. 3 | 4 | var express, 5 | ghost, 6 | parentApp, 7 | errors; 8 | 9 | require('./core/server/overrides'); 10 | 11 | // Make sure dependencies are installed and file system permissions are correct. 12 | require('./core/server/utils/startup-check').check(); 13 | 14 | // Proceed with startup 15 | express = require('express'); 16 | ghost = require('./core'); 17 | errors = require('./core/server/errors'); 18 | 19 | // Create our parent express app instance. 20 | parentApp = express(); 21 | 22 | // Call Ghost to get an instance of GhostServer 23 | ghost().then(function (ghostServer) { 24 | // Mount our Ghost instance on our desired subdirectory path if it exists. 25 | parentApp.use(ghostServer.config.paths.subdir, ghostServer.rootApp); 26 | 27 | // Let Ghost handle starting our server instance. 28 | ghostServer.start(parentApp); 29 | }).catch(function (err) { 30 | errors.logErrorAndExit(err, err.context, err.help); 31 | }); 32 | -------------------------------------------------------------------------------- /web.config: -------------------------------------------------------------------------------- 1 | 2 |