├── core ├── server │ ├── web │ │ ├── admin │ │ │ ├── views │ │ │ │ └── .gitkeep │ │ │ ├── index.js │ │ │ ├── middleware.js │ │ │ ├── serviceworker.js │ │ │ └── controller.js │ │ ├── site │ │ │ ├── index.js │ │ │ └── routes.js │ │ ├── index.js │ │ ├── shared │ │ │ ├── index.js │ │ │ └── middlewares │ │ │ │ ├── image │ │ │ │ └── index.js │ │ │ │ ├── api │ │ │ │ └── index.js │ │ │ │ ├── validation │ │ │ │ ├── index.js │ │ │ │ └── profile-image.js │ │ │ │ ├── update-user-last-seen.js │ │ │ │ ├── emit-events.js │ │ │ │ ├── labs.js │ │ │ │ ├── maintenance.js │ │ │ │ ├── pretty-urls.js │ │ │ │ ├── ghost-locals.js │ │ │ │ ├── admin-redirects.js │ │ │ │ ├── frontend-client.js │ │ │ │ └── log-request.js │ │ └── api │ │ │ └── v2 │ │ │ └── content │ │ │ └── middleware.js │ ├── apps │ │ ├── private-blogging │ │ │ ├── robots.txt │ │ │ └── lib │ │ │ │ └── helpers │ │ │ │ └── index.js │ │ ├── amp │ │ │ ├── lib │ │ │ │ └── helpers │ │ │ │ │ └── index.js │ │ │ └── index.js │ │ └── subscribers │ │ │ ├── index.js │ │ │ └── lib │ │ │ └── helpers │ │ │ └── index.js │ ├── api │ │ ├── shared │ │ │ ├── serializers │ │ │ │ ├── output │ │ │ │ │ └── index.js │ │ │ │ ├── input │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ ├── utils │ │ │ │ ├── index.js │ │ │ │ └── options.js │ │ │ ├── validators │ │ │ │ ├── input │ │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── v2 │ │ │ ├── utils │ │ │ │ ├── validators │ │ │ │ │ ├── output │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── input │ │ │ │ │ │ ├── schemas │ │ │ │ │ │ │ ├── images-upload.json │ │ │ │ │ │ │ ├── tags-edit.json │ │ │ │ │ │ │ ├── pages-add.json │ │ │ │ │ │ │ ├── posts-add.json │ │ │ │ │ │ │ ├── pages-edit.json │ │ │ │ │ │ │ ├── posts-edit.json │ │ │ │ │ │ │ ├── tags-add.json │ │ │ │ │ │ │ └── images.json │ │ │ │ │ │ ├── tags.js │ │ │ │ │ │ ├── pages.js │ │ │ │ │ │ ├── posts.js │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ ├── users.js │ │ │ │ │ │ └── invites.js │ │ │ │ │ └── utils │ │ │ │ │ │ └── strip-keyword.js │ │ │ │ ├── serializers │ │ │ │ │ ├── output │ │ │ │ │ │ ├── redirects.js │ │ │ │ │ │ ├── preview.js │ │ │ │ │ │ ├── oembed.js │ │ │ │ │ │ ├── site.js │ │ │ │ │ │ ├── config.js │ │ │ │ │ │ ├── slugs.js │ │ │ │ │ │ ├── images.js │ │ │ │ │ │ ├── actions.js │ │ │ │ │ │ ├── webhooks.js │ │ │ │ │ │ ├── utils │ │ │ │ │ │ │ └── date.js │ │ │ │ │ │ ├── mail.js │ │ │ │ │ │ ├── themes.js │ │ │ │ │ │ ├── invites.js │ │ │ │ │ │ ├── members.js │ │ │ │ │ │ ├── authors.js │ │ │ │ │ │ ├── tags.js │ │ │ │ │ │ ├── all.js │ │ │ │ │ │ ├── notifications.js │ │ │ │ │ │ ├── pages.js │ │ │ │ │ │ └── posts.js │ │ │ │ │ ├── index.js │ │ │ │ │ └── input │ │ │ │ │ │ ├── integrations.js │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ ├── settings.js │ │ │ │ │ │ ├── authors.js │ │ │ │ │ │ ├── db.js │ │ │ │ │ │ ├── users.js │ │ │ │ │ │ └── tags.js │ │ │ │ └── index.js │ │ │ ├── slack.js │ │ │ ├── roles.js │ │ │ ├── settings-public.js │ │ │ ├── images.js │ │ │ ├── site.js │ │ │ ├── members.js │ │ │ └── config.js │ │ ├── index.js │ │ └── v0.1 │ │ │ └── slack.js │ ├── services │ │ ├── rss │ │ │ ├── index.js │ │ │ ├── renderer.js │ │ │ └── cache.js │ │ ├── themes │ │ │ ├── config │ │ │ │ ├── defaults.json │ │ │ │ └── index.js │ │ │ ├── engines │ │ │ │ ├── defaults.json │ │ │ │ └── index.js │ │ │ ├── engine.js │ │ │ ├── list.js │ │ │ └── loader.js │ │ ├── mail │ │ │ └── index.js │ │ ├── webhooks │ │ │ ├── index.js │ │ │ └── payload.js │ │ ├── routing │ │ │ ├── middlewares │ │ │ │ └── index.js │ │ │ ├── helpers │ │ │ │ ├── error.js │ │ │ │ ├── secure.js │ │ │ │ ├── render-entries.js │ │ │ │ ├── render-entry.js │ │ │ │ ├── renderer.js │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ ├── controllers │ │ │ │ └── index.js │ │ │ └── PreviewRouter.js │ │ ├── url │ │ │ └── index.js │ │ ├── auth │ │ │ ├── api-key │ │ │ │ └── index.js │ │ │ ├── session │ │ │ │ └── index.js │ │ │ ├── passport.js │ │ │ └── index.js │ │ ├── settings │ │ │ ├── default-routes.yaml │ │ │ └── public.js │ │ ├── members │ │ │ └── index.js │ │ └── permissions │ │ │ └── index.js │ ├── lib │ │ ├── members │ │ │ ├── static │ │ │ │ ├── gateway │ │ │ │ │ └── index.html │ │ │ │ └── auth │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── styles │ │ │ │ │ └── members.css │ │ │ │ │ ├── components │ │ │ │ │ ├── FormSubmit.js │ │ │ │ │ ├── FormHeaderCTA.js │ │ │ │ │ ├── FormFooter.js │ │ │ │ │ ├── FormHeader.js │ │ │ │ │ ├── NameInput.js │ │ │ │ │ ├── EmailInput.js │ │ │ │ │ ├── PasswordInput.js │ │ │ │ │ ├── CheckoutForm.js │ │ │ │ │ └── FormInput.js │ │ │ │ │ ├── postcss.config.js │ │ │ │ │ ├── assets │ │ │ │ │ └── images │ │ │ │ │ │ ├── icon-right-arrow.svg │ │ │ │ │ │ ├── icon-email.svg │ │ │ │ │ │ ├── icon-lock.svg │ │ │ │ │ │ ├── ghost-logo.svg │ │ │ │ │ │ └── icon-name.svg │ │ │ │ │ ├── index.js │ │ │ │ │ ├── pages │ │ │ │ │ ├── PasswordResetSentPage.js │ │ │ │ │ ├── RequestPasswordResetPage.js │ │ │ │ │ └── ResetPasswordPage.js │ │ │ │ │ └── package.json │ │ │ └── util.js │ │ ├── mobiledoc │ │ │ ├── atoms │ │ │ │ ├── index.js │ │ │ │ └── soft-return.js │ │ │ ├── cards │ │ │ │ ├── hr.js │ │ │ │ ├── index.js │ │ │ │ ├── card-markdown.js │ │ │ │ ├── html.js │ │ │ │ ├── code.js │ │ │ │ └── embed.js │ │ │ ├── index.js │ │ │ └── create-card.js │ │ ├── social │ │ │ ├── index.js │ │ │ └── urls.js │ │ ├── fs │ │ │ ├── index.js │ │ │ ├── package-json │ │ │ │ └── index.js │ │ │ └── zip-folder.js │ │ ├── common │ │ │ ├── index.js │ │ │ ├── logging.js │ │ │ └── events.js │ │ ├── constants.js │ │ ├── security │ │ │ ├── index.js │ │ │ ├── url.js │ │ │ ├── password.js │ │ │ └── identifier.js │ │ ├── image │ │ │ └── index.js │ │ ├── promise │ │ │ └── sequence.js │ │ ├── request.js │ │ └── ghost-version.js │ ├── public │ │ ├── favicon.ico │ │ ├── 404-ghost.png │ │ ├── 404-ghost@2x.png │ │ ├── robots.txt │ │ └── ghost-sdk.min.js │ ├── data │ │ ├── schema │ │ │ ├── fixtures │ │ │ │ └── index.js │ │ │ ├── clients │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ └── checks.js │ │ ├── migrations │ │ │ ├── hooks │ │ │ │ ├── init │ │ │ │ │ ├── index.js │ │ │ │ │ ├── before.js │ │ │ │ │ └── shutdown.js │ │ │ │ └── migrate │ │ │ │ │ ├── afterEach.js │ │ │ │ │ ├── beforeEach.js │ │ │ │ │ ├── index.js │ │ │ │ │ ├── before.js │ │ │ │ │ └── shutdown.js │ │ │ ├── versions │ │ │ │ ├── 2.13 │ │ │ │ │ └── 1-remove-empty-strings.js │ │ │ │ └── 2.0 │ │ │ │ │ └── 6-replace-fixture-posts.js │ │ │ └── init │ │ │ │ └── 2-create-fixtures.js │ │ ├── db │ │ │ └── index.js │ │ ├── meta │ │ │ ├── rss_url.js │ │ │ ├── og_type.js │ │ │ ├── published_date.js │ │ │ ├── keywords.js │ │ │ ├── canonical_url.js │ │ │ ├── amp_url.js │ │ │ ├── modified_date.js │ │ │ ├── author_image.js │ │ │ ├── creator_url.js │ │ │ ├── author_fb_url.js │ │ │ ├── author_url.js │ │ │ ├── excerpt.js │ │ │ ├── context_object.js │ │ │ ├── blog_logo.js │ │ │ ├── og_image.js │ │ │ ├── cover_image.js │ │ │ └── twitter_image.js │ │ ├── xml │ │ │ └── sitemap │ │ │ │ ├── tag-generator.js │ │ │ │ ├── page-generator.js │ │ │ │ ├── post-generator.js │ │ │ │ ├── utils.js │ │ │ │ └── user-generator.js │ │ └── importer │ │ │ └── importers │ │ │ └── data │ │ │ ├── subscribers.js │ │ │ └── roles.js │ ├── models │ │ ├── relations │ │ │ └── index.js │ │ ├── plugins │ │ │ ├── index.js │ │ │ └── transaction-events.js │ │ ├── author.js │ │ ├── tag-public.js │ │ ├── refreshtoken.js │ │ ├── app-field.js │ │ ├── app-setting.js │ │ ├── client-trusted-domain.js │ │ └── accesstoken.js │ ├── helpers │ │ ├── tpl │ │ │ ├── navigation.hbs │ │ │ ├── pagination.hbs │ │ │ └── subscribe_form.hbs │ │ ├── encode.js │ │ ├── title.js │ │ ├── meta_title.js │ │ ├── meta_description.js │ │ ├── page_url.js │ │ ├── asset.js │ │ ├── lang.js │ │ ├── url.js │ │ ├── twitter_url.js │ │ ├── facebook_url.js │ │ └── is.js │ ├── adapters │ │ └── scheduling │ │ │ ├── SchedulingBase.js │ │ │ └── index.js │ ├── config │ │ └── env │ │ │ ├── config.production.json │ │ │ └── config.development.json │ └── analytics-events.js ├── test │ ├── utils │ │ ├── fixtures │ │ │ ├── settings │ │ │ │ ├── test.yml │ │ │ │ ├── notyaml.md │ │ │ │ ├── badroutes.yaml │ │ │ │ ├── goodroutes.yaml │ │ │ │ ├── routes.yaml │ │ │ │ └── newroutes.yaml │ │ │ ├── test.hbs │ │ │ ├── themes │ │ │ │ ├── test-theme │ │ │ │ │ ├── post.hbs │ │ │ │ │ ├── default.hbs │ │ │ │ │ ├── something.hbs │ │ │ │ │ ├── index.hbs │ │ │ │ │ ├── home.hbs │ │ │ │ │ ├── assets │ │ │ │ │ │ ├── screenshot-desktop.jpg │ │ │ │ │ │ └── screenshot-mobile.jpg │ │ │ │ │ └── podcast │ │ │ │ │ │ └── rss.hbs │ │ │ │ ├── test-theme-channels │ │ │ │ │ ├── default.hbs │ │ │ │ │ ├── channel2.hbs │ │ │ │ │ └── channel3.hbs │ │ │ │ ├── casper │ │ │ │ │ ├── locales │ │ │ │ │ │ ├── de.json │ │ │ │ │ │ └── en.json │ │ │ │ │ ├── assets │ │ │ │ │ │ ├── screenshot-desktop.jpg │ │ │ │ │ │ └── screenshot-mobile.jpg │ │ │ │ │ └── partials │ │ │ │ │ │ ├── icons │ │ │ │ │ │ ├── facebook.hbs │ │ │ │ │ │ ├── infinity.hbs │ │ │ │ │ │ ├── rss.hbs │ │ │ │ │ │ ├── avatar.hbs │ │ │ │ │ │ ├── point.hbs │ │ │ │ │ │ ├── location.hbs │ │ │ │ │ │ ├── website.hbs │ │ │ │ │ │ └── twitter.hbs │ │ │ │ │ │ └── byline-single.hbs │ │ │ │ ├── broken-theme │ │ │ │ │ └── package.json │ │ │ │ ├── casper.zip │ │ │ │ ├── invalid.zip │ │ │ │ ├── valid.zip │ │ │ │ ├── warnings.zip │ │ │ │ └── casper-1.4 │ │ │ │ │ ├── README.md │ │ │ │ │ └── partials │ │ │ │ │ └── navigation.hbs │ │ │ ├── import │ │ │ │ ├── zips │ │ │ │ │ ├── zip-image-dir │ │ │ │ │ │ └── images │ │ │ │ │ │ │ └── image.jpg │ │ │ │ │ ├── zip-without-base-dir │ │ │ │ │ │ └── test.json │ │ │ │ │ ├── zip-with-base-dir │ │ │ │ │ │ └── basedir │ │ │ │ │ │ │ └── test.json │ │ │ │ │ ├── zip-old-roon-export │ │ │ │ │ │ └── Roon-Export │ │ │ │ │ │ │ └── published │ │ │ │ │ │ │ └── test.md │ │ │ │ │ └── zip-with-double-base-dir │ │ │ │ │ │ └── basedir │ │ │ │ │ │ └── basedir │ │ │ │ │ │ └── test.json │ │ │ │ ├── deleted-2014-12-19-test-1.md │ │ │ │ ├── draft-2014-12-19-test-1.md │ │ │ │ ├── draft-2014-12-19-test-2.md │ │ │ │ ├── published-2014-12-19-test-1.md │ │ │ │ └── draft-2014-12-19-test-3.md │ │ │ ├── example.js │ │ │ ├── csv │ │ │ │ ├── single-column-with-header.csv │ │ │ │ ├── two-columns-with-header.csv │ │ │ │ └── two-columns-obscure-header.csv │ │ │ ├── app │ │ │ │ ├── badlib.js │ │ │ │ ├── goodlib.js │ │ │ │ ├── nested │ │ │ │ │ └── goodnested.js │ │ │ │ ├── badoutside.js │ │ │ │ ├── badtop.js │ │ │ │ ├── badinstall.js │ │ │ │ ├── badrequire.js │ │ │ │ └── good.js │ │ │ ├── images │ │ │ │ ├── favicon.ico │ │ │ │ ├── favicon.png │ │ │ │ ├── myicon.ico │ │ │ │ ├── ghosticon.jpg │ │ │ │ ├── ghost-logo.png │ │ │ │ ├── ghost-logo.pngx │ │ │ │ ├── loadingcat.gif │ │ │ │ ├── favicon_too_large.png │ │ │ │ ├── favicon_too_small.png │ │ │ │ ├── loadingcat_square.gif │ │ │ │ ├── favicon_16x_single.ico │ │ │ │ ├── favicon_64x_single.ico │ │ │ │ ├── favicon_multi_sizes.ico │ │ │ │ ├── favicon_not_square.png │ │ │ │ └── favicon_size_too_large.png │ │ │ ├── config │ │ │ │ ├── overrides.json │ │ │ │ ├── env │ │ │ │ │ ├── config.testing.json │ │ │ │ │ └── config.testing-mysql.json │ │ │ │ ├── defaults.json │ │ │ │ ├── config.testing.json │ │ │ │ └── config.testing-mysql.json │ │ │ └── export │ │ │ │ └── broken.json │ │ ├── mocks │ │ │ ├── index.js │ │ │ └── utils.js │ │ └── assertions.js │ ├── .eslintignore │ ├── unit │ │ ├── web │ │ │ ├── middleware │ │ │ │ └── theme-handler_spec.js │ │ │ ├── shared │ │ │ │ └── middleware │ │ │ │ │ └── api │ │ │ │ │ └── spam-prevention_spec.js │ │ │ └── api │ │ │ │ └── v2 │ │ │ │ └── content │ │ │ │ └── middleware_spec.js │ │ ├── helpers │ │ │ ├── test_tpl │ │ │ │ ├── pagination.hbs │ │ │ │ └── navigation.hbs │ │ │ ├── encode_spec.js │ │ │ ├── template_spec.js │ │ │ ├── lang_spec.js │ │ │ └── post_class_spec.js │ │ ├── lib │ │ │ ├── mobiledoc │ │ │ │ ├── atoms │ │ │ │ │ └── soft-return_spec.js │ │ │ │ ├── cards │ │ │ │ │ └── hr_spec.js │ │ │ │ └── converters │ │ │ │ │ └── converters_spec.js │ │ │ ├── security │ │ │ │ └── password_spec.js │ │ │ └── promise │ │ │ │ └── sequence_spec.js │ │ ├── services │ │ │ └── auth │ │ │ │ └── passport_spec.js │ │ ├── models │ │ │ └── permission_spec.js │ │ ├── data │ │ │ └── meta │ │ │ │ └── rss_url_spec.js │ │ └── api │ │ │ ├── v2 │ │ │ └── utils │ │ │ │ └── index_spec.js │ │ │ └── shared │ │ │ └── util │ │ │ └── options_spec.js │ ├── regression │ │ └── README.md │ ├── .jshintrc │ └── acceptance │ │ └── README.md └── index.js ├── .eslintignore ├── content ├── apps │ └── README.md ├── logs │ └── README.md ├── images │ └── README.md ├── data │ └── README.md ├── settings │ └── README.md └── adapters │ └── README.md ├── .gitattributes ├── .gitmodules ├── .eslintrc.json ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── stale.yml └── ISSUE_TEMPLATE │ ├── --anything-else.md │ └── ---bug-report.md ├── .editorconfig ├── MigratorConfig.js ├── SECURITY.md └── .npmignore /core/server/web/admin/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/settings/test.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/settings/notyaml.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/test/.eslintignore: -------------------------------------------------------------------------------- 1 | core/test/coverage/** 2 | -------------------------------------------------------------------------------- /core/test/unit/web/middleware/theme-handler_spec.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/test.hbs: -------------------------------------------------------------------------------- 1 |

HelloWorld

-------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/test-theme/post.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/test-theme/default.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/test-theme/something.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/test-theme-channels/default.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/server/web/site/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./app'); 2 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/import/zips/zip-image-dir/images/image.jpg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/import/zips/zip-without-base-dir/test.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/server/apps/private-blogging/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / -------------------------------------------------------------------------------- /core/server/web/admin/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./app'); 2 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/import/zips/zip-with-base-dir/basedir/test.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/test-theme-channels/channel2.hbs: -------------------------------------------------------------------------------- 1 | channel2 -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/test-theme-channels/channel3.hbs: -------------------------------------------------------------------------------- 1 | channel3 -------------------------------------------------------------------------------- /core/server/api/shared/serializers/output/index.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/output/index.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /core/server/services/rss/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./renderer'); 2 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/import/deleted-2014-12-19-test-1.md: -------------------------------------------------------------------------------- 1 | You're live! Nice. -------------------------------------------------------------------------------- /core/test/utils/fixtures/import/draft-2014-12-19-test-1.md: -------------------------------------------------------------------------------- 1 | You're live! Nice. -------------------------------------------------------------------------------- /core/server/lib/members/static/gateway/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/server/services/themes/config/defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts_per_page": 5 3 | } 4 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/import/zips/zip-old-roon-export/Roon-Export/published/test.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/import/zips/zip-with-double-base-dir/basedir/basedir/test.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/test-theme/index.hbs: -------------------------------------------------------------------------------- 1 |
{{tag.slug}}
-------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | core/server/public/**/*.js 2 | core/server/lib/members/static/auth/**/*.js 3 | -------------------------------------------------------------------------------- /content/apps/README.md: -------------------------------------------------------------------------------- 1 | # Content / Apps 2 | 3 | Coming soon, Ghost apps will appear here. -------------------------------------------------------------------------------- /core/server/services/themes/engines/defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "ghost-api": "v0.1" 3 | } 4 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/example.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | answer: 42 4 | }; 5 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /dist 3 | /build 4 | /*.log 5 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/import/draft-2014-12-19-test-2.md: -------------------------------------------------------------------------------- 1 | # Welcome to Ghost 2 | 3 | You're live! Nice. -------------------------------------------------------------------------------- /core/test/utils/fixtures/import/published-2014-12-19-test-1.md: -------------------------------------------------------------------------------- 1 | #Welcome to Ghost 2 | 3 | You're live! Nice. -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper/locales/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "Top left Button": "Oben Links." 3 | } 4 | -------------------------------------------------------------------------------- /core/server/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/server/public/favicon.ico -------------------------------------------------------------------------------- /core/test/utils/fixtures/csv/single-column-with-header.csv: -------------------------------------------------------------------------------- 1 | email 2 | jbloggs@example.com 3 | test@example.com 4 | -------------------------------------------------------------------------------- /core/server/public/404-ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/server/public/404-ghost.png -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Top left Button": "Left Button on Top" 3 | } 4 | -------------------------------------------------------------------------------- /core/test/utils/mocks/index.js: -------------------------------------------------------------------------------- 1 | exports.utils = require('./utils'); 2 | exports.express = require('./express'); 3 | -------------------------------------------------------------------------------- /content/logs/README.md: -------------------------------------------------------------------------------- 1 | # Content / Logs 2 | 3 | This is the default log file location when Ghost runs in Production. 4 | -------------------------------------------------------------------------------- /core/server/public/404-ghost@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/server/public/404-ghost@2x.png -------------------------------------------------------------------------------- /core/test/utils/fixtures/csv/two-columns-with-header.csv: -------------------------------------------------------------------------------- 1 | id,email 2 | 1,"jbloggs@example.com" 3 | 1,test@example.com 4 | -------------------------------------------------------------------------------- /core/server/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Sitemap: {{blog-url}}/sitemap.xml 3 | Disallow: /ghost/ 4 | Disallow: /p/ 5 | -------------------------------------------------------------------------------- /core/server/services/mail/index.js: -------------------------------------------------------------------------------- 1 | exports.GhostMailer = require('./GhostMailer'); 2 | exports.utils = require('./utils'); 3 | -------------------------------------------------------------------------------- /core/server/web/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get shared() { 3 | return require('./shared'); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/app/badlib.js: -------------------------------------------------------------------------------- 1 | var knex = require('knex'); 2 | 3 | module.exports = { 4 | knex: knex 5 | }; 6 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/broken-theme/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "broken-theme", 3 | "version": "0.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /content/images/README.md: -------------------------------------------------------------------------------- 1 | # Content / Images 2 | 3 | If using the standard file storage, Ghost will upload images to this directory. -------------------------------------------------------------------------------- /core/server/data/schema/fixtures/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./fixtures'); 2 | module.exports.utils = require('./utils'); 3 | -------------------------------------------------------------------------------- /core/server/lib/mobiledoc/atoms/index.js: -------------------------------------------------------------------------------- 1 | const softReturn = require('./soft-return'); 2 | 3 | module.exports = [softReturn]; 4 | -------------------------------------------------------------------------------- /core/server/lib/social/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get urls() { 3 | return require('./urls'); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /core/test/unit/helpers/test_tpl/pagination.hbs: -------------------------------------------------------------------------------- 1 | {{@blog.title}} 2 | Page {{page}} of {{pages}} 3 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/app/goodlib.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | util: function () { 3 | return 42; 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/csv/two-columns-obscure-header.csv: -------------------------------------------------------------------------------- 1 | id,Email Address 2 | 1,"jbloggs@example.com" 3 | 2,test@example.com 4 | -------------------------------------------------------------------------------- /core/server/data/migrations/hooks/init/index.js: -------------------------------------------------------------------------------- 1 | exports.shutdown = require('./shutdown'); 2 | exports.before = require('./before'); 3 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/favicon.ico -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/favicon.png -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/myicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/myicon.ico -------------------------------------------------------------------------------- /core/test/utils/fixtures/import/draft-2014-12-19-test-3.md: -------------------------------------------------------------------------------- 1 | ![](/images/kitten.jpg) 2 | 3 | # Welcome to Ghost 4 | 5 | You're live! Nice. -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/themes/casper.zip -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/invalid.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/themes/invalid.zip -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/valid.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/themes/valid.zip -------------------------------------------------------------------------------- /core/server/api/shared/utils/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get options() { 3 | return require('./options'); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /core/server/models/relations/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get authors() { 3 | return require('./authors'); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /core/server/services/webhooks/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get listen() { 3 | return require('./listen'); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/app/nested/goodnested.js: -------------------------------------------------------------------------------- 1 | var lib = require('../goodlib.js'); 2 | 3 | module.exports = { 4 | other: 42 5 | }; 6 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/ghosticon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/ghosticon.jpg -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/warnings.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/themes/warnings.zip -------------------------------------------------------------------------------- /core/server/api/shared/serializers/input/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get all() { 3 | return require('./all'); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /core/server/api/shared/validators/input/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get all() { 3 | return require('./all'); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /core/server/services/themes/engines/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get create() { 3 | return require('./create'); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/config/overrides.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": { 3 | "appRoot": ".", 4 | "corePath": "core/" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/ghost-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/ghost-logo.png -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/ghost-logo.pngx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/ghost-logo.pngx -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/loadingcat.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/loadingcat.gif -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/test-theme/home.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ghost_head}} 4 | 5 | 6 | home.hbs 7 | 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # enforce unix style line endings 2 | *.js text eol=lf 3 | *.md text eol=lf 4 | *.json text eol=lf 5 | *.yml text eol=lf 6 | *.hbs text eol=lf -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/favicon_too_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/favicon_too_large.png -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/favicon_too_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/favicon_too_small.png -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/loadingcat_square.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/loadingcat_square.gif -------------------------------------------------------------------------------- /core/server/services/routing/middlewares/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get pageParam() { 3 | return require('./page-param'); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/favicon_16x_single.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/favicon_16x_single.ico -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/favicon_64x_single.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/favicon_64x_single.ico -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/favicon_multi_sizes.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/favicon_multi_sizes.ico -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/favicon_not_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/favicon_not_square.png -------------------------------------------------------------------------------- /core/test/utils/fixtures/images/favicon_size_too_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/images/favicon_size_too_large.png -------------------------------------------------------------------------------- /core/test/unit/helpers/test_tpl/navigation.hbs: -------------------------------------------------------------------------------- 1 | {{@blog.title}} 2 | 3 | {{#foreach navigation}} 4 | {{label}} 5 | {{/foreach}} 6 | -------------------------------------------------------------------------------- /core/server/services/url/index.js: -------------------------------------------------------------------------------- 1 | const UrlService = require('./UrlService'), 2 | urlService = new UrlService(); 3 | 4 | // Singleton 5 | module.exports = urlService; 6 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/redirects.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | download(response, apiConfig, frame) { 3 | frame.response = response; 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /core/server/data/migrations/hooks/migrate/afterEach.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'); 2 | 3 | module.exports = function afterEach() { 4 | return Promise.resolve(); 5 | }; 6 | -------------------------------------------------------------------------------- /core/server/data/migrations/hooks/migrate/beforeEach.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'); 2 | 3 | module.exports = function beforeEach() { 4 | return Promise.resolve(); 5 | }; 6 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper/assets/screenshot-desktop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/themes/casper/assets/screenshot-desktop.jpg -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper/assets/screenshot-mobile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/themes/casper/assets/screenshot-mobile.jpg -------------------------------------------------------------------------------- /core/server/apps/private-blogging/lib/helpers/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function registerHelpers(ghost) { 2 | ghost.helpers.register('input_password', require('./input_password')); 3 | }; 4 | -------------------------------------------------------------------------------- /core/server/data/schema/clients/index.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('./sqlite3'), 2 | mysql = require('./mysql'); 3 | 4 | module.exports = { 5 | sqlite3: sqlite3, 6 | mysql: mysql 7 | }; 8 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/styles/members.css: -------------------------------------------------------------------------------- 1 | @import './normalize.css'; 2 | @import './utils.css'; 3 | @import './variables.css'; 4 | @import './components.css'; 5 | @import './screen.css'; -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/test-theme/assets/screenshot-desktop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/themes/test-theme/assets/screenshot-desktop.jpg -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/test-theme/assets/screenshot-mobile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/Ghost/master/core/test/utils/fixtures/themes/test-theme/assets/screenshot-mobile.jpg -------------------------------------------------------------------------------- /core/server/api/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./v0.1'); 2 | module.exports['v0.1'] = require('./v0.1'); 3 | module.exports.v2 = require('./v2'); 4 | module.exports.shared = require('./shared'); 5 | -------------------------------------------------------------------------------- /core/server/web/site/routes.js: -------------------------------------------------------------------------------- 1 | const routing = require('../../services/routing'); 2 | 3 | module.exports = function siteRoutes(options = {}) { 4 | return routing.bootstrap.init(options); 5 | }; 6 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/config/env/config.testing.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "http://localhost:2368", 3 | "logging": { 4 | "level": "info", 5 | "transports": ["stdout"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /core/server/lib/mobiledoc/atoms/soft-return.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'soft-return', 3 | type: 'dom', 4 | render(opts) { 5 | return opts.env.dom.createElement('br'); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/config/env/config.testing-mysql.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "http://localhost:2368", 3 | "logging": { 4 | "level": "info", 5 | "transports": ["stdout"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "content/themes/casper"] 2 | path = content/themes/casper 3 | url = ../../TryGhost/Casper.git 4 | [submodule "core/client"] 5 | path = core/client 6 | url = ../../TryGhost/Ghost-Admin.git 7 | -------------------------------------------------------------------------------- /core/server/data/migrations/versions/2.13/1-remove-empty-strings.js: -------------------------------------------------------------------------------- 1 | const Promise = require('bluebird'); 2 | 3 | module.exports.up = () => Promise.resolve(); 4 | 5 | module.exports.down = () => Promise.resolve(); 6 | -------------------------------------------------------------------------------- /core/server/services/auth/api-key/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get admin() { 3 | return require('./admin'); 4 | }, 5 | get content() { 6 | return require('./content'); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper/partials/icons/facebook.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "plugins": [ 7 | "ghost" 8 | ], 9 | "extends": [ 10 | "plugin:ghost/node" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /core/server/api/shared/validators/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get handle() { 3 | return require('./handle'); 4 | }, 5 | 6 | get input() { 7 | return require('./input'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /core/server/lib/fs/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get readCSV() { 3 | return require('./read-csv'); 4 | }, 5 | 6 | get zipFolder() { 7 | return require('./zip-folder'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /core/server/services/settings/default-routes.yaml: -------------------------------------------------------------------------------- 1 | routes: 2 | 3 | collections: 4 | /: 5 | permalink: /{slug}/ 6 | template: index 7 | 8 | taxonomies: 9 | tag: /tag/{slug}/ 10 | author: /author/{slug}/ 11 | -------------------------------------------------------------------------------- /core/server/web/shared/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get middlewares() { 3 | return require('./middlewares'); 4 | }, 5 | 6 | get utils() { 7 | return require('./utils'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/export/broken.json: -------------------------------------------------------------------------------- 1 | {"asdadas":[{ 2 | "meta": { 3 | "exported_on": 1388318311015, 4 | "version": "003" 5 | }, 6 | "data": { 7 | "posts": [] 8 | } 9 | }]} 10 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get input() { 3 | return require('./input'); 4 | }, 5 | 6 | get output() { 7 | return require('./output'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/preview.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | all(model, apiConfig, frame) { 3 | frame.response = { 4 | preview: [model.toJSON(frame.options)] 5 | }; 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get input() { 3 | return require('./input'); 4 | }, 5 | 6 | get output() { 7 | return require('./output'); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /core/server/data/migrations/hooks/migrate/index.js: -------------------------------------------------------------------------------- 1 | exports.before = require('./before'); 2 | exports.beforeEach = require('./beforeEach'); 3 | exports.afterEach = require('./afterEach'); 4 | exports.shutdown = require('./shutdown'); 5 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/config/defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "client": "sqlite3", 4 | "connection": { 5 | "filename": "/test.db" 6 | }, 7 | "debug": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/settings/badroutes.yaml: -------------------------------------------------------------------------------- 1 | routes: 2 | 3 | collections: 4 | / 5 | permalink: /{slug}/ 6 | template: 7 | - index 8 | 9 | taxonomies: 10 | tag: /tag/{slug}/ 11 | author: /author/{slug}/ 12 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/settings/goodroutes.yaml: -------------------------------------------------------------------------------- 1 | routes: 2 | 3 | collections: 4 | /: 5 | permalink: /{slug}/ 6 | template: 7 | - index 8 | 9 | taxonomies: 10 | tag: /tag/{slug}/ 11 | author: /author/{slug}/ 12 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/settings/routes.yaml: -------------------------------------------------------------------------------- 1 | routes: 2 | 3 | collections: 4 | /: 5 | permalink: /{slug}/ 6 | template: 7 | - index 8 | 9 | taxonomies: 10 | tag: /tag/{slug}/ 11 | author: /author/{slug}/ 12 | -------------------------------------------------------------------------------- /core/server/data/migrations/hooks/init/before.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'), 2 | models = require('../../../../models'); 3 | 4 | module.exports = function before() { 5 | models.init(); 6 | return Promise.resolve(); 7 | }; 8 | -------------------------------------------------------------------------------- /core/server/data/migrations/hooks/migrate/before.js: -------------------------------------------------------------------------------- 1 | var backup = require('../../../db/backup'), 2 | models = require('../../../../models'); 3 | 4 | module.exports = function before() { 5 | models.init(); 6 | return backup(); 7 | }; 8 | -------------------------------------------------------------------------------- /core/server/data/schema/index.js: -------------------------------------------------------------------------------- 1 | module.exports.tables = require('./schema'); 2 | module.exports.checks = require('./checks'); 3 | module.exports.commands = require('./commands'); 4 | module.exports.defaultSettings = require('./default-settings'); 5 | -------------------------------------------------------------------------------- /core/server/web/shared/middlewares/image/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get normalize() { 3 | return require('./normalize'); 4 | }, 5 | get handleImageSizes() { 6 | return require('./handle-image-sizes'); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/settings/newroutes.yaml: -------------------------------------------------------------------------------- 1 | routes: 2 | 3 | collections: 4 | /blog/: 5 | permalink: /blog/{year}/{slug}/ 6 | template: 7 | - index 8 | 9 | taxonomies: 10 | tag: /category/{slug}/ 11 | author: /author/{slug}/ 12 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper/partials/icons/infinity.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/server/helpers/tpl/navigation.hbs: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /core/server/lib/mobiledoc/cards/hr.js: -------------------------------------------------------------------------------- 1 | const createCard = require('../create-card'); 2 | 3 | module.exports = createCard({ 4 | name: 'hr', 5 | type: 'dom', 6 | render(opts) { 7 | return opts.env.dom.createElement('hr'); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/components/FormSubmit.js: -------------------------------------------------------------------------------- 1 | export default ({onClick, label}) => ( 2 |
3 | 6 |
7 | ); 8 | -------------------------------------------------------------------------------- /core/server/adapters/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/api/v2/slack.js: -------------------------------------------------------------------------------- 1 | const common = require('../../lib/common'); 2 | 3 | module.exports = { 4 | docName: 'slack', 5 | sendTest: { 6 | permissions: false, 7 | query() { 8 | common.events.emit('slack.test'); 9 | } 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/input/schemas/images-upload.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "$id": "images.upload", 5 | "title": "images.upload", 6 | "description": "Schema for images.upload", 7 | "$ref": "images#/definitions/image" 8 | } 9 | -------------------------------------------------------------------------------- /core/server/data/db/index.js: -------------------------------------------------------------------------------- 1 | var connection; 2 | 3 | Object.defineProperty(exports, 'knex', { 4 | enumerable: true, 5 | configurable: true, 6 | get: function get() { 7 | connection = connection || require('./connection'); 8 | return connection; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /core/server/lib/mobiledoc/cards/index.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | require('./card-markdown'), 3 | require('./code'), 4 | require('./embed'), 5 | require('./hr'), 6 | require('./html'), 7 | require('./image'), 8 | require('./markdown'), 9 | require('./gallery') 10 | ]; 11 | -------------------------------------------------------------------------------- /core/server/api/shared/serializers/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get handle() { 3 | return require('./handle'); 4 | }, 5 | 6 | get input() { 7 | return require('./input'); 8 | }, 9 | 10 | get output() { 11 | return require('./output'); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /core/server/data/meta/rss_url.js: -------------------------------------------------------------------------------- 1 | const routingService = require('../../services/routing'); 2 | 3 | function getRssUrl(data, absolute) { 4 | return routingService.registry.getRssUrl({ 5 | secure: data.secure, 6 | absolute: absolute 7 | }); 8 | } 9 | 10 | module.exports = getRssUrl; 11 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper/partials/icons/rss.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/oembed.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:oembed'); 2 | 3 | module.exports = { 4 | all(res, apiConfig, frame) { 5 | debug('all'); 6 | frame.response = res; 7 | debug(frame.response); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/components/FormHeaderCTA.js: -------------------------------------------------------------------------------- 1 | export default ({title, label, icon, hash}) => ( 2 |
3 | {title ? (

{ title }

) : ""} 4 | 5 | { label } 6 | { icon } 7 | 8 |
9 | ); 10 | -------------------------------------------------------------------------------- /core/test/regression/README.md: -------------------------------------------------------------------------------- 1 | ## Regression Tests 2 | 3 | This folder should contain packages which we test in a cron job once per day. 4 | These tests should ensure that we don't break Ghost. 5 | 6 | The goal is that most of these packages use Ghost's API's to test behaviours, otherwise transform the tests into unit tests. 7 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/app/badoutside.js: -------------------------------------------------------------------------------- 1 | var lib = require('../example'); 2 | 3 | function BadApp(app) { 4 | this.app = app; 5 | } 6 | 7 | BadApp.prototype.install = function () { 8 | return lib.answer; 9 | }; 10 | 11 | BadApp.prototype.activate = function () { 12 | }; 13 | 14 | module.exports = BadApp; 15 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/site.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:site'); 2 | 3 | module.exports = { 4 | read(data, apiConfig, frame) { 5 | debug('read'); 6 | 7 | frame.response = { 8 | site: data 9 | }; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /core/server/data/migrations/versions/2.0/6-replace-fixture-posts.js: -------------------------------------------------------------------------------- 1 | const Promise = require('bluebird'); 2 | 3 | // @NOTE: the logic of this migration script was removed. 4 | module.exports.up = () => { 5 | return Promise.resolve(); 6 | }; 7 | 8 | module.exports.down = () => { 9 | return Promise.resolve(); 10 | }; 11 | -------------------------------------------------------------------------------- /core/server/models/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | filter: require('./filter'), 3 | includeCount: require('./include-count'), 4 | pagination: require('./pagination'), 5 | collision: require('./collision'), 6 | transactionEvents: require('./transaction-events'), 7 | hasPosts: require('./has-posts') 8 | }; 9 | -------------------------------------------------------------------------------- /core/server/web/shared/middlewares/api/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get cors() { 3 | return require('./cors'); 4 | }, 5 | 6 | get spamPrevention() { 7 | return require('./spam-prevention'); 8 | }, 9 | 10 | get versionMatch() { 11 | return require('./version-match'); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /core/server/web/shared/middlewares/validation/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get upload() { 3 | return require('./upload'); 4 | }, 5 | 6 | get blogIcon() { 7 | return require('./blog-icon'); 8 | }, 9 | 10 | get profileImage() { 11 | return require('./profile-image'); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/app/badtop.js: -------------------------------------------------------------------------------- 1 | var knex = require('knex'); 2 | 3 | function BadApp(app) { 4 | this.app = app; 5 | } 6 | 7 | BadApp.prototype.install = function () { 8 | return knex.dropTableIfExists('users'); 9 | }; 10 | 11 | BadApp.prototype.activate = function () { 12 | }; 13 | 14 | module.exports = BadApp; 15 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/app/badinstall.js: -------------------------------------------------------------------------------- 1 | function BadApp(app) { 2 | this.app = app; 3 | } 4 | 5 | BadApp.prototype.install = function () { 6 | var knex = require('knex'); 7 | 8 | return knex.dropTableIfExists('users'); 9 | }; 10 | 11 | BadApp.prototype.activate = function () { 12 | }; 13 | 14 | module.exports = BadApp; 15 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/config/config.testing.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": { 3 | "corePath": "try-to-override" 4 | }, 5 | "database": { 6 | "connection": { 7 | "filename": "/hehe.db" 8 | }, 9 | "debug": true 10 | }, 11 | "logging": { 12 | "level": "error" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/test-theme/podcast/rss.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{@blog.title}} 4 | {{#get "posts" filter="featured:true" limit="20"}} 5 | {{#foreach posts}} 6 | {{url}} 7 | {{/foreach}} 8 | {{/get}} 9 | 10 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/config.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:config'); 2 | 3 | module.exports = { 4 | all(data, apiConfig, frame) { 5 | frame.response = { 6 | config: data 7 | }; 8 | 9 | debug(frame.response); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/app/badrequire.js: -------------------------------------------------------------------------------- 1 | var lib = require('./badlib'); 2 | 3 | function BadApp(app) { 4 | this.app = app; 5 | } 6 | 7 | BadApp.prototype.install = function () { 8 | return lib.knex.dropTableIfExists('users'); 9 | }; 10 | 11 | BadApp.prototype.activate = function () { 12 | }; 13 | 14 | module.exports = BadApp; 15 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/config/config.testing-mysql.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": { 3 | "corePath": "try-to-override" 4 | }, 5 | "database": { 6 | "connection": { 7 | "filename": "/hehe.db" 8 | }, 9 | "debug": true 10 | }, 11 | "logging": { 12 | "level": "error" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/server/adapters/scheduling/index.js: -------------------------------------------------------------------------------- 1 | const 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/lib/members/static/auth/components/FormFooter.js: -------------------------------------------------------------------------------- 1 | export default ({title, hash, label}) => ( 2 |
3 |
4 |

{ title }

5 | 6 | { label } 7 | 8 |
9 |
10 | ); 11 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-import'), 4 | require('autoprefixer'), 5 | require('postcss-css-variables'), 6 | require('postcss-color-mod-function'), 7 | require('cssnano'), 8 | require('postcss-custom-properties') 9 | ] 10 | }; 11 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper/partials/icons/avatar.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/server/data/xml/sitemap/tag-generator.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'), 2 | BaseMapGenerator = require('./base-generator'); 3 | 4 | class TagsMapGenerator extends BaseMapGenerator { 5 | constructor(opts) { 6 | super(); 7 | 8 | this.name = 'tags'; 9 | _.extend(this, opts); 10 | } 11 | } 12 | 13 | module.exports = TagsMapGenerator; 14 | -------------------------------------------------------------------------------- /core/server/helpers/encode.js: -------------------------------------------------------------------------------- 1 | // # Encode Helper 2 | // 3 | // Usage: `{{encode uri}}` 4 | // 5 | // Returns URI encoded string 6 | 7 | var proxy = require('./proxy'), 8 | SafeString = proxy.SafeString; 9 | 10 | module.exports = function encode(string, options) { 11 | var uri = string || options; 12 | return new SafeString(encodeURIComponent(uri)); 13 | }; 14 | -------------------------------------------------------------------------------- /core/server/lib/common/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get i18n() { 3 | return require('./i18n'); 4 | }, 5 | 6 | get events() { 7 | return require('./events'); 8 | }, 9 | 10 | get errors() { 11 | return require('./errors'); 12 | }, 13 | 14 | get logging() { 15 | return require('./logging'); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /core/server/lib/mobiledoc/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | activate() { 3 | // needed by ghost 4 | }, 5 | 6 | get cards() { 7 | return require('./cards'); 8 | }, 9 | 10 | get atoms() { 11 | return require('./atoms'); 12 | }, 13 | 14 | get converters() { 15 | return require('./converters'); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper/partials/icons/point.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/slugs.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:slugs'); 2 | 3 | module.exports = { 4 | all(slug, apiConfig, frame) { 5 | debug('all'); 6 | 7 | frame.response = { 8 | slugs: [{slug}] 9 | }; 10 | 11 | debug(frame.response); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /core/server/data/xml/sitemap/page-generator.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'), 2 | BaseMapGenerator = require('./base-generator'); 3 | 4 | class PageMapGenerator extends BaseMapGenerator { 5 | constructor(opts) { 6 | super(); 7 | 8 | this.name = 'pages'; 9 | 10 | _.extend(this, opts); 11 | } 12 | } 13 | 14 | module.exports = PageMapGenerator; 15 | -------------------------------------------------------------------------------- /core/server/data/xml/sitemap/post-generator.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'), 2 | BaseMapGenerator = require('./base-generator'); 3 | 4 | class PostMapGenerator extends BaseMapGenerator { 5 | constructor(opts) { 6 | super(); 7 | 8 | this.name = 'posts'; 9 | 10 | _.extend(this, opts); 11 | } 12 | } 13 | 14 | module.exports = PostMapGenerator; 15 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper/partials/icons/location.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/server/lib/mobiledoc/cards/card-markdown.js: -------------------------------------------------------------------------------- 1 | // this card is just an alias of the `markdown` card which is necessary because 2 | // our markdown-only editor was using the `card-markdown` card name 3 | const markdownCard = require('./markdown'); 4 | const createCard = require('../create-card'); 5 | 6 | module.exports = createCard( 7 | Object.assign({}, markdownCard, {name: 'card-markdown'}) 8 | ); 9 | -------------------------------------------------------------------------------- /core/server/services/routing/helpers/error.js: -------------------------------------------------------------------------------- 1 | function handleError(next) { 2 | return function handleError(err) { 3 | // If we've thrown an error message of type: 'NotFound' then we found no path match. 4 | if (err.errorType === 'NotFoundError') { 5 | return next(); 6 | } 7 | 8 | return next(err); 9 | }; 10 | } 11 | 12 | module.exports = handleError; 13 | -------------------------------------------------------------------------------- /core/server/api/shared/utils/options.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | const trimAndLowerCase = (params) => { 4 | params = params || ''; 5 | 6 | if (_.isString(params)) { 7 | params = params.split(','); 8 | } 9 | 10 | return params.map((item) => { 11 | return item.trim().toLowerCase(); 12 | }); 13 | }; 14 | 15 | module.exports.trimAndLowerCase = trimAndLowerCase; 16 | -------------------------------------------------------------------------------- /core/server/data/meta/og_type.js: -------------------------------------------------------------------------------- 1 | function getOgType(data) { 2 | var context = data.context ? data.context[0] : null; 3 | 4 | context = context === 'amp' ? 'post' : context; 5 | 6 | if (context === 'author') { 7 | return 'profile'; 8 | } 9 | if (context === 'post') { 10 | return 'article'; 11 | } 12 | return 'website'; 13 | } 14 | 15 | module.exports = getOgType; 16 | -------------------------------------------------------------------------------- /core/server/data/meta/published_date.js: -------------------------------------------------------------------------------- 1 | function getPublishedDate(data) { 2 | var context = data.context ? data.context[0] : null; 3 | 4 | context = context === 'amp' ? 'post' : context; 5 | 6 | if (data[context] && data[context].published_at) { 7 | return new Date(data[context].published_at).toISOString(); 8 | } 9 | return null; 10 | } 11 | 12 | module.exports = getPublishedDate; 13 | -------------------------------------------------------------------------------- /core/server/services/routing/helpers/secure.js: -------------------------------------------------------------------------------- 1 | // TODO: figure out how to remove the need for this 2 | // Add Request context parameter to the data object 3 | // to be passed down to the templates 4 | function setRequestIsSecure(req, data) { 5 | (Array.isArray(data) ? data : [data]).forEach(function forEach(d) { 6 | d.secure = req.secure; 7 | }); 8 | } 9 | 10 | module.exports = setRequestIsSecure; 11 | -------------------------------------------------------------------------------- /core/server/data/migrations/hooks/migrate/shutdown.js: -------------------------------------------------------------------------------- 1 | const database = require('../../../db'); 2 | 3 | module.exports = function shutdown(options = {}) { 4 | /** 5 | * We have to close Ghost's db connection if knex-migrator was used in the shell. 6 | * Otherwise the process doesn't exit. 7 | */ 8 | if (options.executedFromShell === true) { 9 | return database.knex.destroy(); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /core/server/lib/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ONE_HOUR_S: 3600, 3 | ONE_DAY_S: 86400, 4 | ONE_MONTH_S: 2628000, 5 | SIX_MONTH_S: 15768000, 6 | ONE_YEAR_S: 31536000, 7 | FIVE_MINUTES_MS: 300000, 8 | ONE_HOUR_MS: 3600000, 9 | ONE_DAY_MS: 86400000, 10 | ONE_WEEK_MS: 604800000, 11 | ONE_MONTH_MS: 2628000000, 12 | SIX_MONTH_MS: 15768000000, 13 | ONE_YEAR_MS: 31536000000 14 | }; 15 | -------------------------------------------------------------------------------- /content/settings/README.md: -------------------------------------------------------------------------------- 1 | # Content / Settings 2 | 3 | ### routes.yaml 4 | 5 | 6 | 7 | This is how the default `routes.yaml` file looks like: 8 | 9 | ```yaml 10 | routes: 11 | 12 | collections: 13 | /: 14 | permalink: '/{slug}/' 15 | template: 16 | - index 17 | 18 | taxonomies: 19 | tag: /tag/{slug}/ 20 | author: /author/{slug}/ 21 | ``` 22 | -------------------------------------------------------------------------------- /core/server/data/meta/keywords.js: -------------------------------------------------------------------------------- 1 | const models = require('../../models'); 2 | 3 | function getKeywords(data) { 4 | if (data.post && data.post.tags && data.post.tags.length > 0) { 5 | return models.Base.Model.filterByVisibility(data.post.tags, ['public'], false, function processItem(item) { 6 | return item.name; 7 | }); 8 | } 9 | return null; 10 | } 11 | 12 | module.exports = getKeywords; 13 | -------------------------------------------------------------------------------- /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 proxy = require('./proxy'), 7 | SafeString = proxy.SafeString, 8 | escapeExpression = proxy.escapeExpression; 9 | 10 | module.exports = function title() { 11 | return new SafeString(escapeExpression(this.title || '')); 12 | }; 13 | -------------------------------------------------------------------------------- /core/server/lib/social/urls.js: -------------------------------------------------------------------------------- 1 | module.exports.twitter = function twitter(username) { 2 | // Creates the canonical twitter URL without the '@' 3 | return 'https://twitter.com/' + username.replace(/^@/, ''); 4 | }; 5 | 6 | module.exports.facebook = function facebook(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/services/auth/session/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // @TODO: expose files/units and not functions of units 3 | get createSession() { 4 | return require('./middleware').createSession; 5 | }, 6 | 7 | get destroySession() { 8 | return require('./middleware').destroySession; 9 | }, 10 | 11 | get authenticate() { 12 | return require('./middleware').authenticate; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Got some code for us? Awesome 🎊! 2 | 3 | Please include a description of your change & check your PR against this list, thanks! 4 | 5 | - [ ] There's a clear use-case for this code change 6 | - [ ] Commit message has a short title & references relevant issues 7 | - [ ] The build will pass (run `yarn test` and `yarn lint`) 8 | 9 | More info can be found by clicking the "guidelines for contributing" link above. 10 | -------------------------------------------------------------------------------- /core/server/data/meta/canonical_url.js: -------------------------------------------------------------------------------- 1 | var urlService = require('../../services/url'), 2 | getUrl = require('./url'); 3 | 4 | function getCanonicalUrl(data) { 5 | var url = urlService.utils.urlJoin(urlService.utils.urlFor('home', true), getUrl(data, false)); 6 | 7 | if (url.indexOf('/amp/')) { 8 | url = url.replace(/\/amp\/$/i, '/'); 9 | } 10 | 11 | return url; 12 | } 13 | 14 | module.exports = getCanonicalUrl; 15 | -------------------------------------------------------------------------------- /core/server/helpers/tpl/pagination.hbs: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /core/server/lib/security/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get url() { 3 | return require('./url'); 4 | }, 5 | 6 | get tokens() { 7 | return require('./tokens'); 8 | }, 9 | 10 | get string() { 11 | return require('./string'); 12 | }, 13 | 14 | get identifier() { 15 | return require('./identifier'); 16 | }, 17 | 18 | get password() { 19 | return require('./password'); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper/partials/icons/website.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.hbs] 14 | insert_final_newline = false 15 | 16 | [{package}.json] 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | [*.yml] 23 | indent_size = 2 24 | 25 | [Makefile] 26 | indent_style = tab 27 | -------------------------------------------------------------------------------- /core/server/helpers/meta_title.js: -------------------------------------------------------------------------------- 1 | // # Meta Title Helper 2 | // Usage: `{{meta_title}}` 3 | // 4 | // Page title used for sharing and SEO 5 | var proxy = require('./proxy'), 6 | getMetaDataTitle = proxy.metaData.getMetaDataTitle; 7 | 8 | // We use the name meta_title to match the helper for consistency: 9 | module.exports = function meta_title(options) { // eslint-disable-line camelcase 10 | return getMetaDataTitle(this, options.data.root, options); 11 | }; 12 | -------------------------------------------------------------------------------- /core/server/web/shared/middlewares/update-user-last-seen.js: -------------------------------------------------------------------------------- 1 | const constants = require('../../../lib/constants'); 2 | 3 | module.exports = function updateUserLastSeenMiddleware(req, res, next) { 4 | if (!req.user) { 5 | return next(); 6 | } 7 | 8 | if (Date.now() - req.user.get('last_seen') < constants.ONE_HOUR_MS) { 9 | return next(); 10 | } 11 | 12 | req.user.updateLastSeen().then(() => { 13 | next(); 14 | }, next); 15 | }; 16 | -------------------------------------------------------------------------------- /core/server/web/shared/middlewares/emit-events.js: -------------------------------------------------------------------------------- 1 | const common = require('../../../lib/common'); 2 | const INVALIDATE_ALL = '/*'; 3 | 4 | module.exports = function emitEvents(req, res, next) { 5 | res.on('finish', function triggerEvents() { 6 | if (res.get('X-Cache-Invalidate') === INVALIDATE_ALL) { 7 | common.events.emit('site.changed'); 8 | } 9 | 10 | res.removeListener('finish', triggerEvents); 11 | }); 12 | next(); 13 | }; 14 | -------------------------------------------------------------------------------- /core/server/services/webhooks/payload.js: -------------------------------------------------------------------------------- 1 | const serialize = require('./serialize'); 2 | const Promise = require('bluebird'); 3 | 4 | module.exports = (event, model) => { 5 | const payload = {}; 6 | 7 | if (model) { 8 | return serialize(event, model) 9 | .then((result) => { 10 | Object.assign(payload, result); 11 | 12 | return payload; 13 | }); 14 | } 15 | 16 | return Promise.resolve(payload); 17 | }; 18 | -------------------------------------------------------------------------------- /core/server/services/themes/config/index.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'), 2 | defaultConfig = require('./defaults'), 3 | allowedKeys = ['posts_per_page', 'image_sizes']; 4 | 5 | module.exports.create = function configLoader(packageJson) { 6 | var config = _.cloneDeep(defaultConfig); 7 | 8 | if (packageJson && packageJson.hasOwnProperty('config')) { 9 | config = _.assign(config, _.pick(packageJson.config, allowedKeys)); 10 | } 11 | 12 | return config; 13 | }; 14 | -------------------------------------------------------------------------------- /core/server/api/v2/roles.js: -------------------------------------------------------------------------------- 1 | const models = require('../../models'); 2 | 3 | module.exports = { 4 | docName: 'roles', 5 | browse: { 6 | options: [ 7 | 'permissions' 8 | ], 9 | validation: { 10 | options: { 11 | permissions: ['assign'] 12 | } 13 | }, 14 | permissions: true, 15 | query(frame) { 16 | return models.Role.findAll(frame.options); 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/images.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:images'); 2 | const mapper = require('./utils/mapper'); 3 | 4 | module.exports = { 5 | upload(path, apiConfig, frame) { 6 | debug('upload'); 7 | 8 | return frame.response = { 9 | images: [{ 10 | url: mapper.mapImage(path), 11 | ref: frame.data.ref || null 12 | }] 13 | }; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /core/server/data/meta/amp_url.js: -------------------------------------------------------------------------------- 1 | var urlService = require('../../services/url'), 2 | getUrl = require('./url'), 3 | _ = require('lodash'); 4 | 5 | function getAmplUrl(data) { 6 | var context = data.context ? data.context : null; 7 | 8 | if (_.includes(context, 'post') && !_.includes(context, 'amp')) { 9 | return urlService.utils.urlJoin(urlService.utils.urlFor('home', true), getUrl(data, false), 'amp/'); 10 | } 11 | return null; 12 | } 13 | 14 | module.exports = getAmplUrl; 15 | -------------------------------------------------------------------------------- /core/server/data/xml/sitemap/utils.js: -------------------------------------------------------------------------------- 1 | var urlService = require('../../../services/url'), 2 | sitemapsUtils; 3 | 4 | sitemapsUtils = { 5 | getDeclarations: function () { 6 | var baseUrl = urlService.utils.urlFor('sitemap_xsl', true); 7 | baseUrl = baseUrl.replace(/^(http:|https:)/, ''); 8 | return '' + 9 | ''; 10 | } 11 | }; 12 | 13 | module.exports = sitemapsUtils; 14 | -------------------------------------------------------------------------------- /core/server/lib/image/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get blogIcon() { 3 | return require('./blog-icon'); 4 | }, 5 | 6 | get imageSize() { 7 | return require('./image-size'); 8 | }, 9 | 10 | get gravatar() { 11 | return require('./gravatar'); 12 | }, 13 | 14 | get imageSizeCache() { 15 | return require('./cached-image-size-from-url'); 16 | }, 17 | 18 | get manipulator() { 19 | return require('./manipulator'); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /core/server/services/routing/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get bootstrap() { 3 | return require('./bootstrap'); 4 | }, 5 | 6 | get registry() { 7 | return require('./registry'); 8 | }, 9 | 10 | get helpers() { 11 | return require('./helpers'); 12 | }, 13 | 14 | get CollectionRouter() { 15 | return require('./CollectionRouter'); 16 | }, 17 | 18 | get TaxonomyRouter() { 19 | return require('./TaxonomyRouter'); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/assets/images/icon-right-arrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/server/models/author.js: -------------------------------------------------------------------------------- 1 | const ghostBookshelf = require('./base'); 2 | const user = require('./user'); 3 | 4 | const Author = user.User.extend({ 5 | shouldHavePosts: { 6 | joinTo: 'author_id', 7 | joinTable: 'posts_authors' 8 | } 9 | }); 10 | 11 | const Authors = ghostBookshelf.Collection.extend({ 12 | model: Author 13 | }); 14 | 15 | module.exports = { 16 | Author: ghostBookshelf.model('Author', Author), 17 | Authors: ghostBookshelf.collection('Authors', Authors) 18 | }; 19 | -------------------------------------------------------------------------------- /core/server/api/v2/settings-public.js: -------------------------------------------------------------------------------- 1 | const settingsCache = require('../../services/settings/cache'); 2 | 3 | module.exports = { 4 | docName: 'settings', 5 | 6 | browse: { 7 | permissions: true, 8 | query() { 9 | // @TODO: decouple settings cache from API knowledge 10 | // The controller fetches models (or cached models) and the API frame for the target API version formats the response. 11 | return settingsCache.getPublic(); 12 | } 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/actions.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:actions'); 2 | const mapper = require('./utils/mapper'); 3 | 4 | module.exports = { 5 | browse(models, apiConfig, frame) { 6 | debug('browse'); 7 | 8 | frame.response = { 9 | actions: models.data.map(model => mapper.mapAction(model, frame)), 10 | meta: models.meta 11 | }; 12 | 13 | debug(frame.response); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /MigratorConfig.js: -------------------------------------------------------------------------------- 1 | var config = require('./core/server/config'), 2 | ghostVersion = require('./core/server/lib/ghost-version'); 3 | 4 | /** 5 | * knex-migrator can be used via CLI or within the application 6 | * when using the CLI, we need to ensure that our global overrides are triggered 7 | */ 8 | require('./core/server/overrides'); 9 | 10 | module.exports = { 11 | currentVersion: ghostVersion.safe, 12 | database: config.get('database'), 13 | migrationPath: config.get('paths:migrationPath') 14 | }; 15 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/input/integrations.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:integrations'); 3 | 4 | module.exports = { 5 | add(apiConfig, frame) { 6 | debug('add'); 7 | 8 | frame.data = _.pick(frame.data.integrations[0], apiConfig.data); 9 | }, 10 | edit(apiConfig, frame) { 11 | debug('edit'); 12 | 13 | frame.data = _.pick(frame.data.integrations[0], apiConfig.data); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/webhooks.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:webhooks'); 2 | 3 | module.exports = { 4 | all(models, apiConfig, frame) { 5 | debug('all'); 6 | // CASE: e.g. destroy returns null 7 | if (!models) { 8 | return; 9 | } 10 | 11 | frame.response = { 12 | webhooks: [models.toJSON(frame.options)] 13 | }; 14 | 15 | debug(frame.response); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /core/server/lib/mobiledoc/cards/html.js: -------------------------------------------------------------------------------- 1 | const createCard = require('../create-card'); 2 | 3 | module.exports = createCard({ 4 | name: 'html', 5 | type: 'dom', 6 | render(opts) { 7 | if (!opts.payload.html) { 8 | return ''; 9 | } 10 | 11 | // use the SimpleDOM document to create a raw HTML section. 12 | // avoids parsing/rendering of potentially broken or unsupported HTML 13 | return opts.env.dom.createRawHTMLSection(opts.payload.html); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /core/server/data/meta/modified_date.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | function getModifiedDate(data) { 4 | var context = data.context ? data.context : null, 5 | modDate; 6 | 7 | context = _.includes(context, 'amp') ? 'post' : context; 8 | 9 | if (data[context]) { 10 | modDate = data[context].updated_at || null; 11 | if (modDate) { 12 | return new Date(modDate).toISOString(); 13 | } 14 | } 15 | return null; 16 | } 17 | 18 | module.exports = getModifiedDate; 19 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/components/FormHeader.js: -------------------------------------------------------------------------------- 1 | import { IconError } from './icons'; 2 | 3 | export default ({title, error, errorText, children}) => ( 4 |
5 |
6 | { title ? (

{title}

) : "" } 7 | { children } 8 |
9 | {(error ? 10 |
{ IconError } 11 | {errorText} 12 |
: "") 13 | } 14 |
15 | ); 16 | -------------------------------------------------------------------------------- /core/server/services/routing/helpers/render-entries.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('services:routing:helpers:render-entries'), 2 | formatResponse = require('./format-response'), 3 | renderer = require('./renderer'); 4 | 5 | module.exports = function renderEntries(req, res) { 6 | debug('renderEntries called'); 7 | return function renderEntries(result) { 8 | // Format data 2 9 | // Render 10 | return renderer(req, res, formatResponse.entries(result)); 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Vulnerabilities 2 | 3 | Potential security vulnerabilities can be reported directly us at `security@ghost.org`. The Ghost Security Team communicates privately and works in a secured, isolated repository for tracking, testing, and resolving security-related issues. 4 | 5 | The full, up-to-date details of our security policy and procedure can always be found in our documentation: 6 | 7 | https://docs.ghost.org/security/ 8 | 9 | Please refer to this before emailing us. Thanks for helping make Ghost safe for everyone 🙏. -------------------------------------------------------------------------------- /core/server/models/tag-public.js: -------------------------------------------------------------------------------- 1 | const ghostBookshelf = require('./base'); 2 | const tag = require('./tag'); 3 | 4 | const TagPublic = tag.Tag.extend({ 5 | shouldHavePosts: { 6 | joinTo: 'tag_id', 7 | joinTable: 'posts_tags' 8 | } 9 | }); 10 | 11 | const TagsPublic = ghostBookshelf.Collection.extend({ 12 | model: TagPublic 13 | }); 14 | 15 | module.exports = { 16 | TagPublic: ghostBookshelf.model('TagPublic', TagPublic), 17 | TagsPublic: ghostBookshelf.collection('TagsPublic', TagsPublic) 18 | }; 19 | -------------------------------------------------------------------------------- /core/server/lib/common/logging.js: -------------------------------------------------------------------------------- 1 | const config = require('../../config'), 2 | {logging} = require('ghost-ignition'); 3 | 4 | module.exports = logging({ 5 | env: config.get('env'), 6 | path: config.get('logging:path') || config.getContentPath('logs'), 7 | domain: config.get('url'), 8 | mode: config.get('logging:mode'), 9 | level: config.get('logging:level'), 10 | transports: config.get('logging:transports'), 11 | loggly: config.get('logging:loggly'), 12 | rotation: config.get('logging:rotation') 13 | }); 14 | -------------------------------------------------------------------------------- /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/api/shared/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get headers() { 3 | return require('./headers'); 4 | }, 5 | 6 | get http() { 7 | return require('./http'); 8 | }, 9 | 10 | get Frame() { 11 | return require('./frame'); 12 | }, 13 | 14 | get pipeline() { 15 | return require('./pipeline'); 16 | }, 17 | 18 | get validators() { 19 | return require('./validators'); 20 | }, 21 | 22 | get serializers() { 23 | return require('./serializers'); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /core/server/apps/amp/lib/helpers/index.js: -------------------------------------------------------------------------------- 1 | // Dirty require! 2 | const ghostHead = require('../../../../helpers/ghost_head'); 3 | 4 | function registerAmpHelpers(ghost) { 5 | ghost.helpers.registerAsync('amp_content', require('./amp_content')); 6 | 7 | ghost.helpers.register('amp_components', require('./amp_components')); 8 | 9 | // we use the {{ghost_head}} helper, but call it {{amp_ghost_head}}, so it's consistent 10 | ghost.helpers.registerAsync('amp_ghost_head', ghostHead); 11 | } 12 | 13 | module.exports = registerAmpHelpers; 14 | -------------------------------------------------------------------------------- /core/test/unit/helpers/encode_spec.js: -------------------------------------------------------------------------------- 1 | var should = require('should'), 2 | 3 | // Stuff we are testing 4 | helpers = require('../../../server/helpers'); 5 | 6 | describe('{{encode}} helper', function () { 7 | it('can escape URI', function () { 8 | var uri = '$pecial!Charact3r(De[iver]y)Foo #Bar', 9 | expected = '%24pecial!Charact3r(De%5Biver%5Dy)Foo%20%23Bar', 10 | escaped = helpers.encode(uri); 11 | 12 | should.exist(escaped); 13 | String(escaped).should.equal(expected); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /core/server/helpers/meta_description.js: -------------------------------------------------------------------------------- 1 | // # Meta Description Helper 2 | // Usage: `{{meta_description}}` 3 | // 4 | // Page description used for sharing and SEO 5 | var proxy = require('./proxy'), 6 | getMetaDataDescription = proxy.metaData.getMetaDataDescription; 7 | 8 | // We use the name meta_description to match the helper for consistency: 9 | module.exports = function meta_description(options) { // eslint-disable-line camelcase 10 | options = options || {}; 11 | 12 | return getMetaDataDescription(this, options.data.root) || ''; 13 | }; 14 | -------------------------------------------------------------------------------- /core/server/services/routing/controllers/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get entry() { 3 | return require('./entry'); 4 | }, 5 | 6 | get collection() { 7 | return require('./collection'); 8 | }, 9 | 10 | get rss() { 11 | return require('./rss'); 12 | }, 13 | 14 | get preview() { 15 | return require('./preview'); 16 | }, 17 | 18 | get channel() { 19 | return require('./channel'); 20 | }, 21 | 22 | get static() { 23 | return require('./static'); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /core/server/config/env/config.production.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "client": "mysql", 4 | "connection": { 5 | "host" : "127.0.0.1", 6 | "user" : "root", 7 | "password" : "", 8 | "database" : "ghost" 9 | } 10 | }, 11 | "paths": { 12 | "contentPath": "content/" 13 | }, 14 | "logging": { 15 | "level": "info", 16 | "rotation": { 17 | "enabled": true 18 | }, 19 | "transports": ["file", "stdout"] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /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/services/rss/renderer.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'), 2 | rssCache = require('./cache'); 3 | 4 | module.exports.render = function render(res, baseUrl, data) { 5 | // Format data - this is the same as what Express does 6 | var rssData = _.merge({}, res.locals, data); 7 | 8 | // Fetch RSS from the cache 9 | return rssCache 10 | .getXML(baseUrl, rssData) 11 | .then(function then(feedXml) { 12 | res.set('Content-Type', 'text/xml; charset=UTF-8'); 13 | res.send(feedXml); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/components/NameInput.js: -------------------------------------------------------------------------------- 1 | import FormInput from './FormInput'; 2 | import { IconName } from './icons'; 3 | 4 | export default ({value, error, children, onInput, className}) => ( 5 | 16 | {children} 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/input/schemas/tags-edit.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "$id": "tags.edit", 5 | "title": "tags.edit", 6 | "description": "Schema for tags.edit", 7 | "type": "object", 8 | "additionalProperties": false, 9 | "properties": { 10 | "tags": { 11 | "type": "array", 12 | "minItems": 1, 13 | "maxItems": 1, 14 | "items": {"$ref": "tags#/definitions/tag"} 15 | } 16 | }, 17 | "required": [ "tags" ] 18 | } 19 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/components/EmailInput.js: -------------------------------------------------------------------------------- 1 | import FormInput from './FormInput'; 2 | import { IconEmail } from './icons'; 3 | 4 | export default ({value, error, children, onInput, className}) => ( 5 | 16 | {children} 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /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/web/admin/middleware.js: -------------------------------------------------------------------------------- 1 | const urlService = require('../../services/url'); 2 | 3 | function redirectAdminUrls(req, res, next) { 4 | const subdir = urlService.utils.getSubdir(), 5 | ghostPathRegex = new RegExp(`^${subdir}/ghost/(.+)`), 6 | ghostPathMatch = req.originalUrl.match(ghostPathRegex); 7 | 8 | if (ghostPathMatch) { 9 | return res.redirect(urlService.utils.urlJoin(urlService.utils.urlFor('admin'), '#', ghostPathMatch[1])); 10 | } 11 | 12 | next(); 13 | } 14 | 15 | module.exports = [ 16 | redirectAdminUrls 17 | ]; 18 | -------------------------------------------------------------------------------- /core/server/api/v2/images.js: -------------------------------------------------------------------------------- 1 | const storage = require('../../adapters/storage'); 2 | 3 | module.exports = { 4 | docName: 'images', 5 | upload: { 6 | statusCode: 201, 7 | permissions: false, 8 | query(frame) { 9 | const store = storage.getStorage(); 10 | 11 | if (frame.files) { 12 | return Promise 13 | .map(frame.files, file => store.save(file)) 14 | .then(paths => paths[0]); 15 | } 16 | return store.save(frame.file); 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /core/server/data/xml/sitemap/user-generator.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'), 2 | validator = require('validator'), 3 | BaseMapGenerator = require('./base-generator'); 4 | 5 | class UserMapGenerator extends BaseMapGenerator { 6 | constructor(opts) { 7 | super(); 8 | 9 | this.name = 'authors'; 10 | _.extend(this, opts); 11 | } 12 | 13 | validateImageUrl(imageUrl) { 14 | return imageUrl && validator.isURL(imageUrl, {protocols: ['http', 'https'], require_protocol: true}); 15 | } 16 | } 17 | 18 | module.exports = UserMapGenerator; 19 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/components/PasswordInput.js: -------------------------------------------------------------------------------- 1 | import FormInput from './FormInput'; 2 | import { IconLock } from './icons'; 3 | 4 | export default ({value, error, children, onInput, className}) => ( 5 | 16 | { children } 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/app/good.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | util = require('./goodlib.js'), 3 | nested = require('./nested/goodnested'); 4 | 5 | function GoodApp(app) { 6 | this.app = app; 7 | } 8 | 9 | GoodApp.prototype.install = function () { 10 | // Goes through app to do data 11 | this.app.something = 42; 12 | this.app.util = util; 13 | this.app.nested = nested; 14 | this.app.path = path.join(__dirname, 'good.js'); 15 | 16 | return true; 17 | }; 18 | 19 | GoodApp.prototype.activate = function () { 20 | }; 21 | 22 | module.exports = GoodApp; 23 | -------------------------------------------------------------------------------- /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 context. 7 | var proxy = require('./proxy'), 8 | getPaginatedUrl = proxy.metaData.getPaginatedUrl; 9 | 10 | // We use the name page_url to match the helper for consistency: 11 | module.exports = function page_url(page, options) { // eslint-disable-line camelcase 12 | if (!options) { 13 | options = page; 14 | page = 1; 15 | } 16 | return getPaginatedUrl(page, options.data.root); 17 | }; 18 | -------------------------------------------------------------------------------- /core/server/api/v2/site.js: -------------------------------------------------------------------------------- 1 | const ghostVersion = require('../../lib/ghost-version'); 2 | const settingsCache = require('../../services/settings/cache'); 3 | const urlService = require('../../services/url'); 4 | 5 | const site = { 6 | docName: 'site', 7 | 8 | read: { 9 | permissions: false, 10 | query() { 11 | return { 12 | title: settingsCache.get('title'), 13 | url: urlService.utils.urlFor('home', true), 14 | version: ghostVersion.safe 15 | }; 16 | } 17 | } 18 | }; 19 | 20 | module.exports = site; 21 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/input/tags.js: -------------------------------------------------------------------------------- 1 | const jsonSchema = require('../utils/json-schema'); 2 | 3 | module.exports = { 4 | add(apiConfig, frame) { 5 | const schema = require('./schemas/tags-add'); 6 | const definitions = require('./schemas/tags'); 7 | return jsonSchema.validate(schema, definitions, frame.data); 8 | }, 9 | 10 | edit(apiConfig, frame) { 11 | const schema = require('./schemas/tags-edit'); 12 | const definitions = require('./schemas/tags'); 13 | return jsonSchema.validate(schema, definitions, frame.data); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /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/services/rss/cache.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'), 2 | generateFeed = require('./generate-feed'), 3 | feedCache = {}; 4 | 5 | module.exports.getXML = function getFeedXml(baseUrl, data) { 6 | var dataHash = crypto.createHash('md5').update(JSON.stringify(data)).digest('hex'); 7 | if (!feedCache[baseUrl] || feedCache[baseUrl].hash !== dataHash) { 8 | // We need to regenerate 9 | feedCache[baseUrl] = { 10 | hash: dataHash, 11 | xml: generateFeed(baseUrl, data) 12 | }; 13 | } 14 | 15 | return feedCache[baseUrl].xml; 16 | }; 17 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/input/pages.js: -------------------------------------------------------------------------------- 1 | const jsonSchema = require('../utils/json-schema'); 2 | 3 | module.exports = { 4 | add(apiConfig, frame) { 5 | const schema = require(`./schemas/pages-add`); 6 | const definitions = require('./schemas/pages'); 7 | return jsonSchema.validate(schema, definitions, frame.data); 8 | }, 9 | 10 | edit(apiConfig, frame) { 11 | const schema = require(`./schemas/pages-edit`); 12 | const definitions = require('./schemas/pages'); 13 | return jsonSchema.validate(schema, definitions, frame.data); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/input/posts.js: -------------------------------------------------------------------------------- 1 | const jsonSchema = require('../utils/json-schema'); 2 | 3 | module.exports = { 4 | add(apiConfig, frame) { 5 | const schema = require(`./schemas/posts-add`); 6 | const definitions = require('./schemas/posts'); 7 | return jsonSchema.validate(schema, definitions, frame.data); 8 | }, 9 | 10 | edit(apiConfig, frame) { 11 | const schema = require(`./schemas/posts-edit`); 12 | const definitions = require('./schemas/posts'); 13 | return jsonSchema.validate(schema, definitions, frame.data); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/assets/images/icon-email.svg: -------------------------------------------------------------------------------- 1 | icon-email -------------------------------------------------------------------------------- /core/server/services/auth/passport.js: -------------------------------------------------------------------------------- 1 | var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy, 2 | BearerStrategy = require('passport-http-bearer').Strategy, 3 | passport = require('passport'), 4 | authStrategies = require('./auth-strategies'); 5 | 6 | /** 7 | * auth types: 8 | * - password: local login 9 | */ 10 | exports.init = function initPassport() { 11 | passport.use(new ClientPasswordStrategy(authStrategies.clientPasswordStrategy)); 12 | passport.use(new BearerStrategy(authStrategies.bearerStrategy)); 13 | 14 | return passport.initialize(); 15 | }; 16 | -------------------------------------------------------------------------------- /core/test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "strict": false, 5 | "sub": true, 6 | "eqeqeq": true, 7 | "laxbreak": true, 8 | "bitwise": true, 9 | "curly": true, 10 | "forin": true, 11 | "immed": true, 12 | "latedef": true, 13 | "newcap": true, 14 | "noarg": true, 15 | "noempty": true, 16 | "nonew": true, 17 | "plusplus": true, 18 | "regexp": true, 19 | "undef": true, 20 | "unused": true, 21 | "indent": 4, 22 | "quotmark": "single", 23 | "predef": [ "-Promise" ] 24 | } 25 | -------------------------------------------------------------------------------- /core/test/unit/helpers/template_spec.js: -------------------------------------------------------------------------------- 1 | var should = require('should'), 2 | hbs = require.main.require('core/server/services/themes/engine'), 3 | template = require.main.require('core/server/helpers/template'); 4 | 5 | describe('Helpers Template', function () { 6 | it('can execute a template', function () { 7 | hbs.registerPartial('test', '

Hello {{name}}

'); 8 | 9 | var safeString = template.execute('test', {name: 'world'}); 10 | 11 | should.exist(safeString); 12 | safeString.should.have.property('string').and.equal('

Hello world

'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /core/server/lib/common/events.js: -------------------------------------------------------------------------------- 1 | const events = require('events'), 2 | util = require('util'); 3 | let EventRegistry, 4 | EventRegistryInstance; 5 | 6 | EventRegistry = function () { 7 | events.EventEmitter.call(this); 8 | }; 9 | 10 | util.inherits(EventRegistry, events.EventEmitter); 11 | 12 | EventRegistry.prototype.onMany = function (arr, onEvent) { 13 | arr.forEach((eventName) => { 14 | this.on(eventName, onEvent); 15 | }); 16 | }; 17 | 18 | EventRegistryInstance = new EventRegistry(); 19 | EventRegistryInstance.setMaxListeners(100); 20 | 21 | module.exports = EventRegistryInstance; 22 | -------------------------------------------------------------------------------- /core/test/unit/lib/mobiledoc/atoms/soft-return_spec.js: -------------------------------------------------------------------------------- 1 | const should = require('should'); 2 | const atom = require('../../../../../server/lib/mobiledoc/atoms/soft-return'); 3 | const SimpleDom = require('simple-dom'); 4 | const serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap); 5 | 6 | describe('Soft return atom', function () { 7 | it('generates a `br` tag', function () { 8 | let opts = { 9 | env: { 10 | dom: new SimpleDom.Document() 11 | } 12 | }; 13 | 14 | serializer.serialize(atom.render(opts)).should.match('
'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /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 | const proxy = require('./proxy'), 6 | get = require('lodash/get'), 7 | getAssetUrl = proxy.metaData.getAssetUrl, 8 | SafeString = proxy.SafeString; 9 | 10 | module.exports = function asset(path, options) { 11 | const hasMinFile = get(options, 'hash.hasMinFile'); 12 | 13 | return new SafeString( 14 | getAssetUrl(path, hasMinFile) 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /core/server/web/admin/serviceworker.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('web:admin:serviceworker'); 2 | const path = require('path'); 3 | 4 | // Route: index 5 | // Path: /ghost/sw.js|sw-registration.js 6 | // Method: GET 7 | module.exports = function adminController(req, res) { 8 | debug('serviceworker called'); 9 | 10 | const sw = path.join(__dirname, '..', '..', '..', 'built', 'assets', 'sw.js'), 11 | swr = path.join(__dirname, '..', '..', '..', 'built', 'assets', 'sw-registration.js'), 12 | fileToSend = req.url === '/sw.js' ? sw : swr; 13 | 14 | res.sendFile(fileToSend); 15 | }; 16 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/input/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get posts() { 3 | return require('./posts'); 4 | }, 5 | 6 | get pages() { 7 | return require('./pages'); 8 | }, 9 | 10 | get invites() { 11 | return require('./invites'); 12 | }, 13 | 14 | get settings() { 15 | return require('./settings'); 16 | }, 17 | 18 | get tags() { 19 | return require('./tags'); 20 | }, 21 | 22 | get users() { 23 | return require('./users'); 24 | }, 25 | 26 | get images() { 27 | return require('./images'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/input/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get db() { 3 | return require('./db'); 4 | }, 5 | 6 | get integrations() { 7 | return require('./integrations'); 8 | }, 9 | 10 | get pages() { 11 | return require('./pages'); 12 | }, 13 | 14 | get posts() { 15 | return require('./posts'); 16 | }, 17 | 18 | get settings() { 19 | return require('./settings'); 20 | }, 21 | 22 | get users() { 23 | return require('./users'); 24 | }, 25 | 26 | get tags() { 27 | return require('./tags'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /core/server/helpers/lang.js: -------------------------------------------------------------------------------- 1 | // # lang helper 2 | // {{lang}} gives the current language tag 3 | // Usage example: 4 | // 5 | // Examples of language tags from RFC 5646: 6 | // de (German) 7 | // fr (French) 8 | // ja (Japanese) 9 | // en-US (English as used in the United States) 10 | // 11 | // Standard: 12 | // Language tags in HTML and XML 13 | // https://www.w3.org/International/articles/language-tags/ 14 | 15 | var proxy = require('./proxy'), 16 | i18n = proxy.i18n, 17 | SafeString = proxy.SafeString; 18 | 19 | module.exports = function lang() { 20 | return new SafeString(i18n.locale()); 21 | }; 22 | -------------------------------------------------------------------------------- /core/test/unit/lib/mobiledoc/cards/hr_spec.js: -------------------------------------------------------------------------------- 1 | const should = require('should'); 2 | const card = require('../../../../../server/lib/mobiledoc/cards/hr'); 3 | const SimpleDom = require('simple-dom'); 4 | const serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap); 5 | 6 | describe('HR card', function () { 7 | it('generates a horizontal rule', function () { 8 | let opts = { 9 | env: { 10 | dom: new SimpleDom.Document() 11 | } 12 | }; 13 | 14 | serializer.serialize(card.render(opts)).should.match('
'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /core/server/api/v0.1/slack.js: -------------------------------------------------------------------------------- 1 | // # Slack API 2 | // API for sending Test Notifications to Slack 3 | const Promise = require('bluebird'), 4 | common = require('../../lib/common'); 5 | 6 | let slack; 7 | 8 | /** 9 | * ## Slack API Method 10 | * 11 | * **See:** [API Methods](constants.js.html#api%20methods) 12 | * @typedef Slack 13 | * @param slack 14 | */ 15 | slack = { 16 | /** 17 | * ### SendTest 18 | * Send a test notification 19 | * 20 | * @public 21 | */ 22 | sendTest() { 23 | common.events.emit('slack.test'); 24 | return Promise.resolve(); 25 | } 26 | }; 27 | 28 | module.exports = slack; 29 | -------------------------------------------------------------------------------- /core/server/config/env/config.development.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "http://localhost:2368", 3 | "database": { 4 | "client": "sqlite3", 5 | "connection": { 6 | "filename": "content/data/ghost-dev.db" 7 | }, 8 | "debug": false 9 | }, 10 | "paths": { 11 | "contentPath": "content/" 12 | }, 13 | "privacy": { 14 | "useRpcPing": false, 15 | "useUpdateCheck": true 16 | }, 17 | "useMinFiles": false, 18 | "caching": { 19 | "theme": { 20 | "maxAge": 0 21 | }, 22 | "admin": { 23 | "maxAge": 0 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/server/services/auth/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get authorize() { 3 | return require('./authorize'); 4 | }, 5 | 6 | get authenticate() { 7 | return require('./authenticate'); 8 | }, 9 | 10 | get session() { 11 | return require('./session'); 12 | }, 13 | /* 14 | * TODO: Get rid of these when v0.1 is gone 15 | */ 16 | get init() { 17 | return (options) => { 18 | require('./oauth').init(options); 19 | return require('./passport').init(options); 20 | }; 21 | }, 22 | get oauth() { 23 | return require('./oauth'); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper-1.4/README.md: -------------------------------------------------------------------------------- 1 | # Casper 2 | 3 | The default theme for [Ghost](http://github.com/tryghost/ghost/). Casper 1.4 is the last release of Casper's original design, created to be compatible **ONLY with Ghost 1.0** and above. If you are running an earlier version of Ghost, you will need [Casper 1.3.7](https://github.com/TryGhost/Casper/releases/tag/1.3.7). For all other versions of Ghost, we recommend switching to the [latest release](https://github.com/TryGhost/Casper/releases) of Casper, with an updated design. 4 | 5 | ## Copyright & License 6 | 7 | Copyright (c) 2013-2019 Ghost Foundation - Released under the [MIT license](LICENSE). 8 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get permissions() { 3 | return require('./permissions'); 4 | }, 5 | 6 | get serializers() { 7 | return require('./serializers'); 8 | }, 9 | 10 | get validators() { 11 | return require('./validators'); 12 | }, 13 | 14 | isContentAPI: (frame) => { 15 | return frame.apiType === 'content'; 16 | }, 17 | 18 | isAdminAPIKey: (frame) => { 19 | return frame.options.context && Object.keys(frame.options.context).length !== 0 && frame.options.context.api_key && 20 | frame.options.context.api_key.type === 'admin'; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/input/settings.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | 3 | module.exports = { 4 | edit(apiConfig, frame) { 5 | // CASE: allow shorthand syntax where a single key and value are passed to edit instead of object and options 6 | if (_.isString(frame.data)) { 7 | frame.data = {settings: [{key: frame.data, value: frame.options}]}; 8 | } 9 | 10 | // prepare data 11 | frame.data.settings.forEach((setting) => { 12 | if (!_.isString(setting.value)) { 13 | setting.value = JSON.stringify(setting.value); 14 | } 15 | }); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /core/server/services/members/index.js: -------------------------------------------------------------------------------- 1 | const config = require('../../config/index.js'); 2 | const common = require('../../lib/common'); 3 | 4 | module.exports = { 5 | get api() { 6 | if (!config.get('enableDeveloperExperiments')) { 7 | return { 8 | apiRouter: function (req, res, next) { 9 | return next(new common.errors.NotFoundError()); 10 | }, 11 | staticRouter: function (req, res, next) { 12 | return next(new common.errors.NotFoundError()); 13 | } 14 | }; 15 | } 16 | return require('./api'); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/input/users.js: -------------------------------------------------------------------------------- 1 | const Promise = require('bluebird'); 2 | const debug = require('ghost-ignition').debug('api:v2:utils:validators:input:users'); 3 | const common = require('../../../../../lib/common'); 4 | 5 | module.exports = { 6 | changePassword(apiConfig, frame) { 7 | debug('changePassword'); 8 | 9 | const data = frame.data.password[0]; 10 | 11 | if (data.newPassword !== data.ne2Password) { 12 | return Promise.reject(new common.errors.ValidationError({ 13 | message: common.i18n.t('errors.models.user.newPasswordsDoNotMatch') 14 | })); 15 | } 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /core/test/acceptance/README.md: -------------------------------------------------------------------------------- 1 | ## Acceptance Tests 2 | 3 | This folder should only contain a set of basic API use cases. 4 | 5 | We are currently refactoring the test env. The "old" folder currently contains all API tests for the 6 | stable API version (v2). The goal is: 7 | 8 | - either keep a test if it's a basic use case e.g. upload an image, schedule a post, download a theme 9 | - otherwise move the test to regression api v2 tests 10 | 11 | We probably need a differentiation for the acceptance tests for session and api_key authentication. 12 | 13 | Before we move tests: 14 | 15 | - we have to re-work how are test utility is structured 16 | - we have to reduce tests 17 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/utils/date.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment-timezone'); 2 | const settingsCache = require('../../../../../../services/settings/cache'); 3 | 4 | const format = (date) => { 5 | return moment(date) 6 | .tz(settingsCache.get('active_timezone')) 7 | .toISOString(true); 8 | }; 9 | 10 | const forPost = (attrs) => { 11 | ['created_at', 'updated_at', 'published_at'].forEach((field) => { 12 | if (attrs[field]) { 13 | attrs[field] = format(attrs[field]); 14 | } 15 | }); 16 | 17 | return attrs; 18 | }; 19 | 20 | module.exports.format = format; 21 | module.exports.forPost = forPost; 22 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/input/invites.js: -------------------------------------------------------------------------------- 1 | const Promise = require('bluebird'); 2 | const common = require('../../../../../lib/common'); 3 | const models = require('../../../../../models'); 4 | 5 | module.exports = { 6 | add(apiConfig, frame) { 7 | return models.User.findOne({email: frame.data.invites[0].email}, frame.options) 8 | .then((user) => { 9 | if (user) { 10 | return Promise.reject(new common.errors.ValidationError({ 11 | message: common.i18n.t('errors.api.users.userAlreadyRegistered') 12 | })); 13 | } 14 | }); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /core/server/web/shared/middlewares/labs.js: -------------------------------------------------------------------------------- 1 | const labsUtil = require('../../../services/labs'); 2 | const common = require('../../../lib/common'); 3 | 4 | const labs = { 5 | subscribers(req, res, next) { 6 | if (labsUtil.isSet('subscribers') === true) { 7 | return next(); 8 | } else { 9 | return next(new common.errors.NotFoundError()); 10 | } 11 | }, 12 | members(req, res, next) { 13 | if (labsUtil.isSet('members') === true) { 14 | return next(); 15 | } else { 16 | return next(new common.errors.NotFoundError()); 17 | } 18 | } 19 | }; 20 | 21 | module.exports = labs; 22 | -------------------------------------------------------------------------------- /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 proxy = require('./proxy'), 8 | SafeString = proxy.SafeString, 9 | getMetaDataUrl = proxy.metaData.getMetaDataUrl; 10 | 11 | module.exports = function url(options) { 12 | var absolute = options && options.hash.absolute, 13 | outputUrl = getMetaDataUrl(this, absolute); 14 | 15 | outputUrl = encodeURI(decodeURI(outputUrl)); 16 | 17 | return new SafeString(outputUrl); 18 | }; 19 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/assets/images/icon-lock.svg: -------------------------------------------------------------------------------- 1 | icon-lock -------------------------------------------------------------------------------- /core/server/lib/security/url.js: -------------------------------------------------------------------------------- 1 | // The token is encoded URL safe by replacing '+' with '-', '\' with '_' and removing '=' 2 | // NOTE: the token is not encoded using valid base64 anymore 3 | module.exports.encodeBase64 = function encodeBase64(base64String) { 4 | return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); 5 | }; 6 | 7 | // Decode url safe base64 encoding and add padding ('=') 8 | module.exports.decodeBase64 = function decodeBase64(base64String) { 9 | base64String = base64String.replace(/-/g, '+').replace(/_/g, '/'); 10 | while (base64String.length % 4) { 11 | base64String += '='; 12 | } 13 | return base64String; 14 | }; 15 | -------------------------------------------------------------------------------- /core/server/lib/security/password.js: -------------------------------------------------------------------------------- 1 | const Promise = require('bluebird'); 2 | 3 | module.exports.hash = function hash(plainPassword) { 4 | const bcrypt = require('bcryptjs'), 5 | bcryptGenSalt = Promise.promisify(bcrypt.genSalt), 6 | bcryptHash = Promise.promisify(bcrypt.hash); 7 | 8 | return bcryptGenSalt().then(function (salt) { 9 | return bcryptHash(plainPassword, salt); 10 | }); 11 | }; 12 | 13 | module.exports.compare = function compare(plainPassword, hashedPassword) { 14 | const bcrypt = require('bcryptjs'), 15 | bcryptCompare = Promise.promisify(bcrypt.compare); 16 | 17 | return bcryptCompare(plainPassword, hashedPassword); 18 | }; 19 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/mail.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:mail'); 3 | 4 | module.exports = { 5 | all(response, apiConfig, frame) { 6 | const toReturn = _.cloneDeep(frame.data); 7 | 8 | delete toReturn.mail[0].options; 9 | // Sendmail returns extra details we don't need and that don't convert to JSON 10 | delete toReturn.mail[0].message.transport; 11 | 12 | toReturn.mail[0].status = { 13 | message: response.message 14 | }; 15 | 16 | frame.response = toReturn; 17 | 18 | debug(frame.response); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /core/server/data/meta/author_image.js: -------------------------------------------------------------------------------- 1 | var urlService = require('../../services/url'), 2 | getContextObject = require('./context_object.js'), 3 | _ = require('lodash'); 4 | 5 | function getAuthorImage(data, absolute) { 6 | var context = data.context ? data.context : null, 7 | contextObject = getContextObject(data, context); 8 | 9 | if ((_.includes(context, 'post') || _.includes(context, 'page')) && contextObject.primary_author && contextObject.primary_author.profile_image) { 10 | return urlService.utils.urlFor('image', {image: contextObject.primary_author.profile_image}, absolute); 11 | } 12 | return null; 13 | } 14 | 15 | module.exports = getAuthorImage; 16 | -------------------------------------------------------------------------------- /core/server/data/meta/creator_url.js: -------------------------------------------------------------------------------- 1 | var getContextObject = require('./context_object.js'), 2 | _ = require('lodash'); 3 | 4 | function getCreatorTwitterUrl(data) { 5 | var context = data.context ? data.context : null, 6 | contextObject = getContextObject(data, context); 7 | 8 | if ((_.includes(context, 'post') || _.includes(context, 'page')) && contextObject.primary_author && contextObject.primary_author.twitter) { 9 | return contextObject.primary_author.twitter; 10 | } else if (_.includes(context, 'author') && contextObject.twitter) { 11 | return contextObject.twitter; 12 | } 13 | return null; 14 | } 15 | 16 | module.exports = getCreatorTwitterUrl; 17 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/input/schemas/pages-add.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "$id": "pages.add", 5 | "title": "pages.add", 6 | "description": "Schema for pages.add", 7 | "type": "object", 8 | "additionalProperties": false, 9 | "properties": { 10 | "pages": { 11 | "type": "array", 12 | "minItems": 1, 13 | "maxItems": 1, 14 | "items": { 15 | "type": "object", 16 | "allOf": [{"$ref": "pages#/definitions/page"}], 17 | "required": ["title"] 18 | } 19 | } 20 | }, 21 | "required": ["pages"] 22 | } 23 | -------------------------------------------------------------------------------- /core/server/helpers/tpl/subscribe_form.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{! This is required for the form to work correctly }} 3 | {{hidden}} 4 | 5 |
6 | {{input_email id=input_id class=input_class placeholder=placeholder value=email autofocus=autofocus}} 7 |
8 | 9 | {{! This is used to get extra info about where this subscriber came from }} 10 | {{script}} 11 |
12 | 13 | {{#if error}} 14 |

{{error.message}}

15 | {{/if}} 16 | -------------------------------------------------------------------------------- /core/test/utils/mocks/utils.js: -------------------------------------------------------------------------------- 1 | var Module = require('module'), 2 | originalRequireFn = Module.prototype.require; 3 | 4 | /** 5 | * helper fn to mock non existent modules 6 | * mocks.utils.mockNotExistingModule(/pattern/, mockedModule) 7 | */ 8 | exports.mockNotExistingModule = function mockNotExistingModule(modulePath, module) { 9 | Module.prototype.require = function (path) { 10 | if (path.match(modulePath)) { 11 | return module; 12 | } 13 | 14 | return originalRequireFn.apply(this, arguments); 15 | }; 16 | }; 17 | 18 | exports.unmockNotExistingModule = function unmockNotExistingModule() { 19 | Module.prototype.require = originalRequireFn; 20 | }; 21 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/input/schemas/posts-add.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "$id": "posts.add", 5 | "title": "posts.add", 6 | "description": "Schema for posts.add", 7 | "type": "object", 8 | "additionalProperties": false, 9 | "properties": { 10 | "posts": { 11 | "type": "array", 12 | "minItems": 1, 13 | "maxItems": 1, 14 | "items": { 15 | "type": "object", 16 | "allOf": [{"$ref": "posts#/definitions/post"}], 17 | "required": ["title"] 18 | } 19 | } 20 | }, 21 | "required": [ "posts" ] 22 | } 23 | -------------------------------------------------------------------------------- /core/server/data/importer/importers/data/subscribers.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('importer:subscribers'), 2 | BaseImporter = require('./base'); 3 | 4 | class SubscribersImporter extends BaseImporter { 5 | constructor(allDataFromFile) { 6 | super(allDataFromFile, { 7 | modelName: 'Subscriber', 8 | dataKeyToImport: 'subscribers' 9 | }); 10 | } 11 | 12 | beforeImport() { 13 | debug('beforeImport'); 14 | return super.beforeImport(); 15 | } 16 | 17 | doImport(options, importOptions) { 18 | return super.doImport(options, importOptions); 19 | } 20 | } 21 | 22 | module.exports = SubscribersImporter; 23 | -------------------------------------------------------------------------------- /core/server/data/meta/author_fb_url.js: -------------------------------------------------------------------------------- 1 | var getContextObject = require('./context_object.js'), 2 | _ = require('lodash'); 3 | 4 | function getAuthorFacebookUrl(data) { 5 | var context = data.context ? data.context : null, 6 | contextObject = getContextObject(data, context); 7 | 8 | if ((_.includes(context, 'post') || _.includes(context, 'page')) && contextObject.primary_author && contextObject.primary_author.facebook) { 9 | return contextObject.primary_author.facebook; 10 | } else if (_.includes(context, 'author') && contextObject.facebook) { 11 | return contextObject.facebook; 12 | } 13 | return null; 14 | } 15 | 16 | module.exports = getAuthorFacebookUrl; 17 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/input/schemas/pages-edit.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "$id": "pages.edit", 5 | "title": "pages.edit", 6 | "description": "Schema for pages.edit", 7 | "type": "object", 8 | "additionalProperties": false, 9 | "properties": { 10 | "pages": { 11 | "type": "array", 12 | "minItems": 1, 13 | "maxItems": 1, 14 | "items": { 15 | "type": "object", 16 | "allOf": [{"$ref": "pages#/definitions/page"}], 17 | "required": ["updated_at"] 18 | } 19 | } 20 | }, 21 | "required": ["pages"] 22 | } 23 | -------------------------------------------------------------------------------- /core/server/web/shared/middlewares/maintenance.js: -------------------------------------------------------------------------------- 1 | const config = require('../../../config'); 2 | const common = require('../../../lib/common'); 3 | const urlService = require('../../../services/url'); 4 | 5 | module.exports = function maintenance(req, res, next) { 6 | if (config.get('maintenance').enabled) { 7 | return next(new common.errors.MaintenanceError({ 8 | message: common.i18n.t('errors.general.maintenance') 9 | })); 10 | } 11 | 12 | if (!urlService.hasFinished()) { 13 | return next(new common.errors.MaintenanceError({ 14 | message: common.i18n.t('errors.general.maintenanceUrlService') 15 | })); 16 | } 17 | 18 | next(); 19 | }; 20 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/input/schemas/posts-edit.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "$id": "posts.edit", 5 | "title": "posts.edit", 6 | "description": "Schema for posts.edit", 7 | "type": "object", 8 | "additionalProperties": false, 9 | "properties": { 10 | "posts": { 11 | "type": "array", 12 | "minItems": 1, 13 | "maxItems": 1, 14 | "items": { 15 | "type": "object", 16 | "allOf": [{"$ref": "posts#/definitions/post"}], 17 | "required": ["updated_at"] 18 | } 19 | } 20 | }, 21 | "required": [ "posts" ] 22 | } 23 | -------------------------------------------------------------------------------- /core/test/unit/lib/security/password_spec.js: -------------------------------------------------------------------------------- 1 | const should = require('should'), 2 | security = require('../../../../server/lib/security'); 3 | 4 | describe('Lib: Security - Password', function () { 5 | it('hash plain password', function () { 6 | return security.password.hash('test') 7 | .then(function (hash) { 8 | hash.should.match(/^\$2[ayb]\$.{56}$/); 9 | }); 10 | }); 11 | 12 | it('compare password', function () { 13 | return security.password.compare('test', '$2a$10$we16f8rpbrFZ34xWj0/ZC.LTPUux8ler7bcdTs5qIleN6srRHhilG') 14 | .then(function (valid) { 15 | valid.should.be.true; 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /core/test/utils/assertions.js: -------------------------------------------------------------------------------- 1 | var should = require('should'), 2 | errorProps = ['message', 'errorType']; 3 | 4 | should.Assertion.add('JSONErrorObject', function () { 5 | this.params = {operator: 'to be a valid JSON Error Object'}; 6 | this.obj.should.be.an.Object(); 7 | this.obj.should.have.properties(errorProps); 8 | }); 9 | 10 | should.Assertion.add('JSONErrorResponse', function () { 11 | this.params = {operator: 'to be a valid JSON Error Response'}; 12 | 13 | this.obj.should.have.property('errors').which.is.an.Array(); 14 | this.obj.errors.length.should.be.above(0); 15 | 16 | this.obj.errors.forEach(function (err) { 17 | err.should.be.a.JSONErrorObject(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 90 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | # exemptLabels: 7 | # Label to use when marking an issue as stale 8 | staleLabel: stale 9 | # Comment to post when marking an issue as stale. Set to `false` to disable 10 | markComment: > 11 | This issue has been automatically marked as stale because it has not had 12 | recent activity. It will be closed if no further activity occurs. Thank you 13 | for your contributions. 14 | # Comment to post when closing a stale issue. Set to `false` to disable 15 | closeComment: false 16 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/input/authors.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:authors'); 2 | const utils = require('../../index'); 3 | 4 | function setDefaultOrder(frame) { 5 | if (!frame.options.order) { 6 | frame.options.order = 'name asc'; 7 | } 8 | } 9 | 10 | module.exports = { 11 | browse(apiConfig, frame) { 12 | debug('browse'); 13 | 14 | if (utils.isContentAPI(frame)) { 15 | setDefaultOrder(frame); 16 | } 17 | }, 18 | 19 | read(apiConfig, frame) { 20 | debug('read'); 21 | 22 | if (utils.isContentAPI(frame)) { 23 | setDefaultOrder(frame); 24 | } 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/themes.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:themes'); 2 | 3 | module.exports = { 4 | browse(themes, apiConfig, frame) { 5 | debug('browse'); 6 | 7 | frame.response = themes; 8 | 9 | debug(frame.response); 10 | }, 11 | 12 | upload() { 13 | debug('upload'); 14 | this.browse(...arguments); 15 | }, 16 | 17 | activate() { 18 | debug('activate'); 19 | this.browse(...arguments); 20 | }, 21 | 22 | download(fn, apiConfig, frame) { 23 | debug('download'); 24 | 25 | frame.response = fn; 26 | 27 | debug(frame.response); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/index.js: -------------------------------------------------------------------------------- 1 | import './styles/members.css'; 2 | import { Component } from 'preact'; 3 | 4 | import MembersProvider from './components/MembersProvider'; 5 | import Modal from './components/Modal'; 6 | 7 | export default class App extends Component { 8 | constructor() { 9 | super(); 10 | const apiUrl = window.location.href.substring(0, window.location.href.indexOf('/members/auth')); 11 | 12 | this.state = { 13 | apiUrl 14 | }; 15 | } 16 | 17 | render() { 18 | return ( 19 | 20 | 21 | 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper-1.4/partials/navigation.hbs: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/invites.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:invites'); 2 | 3 | module.exports = { 4 | all(models, apiConfig, frame) { 5 | debug('all'); 6 | 7 | if (!models) { 8 | return; 9 | } 10 | 11 | if (models.meta) { 12 | frame.response = { 13 | invites: models.data.map(model => model.toJSON(frame.options)), 14 | meta: models.meta 15 | }; 16 | 17 | return; 18 | } 19 | 20 | frame.response = { 21 | invites: [models.toJSON(frame.options)] 22 | }; 23 | 24 | debug(frame.response); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /core/server/data/importer/importers/data/roles.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('importer:roles'), 2 | BaseImporter = require('./base'); 3 | 4 | class RolesImporter extends BaseImporter { 5 | constructor(allDataFromFile) { 6 | super(allDataFromFile, { 7 | modelName: 'Role', 8 | dataKeyToImport: 'roles' 9 | }); 10 | 11 | this.errorConfig.returnDuplicates = false; 12 | } 13 | 14 | beforeImport() { 15 | debug('beforeImport'); 16 | return super.beforeImport(); 17 | } 18 | 19 | doImport(options, importOptions) { 20 | return super.doImport(options, importOptions); 21 | } 22 | } 23 | 24 | module.exports = RolesImporter; 25 | -------------------------------------------------------------------------------- /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 | var proxy = require('./proxy'), 6 | socialUrls = proxy.socialUrls, 7 | localUtils = proxy.localUtils; 8 | 9 | // We use the name twitter_url to match the helper for consistency: 10 | module.exports = function twitter_url(username, options) { // eslint-disable-line camelcase 11 | if (!options) { 12 | options = username; 13 | username = localUtils.findKey('twitter', this, options.data.blog); 14 | } 15 | 16 | if (username) { 17 | return socialUrls.twitter(username); 18 | } 19 | 20 | return null; 21 | }; 22 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/assets/images/ghost-logo.svg: -------------------------------------------------------------------------------- 1 | Brand/Ghost Logotype - Light -------------------------------------------------------------------------------- /core/server/services/settings/public.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The settings with type "blog" were originally meant to be public 3 | * This has been misused - unsplash and slack are incorrectly stored there 4 | * https://github.com/TryGhost/Ghost/issues/10318 5 | * 6 | * This file acts as a new whitelist for "public" settings 7 | */ 8 | 9 | module.exports = { 10 | title: 'title', 11 | description: 'description', 12 | logo: 'logo', 13 | icon: 'icon', 14 | cover_image: 'cover_image', 15 | facebook: 'facebook', 16 | twitter: 'twitter', 17 | default_locale: 'lang', 18 | active_timezone: 'timezone', 19 | ghost_head: 'ghost_head', 20 | ghost_foot: 'ghost_foot', 21 | navigation: 'navigation' 22 | }; 23 | -------------------------------------------------------------------------------- /core/server/lib/mobiledoc/cards/code.js: -------------------------------------------------------------------------------- 1 | const createCard = require('../create-card'); 2 | 3 | module.exports = createCard({ 4 | name: 'code', 5 | type: 'dom', 6 | render(opts) { 7 | let payload = opts.payload; 8 | let dom = opts.env.dom; 9 | 10 | if (!payload.code) { 11 | return ''; 12 | } 13 | 14 | let pre = dom.createElement('pre'); 15 | let code = dom.createElement('code'); 16 | 17 | if (payload.language) { 18 | code.setAttribute('class', `language-${payload.language}`); 19 | } 20 | 21 | code.appendChild(dom.createTextNode(payload.code)); 22 | pre.appendChild(code); 23 | 24 | return pre; 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/members.js: -------------------------------------------------------------------------------- 1 | const common = require('../../../../../lib/common'); 2 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:members'); 3 | 4 | module.exports = { 5 | browse(data, apiConfig, frame) { 6 | debug('browse'); 7 | 8 | frame.response = data; 9 | }, 10 | 11 | read(data, apiConfig, frame) { 12 | debug('read'); 13 | 14 | if (!data) { 15 | return Promise.reject(new common.errors.NotFoundError({ 16 | message: common.i18n.t('errors.api.members.memberNotFound') 17 | })); 18 | } 19 | 20 | frame.response = { 21 | members: [data] 22 | }; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /core/server/data/meta/author_url.js: -------------------------------------------------------------------------------- 1 | var urlService = require('../../services/url'); 2 | 3 | function getAuthorUrl(data, absolute) { 4 | var context = data.context ? data.context[0] : null; 5 | 6 | context = context === 'amp' ? 'post' : context; 7 | 8 | if (data.author) { 9 | return urlService.getUrlByResourceId(data.author.id, {absolute: absolute, secure: data.author.secure, withSubdirectory: true}); 10 | } 11 | 12 | if (data[context] && data[context].primary_author) { 13 | return urlService.getUrlByResourceId(data[context].primary_author.id, {absolute: absolute, secure: data[context].secure, withSubdirectory: true}); 14 | } 15 | 16 | return null; 17 | } 18 | 19 | module.exports = getAuthorUrl; 20 | -------------------------------------------------------------------------------- /core/server/helpers/facebook_url.js: -------------------------------------------------------------------------------- 1 | // # Facebook URL Helper 2 | // Usage: `{{facebook_url}}` or `{{facebook_url author.facebook}}` 3 | // 4 | // Output a url for a facebook username 5 | var proxy = require('./proxy'), 6 | socialUrls = proxy.socialUrls, 7 | localUtils = proxy.localUtils; 8 | 9 | // We use the name facebook_url to match the helper for consistency: 10 | module.exports = function facebook_url(username, options) { // eslint-disable-line camelcase 11 | if (!options) { 12 | options = username; 13 | username = localUtils.findKey('facebook', this, options.data.blog); 14 | } 15 | 16 | if (username) { 17 | return socialUrls.facebook(username); 18 | } 19 | 20 | return null; 21 | }; 22 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/authors.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:authors'); 2 | const mapper = require('./utils/mapper'); 3 | 4 | module.exports = { 5 | browse(models, apiConfig, frame) { 6 | debug('browse'); 7 | 8 | frame.response = { 9 | authors: models.data.map(model => mapper.mapUser(model, frame)), 10 | meta: models.meta 11 | }; 12 | 13 | debug(frame.response); 14 | }, 15 | 16 | read(model, apiConfig, frame) { 17 | debug('read'); 18 | 19 | frame.response = { 20 | authors: [mapper.mapUser(model, frame)] 21 | }; 22 | 23 | debug(frame.response); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/input/schemas/tags-add.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "$id": "tags.add", 5 | "title": "tags.add", 6 | "description": "Schema for tags.add", 7 | "type": "object", 8 | "additionalProperties": false, 9 | "properties": { 10 | "tags": { 11 | "type": "array", 12 | "minItems": 1, 13 | "maxItems": 1, 14 | "additionalProperties": false, 15 | "items": { 16 | "type": "object", 17 | "allOf": [{"$ref": "tags#/definitions/tag"}], 18 | "required": ["name"] 19 | } 20 | } 21 | }, 22 | "required": [ "tags" ] 23 | } 24 | -------------------------------------------------------------------------------- /core/server/web/shared/middlewares/pretty-urls.js: -------------------------------------------------------------------------------- 1 | // Pretty URL redirects 2 | // 3 | // These are two pieces of middleware that handle ensuring that 4 | // URLs get formatted correctly. 5 | // Slashes ensures that we get trailing slashes 6 | // Uncapitalise changes case to lowercase 7 | // @TODO optimise this to reduce the number of redirects required to get to a pretty URL 8 | // @TODO move this to being used by routers? 9 | const slashes = require('connect-slashes'); 10 | const config = require('../../../config'); 11 | 12 | module.exports = [ 13 | slashes(true, { 14 | headers: { 15 | 'Cache-Control': `public, max-age=${config.get('caching:301:maxAge')}` 16 | } 17 | }), 18 | require('./uncapitalise') 19 | ]; 20 | -------------------------------------------------------------------------------- /core/server/data/meta/excerpt.js: -------------------------------------------------------------------------------- 1 | var downsize = require('downsize'); 2 | 3 | function getExcerpt(html, truncateOptions) { 4 | truncateOptions = truncateOptions || {}; 5 | // Strip inline and bottom footnotes 6 | var excerpt = html.replace(/.*?<\/a>/gi, ''); 7 | excerpt = excerpt.replace(/
    .*?<\/ol><\/div>/, ''); 8 | // Strip other html 9 | excerpt = excerpt.replace(/<\/?[^>]+>/gi, ''); 10 | excerpt = excerpt.replace(/(\r\n|\n|\r)+/gm, ' '); 11 | 12 | if (!truncateOptions.words && !truncateOptions.characters) { 13 | truncateOptions.words = 50; 14 | } 15 | 16 | return downsize(excerpt, truncateOptions); 17 | } 18 | 19 | module.exports = getExcerpt; 20 | -------------------------------------------------------------------------------- /core/server/public/ghost-sdk.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/test/unit/services/auth/passport_spec.js: -------------------------------------------------------------------------------- 1 | var should = require('should'), 2 | sinon = require('sinon'), 3 | passport = require('passport'), 4 | ghostPassport = require('../../../../server/services/auth/passport'); 5 | 6 | describe('Ghost Passport', function () { 7 | beforeEach(function () { 8 | sinon.spy(passport, 'use'); 9 | }); 10 | 11 | afterEach(function () { 12 | sinon.restore(); 13 | }); 14 | 15 | describe('[default] local auth', function () { 16 | it('initialise passport with passport auth type', function () { 17 | var response = ghostPassport.init(); 18 | should.exist(response); 19 | passport.use.callCount.should.eql(2); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /core/test/unit/web/shared/middleware/api/spam-prevention_spec.js: -------------------------------------------------------------------------------- 1 | const should = require('should'); 2 | const spamPrevention = require('../../../../../../server/web/shared/middlewares/api/spam-prevention'); 3 | 4 | describe('Spam Prevention', function () { 5 | it('exports a contentApiKey method', function () { 6 | should.equal(typeof spamPrevention.contentApiKey, 'function'); 7 | }); 8 | 9 | describe('contentApiKey method', function () { 10 | it('returns an instance of express-brute', function () { 11 | const ExpressBrute = require('express-brute'); 12 | const result = spamPrevention.contentApiKey(); 13 | 14 | should.equal(result instanceof ExpressBrute, true); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/tags.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:tags'); 2 | const mapper = require('./utils/mapper'); 3 | 4 | module.exports = { 5 | all(models, apiConfig, frame) { 6 | debug('all'); 7 | 8 | if (!models) { 9 | return; 10 | } 11 | 12 | if (models.meta) { 13 | frame.response = { 14 | tags: models.data.map(model => mapper.mapTag(model, frame)), 15 | meta: models.meta 16 | }; 17 | 18 | return; 19 | } 20 | 21 | frame.response = { 22 | tags: [mapper.mapTag(models, frame)] 23 | }; 24 | 25 | debug(frame.response); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/pages/PasswordResetSentPage.js: -------------------------------------------------------------------------------- 1 | import FormHeader from '../components/FormHeader'; 2 | import FormSubmit from '../components/FormSubmit'; 3 | 4 | import Form from '../components/Form'; 5 | 6 | export default ({ error, handleSubmit }) => ( 7 |
    8 | 9 |
    10 |
    11 |

    We’ve sent a recovery email to your inbox. Follow the link in the email to reset your password.

    12 |
    13 | 14 | 15 |
    16 | ); 17 | -------------------------------------------------------------------------------- /core/server/services/routing/helpers/render-entry.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('services:routing:helpers:render-post'), 2 | formatResponse = require('./format-response'), 3 | renderer = require('./renderer'); 4 | /* 5 | * Sets the response context around an entry (post or page) 6 | * and renders it with the correct template. 7 | * Used by post preview and entry methods. 8 | * Returns a function that takes the entry to be rendered. 9 | */ 10 | module.exports = function renderEntry(req, res) { 11 | debug('renderEntry called'); 12 | return function renderEntry(entry) { 13 | // Format data 2 - 1 is in preview/entry 14 | // Render 15 | return renderer(req, res, formatResponse.entry(entry)); 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /core/server/apps/subscribers/index.js: -------------------------------------------------------------------------------- 1 | const router = require('./lib/router'), 2 | registerHelpers = require('./lib/helpers'), 3 | // Dirty requires 4 | labs = require('../../services/labs'); 5 | 6 | module.exports = { 7 | activate(ghost) { 8 | // routeKeywords.subscribe: 'subscribe' 9 | const subscribeRoute = '/subscribe/'; 10 | // TODO, how to do all this only if the Subscribers flag is set?! 11 | registerHelpers(ghost); 12 | 13 | ghost.routeService.registerRouter(subscribeRoute, function labsEnabledRouter(req, res, next) { 14 | if (labs.isSet('subscribers')) { 15 | return router.apply(this, arguments); 16 | } 17 | 18 | next(); 19 | }); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /core/server/apps/subscribers/lib/helpers/index.js: -------------------------------------------------------------------------------- 1 | // Dirty requires! 2 | const labs = require('../../../../services/labs'); 3 | 4 | module.exports = function registerHelpers(ghost) { 5 | ghost.helpers.register('input_email', require('./input_email')); 6 | 7 | ghost.helpers.register('subscribe_form', function labsEnabledHelper() { 8 | let self = this, args = arguments; 9 | 10 | return labs.enabledHelper({ 11 | flagKey: 'subscribers', 12 | flagName: 'Subscribers', 13 | helperName: 'subscribe_form', 14 | helpUrl: 'https://docs.ghost.org/faq/enable-subscribers-feature/' 15 | }, () => { 16 | return require('./subscribe_form').apply(self, args); 17 | }); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /core/server/data/meta/context_object.js: -------------------------------------------------------------------------------- 1 | var settingsCache = require('../../services/settings/cache'), 2 | _ = require('lodash'); 3 | 4 | function getContextObject(data, context) { 5 | /** 6 | * If the data object does not contain the requested context, we return the fallback object. 7 | */ 8 | var blog = { 9 | cover_image: settingsCache.get('cover_image'), 10 | twitter: settingsCache.get('twitter'), 11 | facebook: settingsCache.get('facebook') 12 | }, 13 | contextObject; 14 | 15 | context = _.includes(context, 'page') || _.includes(context, 'amp') ? 'post' : context; 16 | contextObject = data[context] || blog; 17 | return contextObject; 18 | } 19 | 20 | module.exports = getContextObject; 21 | -------------------------------------------------------------------------------- /core/test/unit/helpers/lang_spec.js: -------------------------------------------------------------------------------- 1 | const should = require('should'), 2 | settingsCache = require('../../../server/services/settings/cache'), 3 | helpers = require('../../../server/helpers'), 4 | proxy = require('../../../server/helpers/proxy'); 5 | 6 | describe('{{lang}} helper', function () { 7 | beforeEach(function () { 8 | settingsCache.set('default_locale', {value: 'en'}); 9 | }); 10 | 11 | afterEach(function () { 12 | settingsCache.shutdown(); 13 | }); 14 | 15 | it('returns correct language tag', function () { 16 | let expected = proxy.i18n.locale(), 17 | rendered = helpers.lang.call(); 18 | 19 | should.exist(rendered); 20 | rendered.string.should.equal(expected); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/all.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:all'); 2 | const _ = require('lodash'); 3 | 4 | const removeXBY = (object) => { 5 | _.each(object, (value, key) => { 6 | // CASE: go deeper 7 | if (_.isObject(value) || _.isArray(value)) { 8 | removeXBY(value); 9 | } else if (['updated_by', 'created_by', 'published_by'].includes(key)) { 10 | delete object[key]; 11 | } 12 | }); 13 | 14 | return object; 15 | }; 16 | 17 | module.exports = { 18 | after(apiConfig, frame) { 19 | debug('all after'); 20 | 21 | if (frame.response) { 22 | frame.response = removeXBY(frame.response); 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /core/server/lib/security/identifier.js: -------------------------------------------------------------------------------- 1 | let _private = {}; 2 | 3 | // @TODO: replace with crypto.randomBytes 4 | _private.getRandomInt = function (min, max) { 5 | return Math.floor(Math.random() * (max - min + 1)) + min; 6 | }; 7 | 8 | /** 9 | * Return a unique identifier with the given `len`. 10 | * 11 | * @param {Number} maxLength 12 | * @return {String} 13 | * @api private 14 | */ 15 | module.exports.uid = function uid(maxLength) { 16 | var buf = [], 17 | chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 18 | charLength = chars.length, 19 | i; 20 | 21 | for (i = 0; i < maxLength; i = i + 1) { 22 | buf.push(chars[_private.getRandomInt(0, charLength - 1)]); 23 | } 24 | 25 | return buf.join(''); 26 | }; 27 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/input/db.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:db'); 3 | const optionsUtil = require('../../../../shared/utils/options'); 4 | 5 | const INTERNAL_OPTIONS = ['transacting', 'forUpdate']; 6 | 7 | module.exports = { 8 | all(apiConfig, frame) { 9 | debug('serialize all'); 10 | 11 | if (frame.options.include) { 12 | frame.options.include = optionsUtil.trimAndLowerCase(frame.options.include); 13 | } 14 | 15 | if (!frame.options.context.internal) { 16 | debug('omit internal options'); 17 | frame.options = _.omit(frame.options, INTERNAL_OPTIONS); 18 | } 19 | 20 | debug(frame.options); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/notifications.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:notifications'); 2 | 3 | module.exports = { 4 | all(response, apiConfig, frame) { 5 | if (!response) { 6 | return; 7 | } 8 | 9 | if (!response || !response.length) { 10 | frame.response = { 11 | notifications: [] 12 | }; 13 | 14 | return; 15 | } 16 | 17 | response.forEach((notification) => { 18 | delete notification.seen; 19 | delete notification.addedAt; 20 | }); 21 | 22 | frame.response = { 23 | notifications: response 24 | }; 25 | 26 | debug(frame.response); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /core/server/data/meta/blog_logo.js: -------------------------------------------------------------------------------- 1 | var urlService = require('../../services/url'), 2 | settingsCache = require('../../services/settings/cache'), 3 | imageLib = require('../../lib/image'); 4 | 5 | function getBlogLogo() { 6 | var logo = {}; 7 | 8 | if (settingsCache.get('logo')) { 9 | logo.url = urlService.utils.urlFor('image', {image: settingsCache.get('logo')}, true); 10 | } else { 11 | // CASE: no publication logo is updated. We can try to use either an uploaded publication icon 12 | // or use the default one to make 13 | // Google happy with it. See https://github.com/TryGhost/Ghost/issues/7558 14 | logo.url = imageLib.blogIcon.getIconUrl(true); 15 | } 16 | 17 | return logo; 18 | } 19 | 20 | module.exports = getBlogLogo; 21 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/input/schemas/images.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "$id": "images", 5 | "title": "images", 6 | "description": "Base images definitions", 7 | "definitions": { 8 | "image": { 9 | "type": "object", 10 | "additionalProperties": false, 11 | "properties": { 12 | "purpose": { 13 | "type": "string", 14 | "enum": ["image", "profile_image", "icon"], 15 | "default": "image" 16 | }, 17 | "ref": { 18 | "type": ["string", "null"], 19 | "maxLength": 2000 20 | } 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/test/unit/web/api/v2/content/middleware_spec.js: -------------------------------------------------------------------------------- 1 | const should = require('should'); 2 | const middleware = require('../../../../../../server/web/api/v2/content/middleware'); 3 | 4 | describe('Content Api v2 middleware', function () { 5 | it('exports an authenticatePublic middleware', function () { 6 | should.exist(middleware.authenticatePublic); 7 | }); 8 | 9 | describe('authenticatePublic', function () { 10 | it('uses brute content api middleware as the first middleware in the chain', function () { 11 | const firstMiddleware = middleware.authenticatePublic[0]; 12 | const brute = require('../../../../../../server/web/shared/middlewares/brute'); 13 | 14 | should.equal(firstMiddleware, brute.contentApiKey); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/pages.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:pages'); 2 | const mapper = require('./utils/mapper'); 3 | 4 | module.exports = { 5 | all(models, apiConfig, frame) { 6 | debug('all'); 7 | 8 | // CASE: e.g. destroy returns null 9 | if (!models) { 10 | return; 11 | } 12 | 13 | if (models.meta) { 14 | frame.response = { 15 | pages: models.data.map(model => mapper.mapPost(model, frame)), 16 | meta: models.meta 17 | }; 18 | 19 | return; 20 | } 21 | 22 | frame.response = { 23 | pages: [mapper.mapPost(models, frame)] 24 | }; 25 | 26 | debug(frame.response); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /core/server/data/meta/og_image.js: -------------------------------------------------------------------------------- 1 | var urlService = require('../../services/url'), 2 | getContextObject = require('./context_object.js'), 3 | _ = require('lodash'); 4 | 5 | function getOgImage(data) { 6 | var context = data.context ? data.context : null, 7 | contextObject = getContextObject(data, context); 8 | 9 | if (_.includes(context, 'post') || _.includes(context, 'page') || _.includes(context, 'amp')) { 10 | if (contextObject.og_image) { 11 | return urlService.utils.urlFor('image', {image: contextObject.og_image}, true); 12 | } else if (contextObject.feature_image) { 13 | return urlService.utils.urlFor('image', {image: contextObject.feature_image}, true); 14 | } 15 | } 16 | 17 | return null; 18 | } 19 | 20 | module.exports = getOgImage; 21 | -------------------------------------------------------------------------------- /core/server/services/permissions/index.js: -------------------------------------------------------------------------------- 1 | // canThis(someUser).edit.posts([id]|[[ids]]) 2 | // canThis(someUser).edit.post(somePost|somePostId) 3 | 4 | var models = require('../../models'), 5 | actionsMap = require('./actions-map-cache'), 6 | init; 7 | 8 | init = function init(options) { 9 | options = options || {}; 10 | 11 | // Load all the permissions 12 | return models.Permission.findAll(options) 13 | .then(function (permissionsCollection) { 14 | return actionsMap.init(permissionsCollection); 15 | }); 16 | }; 17 | 18 | module.exports = { 19 | init: init, 20 | canThis: require('./can-this'), 21 | // @TODO: Make it so that we don't need to export these 22 | parseContext: require('./parse-context'), 23 | applyPublicRules: require('./public') 24 | }; 25 | -------------------------------------------------------------------------------- /core/server/web/api/v2/content/middleware.js: -------------------------------------------------------------------------------- 1 | const cors = require('cors'); 2 | const auth = require('../../../../services/auth'); 3 | const shared = require('../../../shared'); 4 | 5 | /** 6 | * Auth Middleware Packages 7 | * 8 | * IMPORTANT 9 | * - cors middleware MUST happen before pretty urls, because otherwise cors header can get lost on redirect 10 | * - url redirects MUST happen after cors, otherwise cors header can get lost on redirect 11 | */ 12 | 13 | /** 14 | * Authentication for public endpoints 15 | */ 16 | module.exports.authenticatePublic = [ 17 | shared.middlewares.brute.contentApiKey, 18 | auth.authenticate.authenticateContentApi, 19 | auth.authorize.authorizeContentApi, 20 | cors(), 21 | shared.middlewares.urlRedirects.adminRedirect, 22 | shared.middlewares.prettyUrls 23 | ]; 24 | -------------------------------------------------------------------------------- /core/server/data/meta/cover_image.js: -------------------------------------------------------------------------------- 1 | var urlService = require('../../services/url'), 2 | getContextObject = require('./context_object.js'), 3 | _ = require('lodash'); 4 | 5 | function getCoverImage(data) { 6 | var context = data.context ? data.context : null, 7 | contextObject = getContextObject(data, context); 8 | 9 | if (_.includes(context, 'home') || _.includes(context, 'author')) { 10 | if (contextObject.cover_image) { 11 | return urlService.utils.urlFor('image', {image: contextObject.cover_image}, true); 12 | } 13 | } else { 14 | if (contextObject.feature_image) { 15 | return urlService.utils.urlFor('image', {image: contextObject.feature_image}, true); 16 | } 17 | } 18 | return null; 19 | } 20 | 21 | module.exports = getCoverImage; 22 | -------------------------------------------------------------------------------- /core/server/data/meta/twitter_image.js: -------------------------------------------------------------------------------- 1 | var urlService = require('../../services/url'), 2 | getContextObject = require('./context_object.js'), 3 | _ = require('lodash'); 4 | 5 | function getTwitterImage(data) { 6 | var context = data.context ? data.context : null, 7 | contextObject = getContextObject(data, context); 8 | 9 | if (_.includes(context, 'post') || _.includes(context, 'page') || _.includes(context, 'amp')) { 10 | if (contextObject.twitter_image) { 11 | return urlService.utils.urlFor('image', {image: contextObject.twitter_image}, true); 12 | } else if (contextObject.feature_image) { 13 | return urlService.utils.urlFor('image', {image: contextObject.feature_image}, true); 14 | } 15 | } 16 | 17 | return null; 18 | } 19 | 20 | module.exports = getTwitterImage; 21 | -------------------------------------------------------------------------------- /core/server/lib/promise/sequence.js: -------------------------------------------------------------------------------- 1 | const Promise = require('bluebird'); 2 | 3 | /** 4 | * expects an array of functions returning a promise 5 | */ 6 | function sequence(tasks /* Any Arguments */) { 7 | const args = Array.prototype.slice.call(arguments, 1); 8 | 9 | return Promise.reduce(tasks, function (results, task) { 10 | const response = task.apply(this, args); 11 | 12 | if (response && response.then) { 13 | return response.then(function (result) { 14 | results.push(result); 15 | return results; 16 | }); 17 | } else { 18 | return Promise.resolve().then(() => { 19 | results.push(response); 20 | return results; 21 | }); 22 | } 23 | }, []); 24 | } 25 | 26 | module.exports = sequence; 27 | -------------------------------------------------------------------------------- /core/server/services/routing/helpers/renderer.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('services:routing:helpers:renderer'), 2 | setContext = require('./context'), 3 | templates = require('./templates'); 4 | 5 | module.exports = function renderer(req, res, data) { 6 | // Context 7 | setContext(req, res, data); 8 | 9 | // Template 10 | templates.setTemplate(req, res, data); 11 | 12 | // Render Call 13 | debug('Rendering template: ' + res._template + ' for: ' + req.originalUrl); 14 | debug('res.locals', res.locals); 15 | 16 | if (res.routerOptions && res.routerOptions.contentType) { 17 | if (res.routerOptions.templates.indexOf(res._template) !== -1) { 18 | res.type(res.routerOptions.contentType); 19 | } 20 | } 21 | 22 | res.render(res._template, data); 23 | }; 24 | -------------------------------------------------------------------------------- /core/server/models/plugins/transaction-events.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a feature request in knex for 1.0. 3 | * https://github.com/tgriesser/knex/issues/1641 4 | */ 5 | module.exports = function (bookshelf) { 6 | const orig1 = bookshelf.transaction; 7 | 8 | bookshelf.transaction = function (cb) { 9 | return orig1.bind(bookshelf)(function (t) { 10 | const orig2 = t.commit; 11 | const orig3 = t.rollback; 12 | 13 | t.commit = function () { 14 | t.emit('committed', true); 15 | return orig2.apply(t, arguments); 16 | }; 17 | 18 | t.rollback = function () { 19 | t.emit('committed', false); 20 | return orig3.apply(t, arguments); 21 | }; 22 | 23 | return cb(t); 24 | }); 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/output/posts.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:posts'); 2 | const mapper = require('./utils/mapper'); 3 | 4 | module.exports = { 5 | all(models, apiConfig, frame) { 6 | debug('all'); 7 | 8 | // CASE: e.g. destroy returns null 9 | if (!models) { 10 | return; 11 | } 12 | 13 | if (models.meta) { 14 | frame.response = { 15 | posts: models.data.map(model => mapper.mapPost(model, frame)), 16 | meta: models.meta 17 | }; 18 | 19 | debug(frame.response); 20 | return; 21 | } 22 | 23 | frame.response = { 24 | posts: [mapper.mapPost(models, frame)] 25 | }; 26 | 27 | debug(frame.response); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /core/server/lib/request.js: -------------------------------------------------------------------------------- 1 | var got = require('got'), 2 | _ = require('lodash'), 3 | validator = require('../data/validation').validator, 4 | common = require('./common'), 5 | ghostVersion = require('./ghost-version'); 6 | 7 | var defaultOptions = { 8 | headers: { 9 | 'user-agent': 'Ghost/' + ghostVersion.original + ' (https://github.com/TryGhost/Ghost)' 10 | } 11 | }; 12 | 13 | module.exports = function request(url, options) { 14 | if (_.isEmpty(url) || !validator.isURL(url)) { 15 | return Promise.reject(new common.errors.InternalServerError({ 16 | message: 'URL empty or invalid.', 17 | code: 'URL_MISSING_INVALID', 18 | context: url 19 | })); 20 | } 21 | 22 | var mergedOptions = _.merge({}, defaultOptions, options); 23 | 24 | return got(url, mergedOptions); 25 | }; 26 | -------------------------------------------------------------------------------- /core/server/web/shared/middlewares/ghost-locals.js: -------------------------------------------------------------------------------- 1 | const ghostVersion = require('../../../lib/ghost-version'); 2 | const themeService = require('../../../services/themes'); 3 | 4 | // ### GhostLocals Middleware 5 | // Expose the standard locals that every request will need to have available 6 | module.exports = function ghostLocals(req, res, next) { 7 | // Make sure we have a locals value. 8 | res.locals = res.locals || {}; 9 | // The current Ghost version 10 | res.locals.version = ghostVersion.full; 11 | // The current Ghost version, but only major.minor 12 | res.locals.safeVersion = ghostVersion.safe; 13 | // relative path from the URL 14 | res.locals.relativeUrl = req.path; 15 | // make ghost api version available for the theme + routing 16 | res.locals.apiVersion = themeService.getApiVersion(); 17 | 18 | next(); 19 | }; 20 | -------------------------------------------------------------------------------- /core/server/lib/fs/package-json/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * # Package Utils 3 | * 4 | * Ghost has / is in the process of gaining support for several different types of sub-packages: 5 | * - Themes: have always been packages, but we're going to lean more heavily on npm & package.json in future 6 | * - Adapters: an early version of apps, replace fundamental pieces like storage, will become npm modules 7 | * - Apps: plugins that can be installed whilst Ghost is running & modify behaviour 8 | * - More? 9 | * 10 | * These utils facilitate loading, reading, managing etc, packages from the file system. 11 | */ 12 | 13 | module.exports = { 14 | get read() { 15 | return require('./read'); 16 | }, 17 | 18 | get parse() { 19 | return require('./parse'); 20 | }, 21 | 22 | get filter() { 23 | return require('./filter'); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /core/server/web/admin/controller.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('web:admin:controller'); 2 | const path = require('path'); 3 | const config = require('../../config'); 4 | const updateCheck = require('../../update-check'); 5 | const common = require('../../lib/common'); 6 | 7 | // Route: index 8 | // Path: /ghost/ 9 | // Method: GET 10 | module.exports = function adminController(req, res) { 11 | debug('index called'); 12 | 13 | // run in background, don't block the admin rendering 14 | updateCheck() 15 | .catch((err) => { 16 | common.logging.error(err); 17 | }); 18 | 19 | const defaultTemplate = config.get('env') === 'production' ? 'default-prod.html' : 'default.html'; 20 | const templatePath = path.resolve(config.get('paths').adminViews, defaultTemplate); 21 | 22 | res.sendFile(templatePath); 23 | }; 24 | -------------------------------------------------------------------------------- /core/server/lib/fs/zip-folder.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | 3 | module.exports = function zipFolder(folderToZip, destination, callback) { 4 | var archiver = require('archiver'), 5 | output = fs.createWriteStream(destination), 6 | archive = archiver.create('zip', {}); 7 | 8 | // If folder to zip is a symlink, we want to get the target 9 | // of the link and zip that instead of zipping the symlink 10 | if (fs.lstatSync(folderToZip).isSymbolicLink()) { 11 | folderToZip = fs.realpathSync(folderToZip); 12 | } 13 | 14 | output.on('close', function () { 15 | callback(null, archive.pointer()); 16 | }); 17 | 18 | archive.on('error', function (err) { 19 | callback(err, null); 20 | }); 21 | 22 | archive.directory(folderToZip, '/'); 23 | archive.pipe(output); 24 | archive.finalize(); 25 | }; 26 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/pages/RequestPasswordResetPage.js: -------------------------------------------------------------------------------- 1 | import FormHeader from '../components/FormHeader'; 2 | import EmailInput from '../components/EmailInput'; 3 | import FormSubmit from '../components/FormSubmit'; 4 | 5 | import Form from '../components/Form'; 6 | 7 | export default ({ error, handleClose, handleSubmit }) => ( 8 |
    9 | 10 |
    11 | 12 | 13 | 16 |
    17 |
    18 | ); 19 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/validators/utils/strip-keyword.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 'strip' keyword is introduced into schemas for following behavior: 3 | * properties that are 'known' but should not be present in the model 4 | * should be stripped from data and not throw validation errors. 5 | * 6 | * An example of such property is `tag.parent` which we want to ignore 7 | * but not necessarily throw a validation error as it was present in 8 | * responses in previous versions of API 9 | */ 10 | module.exports = function defFunc(ajv) { 11 | defFunc.definition = { 12 | errors: false, 13 | modifying: true, 14 | valid: true, 15 | validate: function (schema, data, parentSchema, dataPath, parentData, propName) { 16 | delete parentData[propName]; 17 | } 18 | }; 19 | 20 | ajv.addKeyword('strip', defFunc.definition); 21 | return ajv; 22 | }; 23 | -------------------------------------------------------------------------------- /core/server/web/shared/middlewares/admin-redirects.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const urlService = require('../../../services/url'); 3 | 4 | const adminRedirect = (path) => { 5 | return function doRedirect(req, res) { 6 | return urlService.utils.redirectToAdmin(301, res, path); 7 | }; 8 | }; 9 | 10 | module.exports = function adminRedirects() { 11 | const router = express.Router(); 12 | // Admin redirects - register redirect as route 13 | // TODO: this should be middleware! 14 | router.get(/^\/(logout|signout)\/$/, adminRedirect('#/signout/')); 15 | router.get(/^\/signup\/$/, adminRedirect('#/signup/')); 16 | // redirect to /ghost and let that do the authentication to prevent redirects to /ghost//admin etc. 17 | router.get(/^\/((ghost-admin|admin|dashboard|signin|login)\/?)$/, adminRedirect('/')); 18 | 19 | return router; 20 | }; 21 | -------------------------------------------------------------------------------- /core/server/apps/amp/index.js: -------------------------------------------------------------------------------- 1 | const router = require('./lib/router'), 2 | registerHelpers = require('./lib/helpers'), 3 | urlService = require('../../services/url'), 4 | 5 | // Dirty requires 6 | settingsCache = require('../../services/settings/cache'); 7 | 8 | function ampRouter(req, res) { 9 | if (settingsCache.get('amp') === true) { 10 | return router.apply(this, arguments); 11 | } else { 12 | // routeKeywords.amp: 'amp' 13 | let redirectUrl = req.originalUrl.replace(/amp\/$/, ''); 14 | urlService.utils.redirect301(res, redirectUrl); 15 | } 16 | } 17 | 18 | module.exports = { 19 | activate: function activate(ghost) { 20 | // routeKeywords.amp: 'amp' 21 | let ampRoute = '*/amp/'; 22 | 23 | ghost.routeService.registerRouter(ampRoute, ampRouter); 24 | 25 | registerHelpers(ghost); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper/partials/byline-single.hbs: -------------------------------------------------------------------------------- 1 | {{!-- Everything inside the #author tags pulls data from the author --}} 2 | {{#primary_author}} 3 | 4 |
    5 | {{#if profile_image}} 6 | {{name}} 7 | {{else}} 8 | {{> "icons/avatar"}} 9 | {{/if}} 10 |
    11 |

    {{name}}

    12 | {{#if bio}} 13 |

    {{bio}}

    14 | {{else}} 15 |

    Read more posts by this author.

    16 | {{/if}} 17 |
    18 |
    19 |
    20 | Read More 21 |
    22 | 23 | {{/primary_author}} 24 | -------------------------------------------------------------------------------- /core/server/data/migrations/hooks/init/shutdown.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'), 2 | database = require('../../../db'); 3 | 4 | module.exports = function shutdown(options = {}) { 5 | if (options.executedFromShell === true) { 6 | // running knex-migrator migrate --init in the shell does two different migration calls within a single process 7 | // we have to ensure that we clear the Ghost cache afterwards, otherwise we operate on a destroyed connection 8 | _.each(require.cache, function (val, key) { 9 | if (key.match(/core\/server/)) { 10 | delete require.cache[key]; 11 | } 12 | }); 13 | 14 | /** 15 | * We have to close Ghost's db connection if knex-migrator was used in the shell. 16 | * Otherwise the process doesn't exit. 17 | */ 18 | return database.knex.destroy(); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /core/server/api/v2/members.js: -------------------------------------------------------------------------------- 1 | const memberUserObject = require('../../services/members').api.memberUserObject; 2 | 3 | const members = { 4 | docName: 'members', 5 | browse: { 6 | options: [ 7 | 'limit', 8 | 'fields', 9 | 'filter', 10 | 'order', 11 | 'debug', 12 | 'page' 13 | ], 14 | permissions: true, 15 | validation: {}, 16 | query(frame) { 17 | return memberUserObject.list(frame.options); 18 | } 19 | }, 20 | 21 | read: { 22 | headers: {}, 23 | data: [ 24 | 'id', 25 | 'email' 26 | ], 27 | validation: {}, 28 | permissions: true, 29 | query(frame) { 30 | return memberUserObject.get(frame.data, frame.options); 31 | } 32 | } 33 | }; 34 | 35 | module.exports = members; 36 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/assets/images/icon-name.svg: -------------------------------------------------------------------------------- 1 | icon-name -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/components/CheckoutForm.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { CardElement } from 'react-stripe-elements'; 3 | 4 | class CheckoutForm extends Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | 9 | render() { 10 | let style = { 11 | base: { 12 | '::placeholder': { 13 | color: '#8795A1', 14 | fontSize: '15px' 15 | } 16 | }, 17 | invalid: { 18 | '::placeholder': { 19 | color: 'rgba(240, 82, 48, 0.75)' 20 | } 21 | } 22 | }; 23 | return ( 24 |
    25 | 26 |
    27 | ); 28 | } 29 | } 30 | 31 | export default CheckoutForm; 32 | -------------------------------------------------------------------------------- /core/server/lib/ghost-version.js: -------------------------------------------------------------------------------- 1 | const semver = require('semver'), 2 | packageInfo = require('../../../package.json'), 3 | version = packageInfo.version, 4 | plainVersion = version.match(/^(\d+\.)?(\d+\.)?(\d+)/)[0]; 5 | 6 | let _private = {}; 7 | 8 | _private.compose = function compose(type) { 9 | switch (type) { 10 | case 'pre': 11 | return plainVersion + '-' + semver.prerelease(version)[0] + (semver.prerelease(version)[1] ? '.' + semver.prerelease(version)[1] : ''); 12 | default: 13 | return version; 14 | } 15 | }; 16 | 17 | // major.minor 18 | module.exports.safe = version.match(/^(\d+\.)?(\d+)/)[0]; 19 | 20 | // major.minor.patch-{prerelease} 21 | module.exports.full = semver.prerelease(version) ? _private.compose('pre') : plainVersion; 22 | 23 | // original string in package.json (can contain pre-release and build suffix) 24 | module.exports.original = version; 25 | 26 | -------------------------------------------------------------------------------- /core/server/services/themes/engine.js: -------------------------------------------------------------------------------- 1 | var hbs = require('express-hbs'), 2 | config = require('../../config'), 3 | instance = hbs.create(); 4 | 5 | // @TODO think about a config option for this e.g. theme.devmode? 6 | if (config.get('env') !== 'production') { 7 | instance.handlebars.logger.level = 0; 8 | } 9 | 10 | instance.escapeExpression = instance.handlebars.Utils.escapeExpression; 11 | 12 | instance.configure = function configure(partialsPath) { 13 | var hbsOptions = { 14 | partialsDir: [config.get('paths').helperTemplates], 15 | onCompile: function onCompile(exhbs, source) { 16 | return exhbs.handlebars.compile(source, {preventIndent: true}); 17 | } 18 | }; 19 | 20 | if (partialsPath) { 21 | hbsOptions.partialsDir.push(partialsPath); 22 | } 23 | 24 | return instance.express4(hbsOptions); 25 | }; 26 | 27 | module.exports = instance; 28 | -------------------------------------------------------------------------------- /core/server/web/shared/middlewares/validation/profile-image.js: -------------------------------------------------------------------------------- 1 | const common = require('../../../../lib/common'); 2 | const imageLib = require('../../../../lib/image'); 3 | 4 | module.exports = function profileImage(req, res, next) { 5 | // we checked for a valid image file, now we need to do validations for profile image 6 | imageLib.imageSize.getImageSizeFromPath(req.file.path).then((response) => { 7 | // save the image dimensions in new property for file 8 | req.file.dimensions = response; 9 | 10 | // CASE: file needs to be a square 11 | if (req.file.dimensions.width !== req.file.dimensions.height) { 12 | return next(new common.errors.ValidationError({ 13 | message: common.i18n.t('errors.api.images.isNotSquare') 14 | })); 15 | } 16 | 17 | next(); 18 | }).catch((err) => { 19 | next(err); 20 | }); 21 | }; 22 | -------------------------------------------------------------------------------- /core/test/unit/models/permission_spec.js: -------------------------------------------------------------------------------- 1 | const should = require('should'), 2 | sinon = require('sinon'), 3 | models = require('../../../server/models'), 4 | testUtils = require('../../utils'), 5 | configUtils = require('../../utils/configUtils'); 6 | 7 | describe('Unit: models/permission', function () { 8 | before(function () { 9 | models.init(); 10 | }); 11 | 12 | after(function () { 13 | sinon.restore(); 14 | configUtils.restore(); 15 | }); 16 | 17 | describe('add', function () { 18 | it('[error] validation', function () { 19 | return models.Permission.add({}) 20 | .then(function () { 21 | 'Should fail'.should.be.true(); 22 | }) 23 | .catch(function (err) { 24 | err.length.should.eql(3); 25 | }); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /core/index.js: -------------------------------------------------------------------------------- 1 | // ## Server Loader 2 | // Passes options through the boot process to get a server instance back 3 | const server = require('./server'); 4 | const common = require('./server/lib/common'); 5 | const GhostServer = require('./server/ghost-server'); 6 | 7 | // Set the default environment to be `development` 8 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 9 | 10 | function makeGhost(options) { 11 | options = options || {}; 12 | 13 | return server(options) 14 | .catch((err) => { 15 | if (!common.errors.utils.isIgnitionError(err)) { 16 | err = new common.errors.GhostError({message: err.message, err: err}); 17 | } 18 | 19 | return GhostServer.announceServerStopped(err) 20 | .finally(() => { 21 | throw err; 22 | }); 23 | }); 24 | } 25 | 26 | module.exports = makeGhost; 27 | -------------------------------------------------------------------------------- /core/test/utils/fixtures/themes/casper/partials/icons/twitter.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | !** 2 | .build 3 | .dist 4 | .tmp 5 | docs/** 6 | _site/** 7 | content/adapters/** 8 | !content/adapters/README.md 9 | content/apps/** 10 | !content/apps/README.md 11 | content/data/** 12 | !content/data/README.md 13 | content/images/** 14 | !content/images/README.md 15 | content/logs/** 16 | !content/logs/README.md 17 | content/themes/** 18 | !content/themes/casper/** 19 | node_modules/** 20 | core/server/lib/members/static/auth/node_modules/** 21 | **/*.db* 22 | *.db* 23 | .af* 24 | .git* 25 | .groc* 26 | .jshintrc 27 | .jscsrc 28 | *.iml 29 | core/built/**/*.map 30 | core/built/**/test-* 31 | core/built/**/tests-* 32 | core/client/** 33 | core/test/** 34 | CONTRIBUTING.md 35 | SECURITY.md 36 | .travis.yml 37 | *.html 38 | !core/server/web/admin/views/** 39 | !core/server/services/mail/templates/** 40 | bower_components/** 41 | .editorconfig 42 | gulpfile.js 43 | !content/themes/casper/gulpfile.js 44 | package-lock.json 45 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/input/users.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:users'); 2 | const url = require('./utils/url'); 3 | 4 | module.exports = { 5 | read(apiConfig, frame) { 6 | debug('read'); 7 | 8 | if (frame.data.id === 'me' && frame.options.context && frame.options.context.user) { 9 | frame.data.id = frame.options.context.user; 10 | } 11 | }, 12 | 13 | edit(apiConfig, frame) { 14 | debug('edit'); 15 | 16 | if (frame.options.id === 'me' && frame.options.context && frame.options.context.user) { 17 | frame.options.id = frame.options.context.user; 18 | } 19 | 20 | if (frame.data.users[0].password) { 21 | delete frame.data.users[0].password; 22 | } 23 | 24 | frame.data.users[0] = url.forUser(Object.assign({}, frame.data.users[0])); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/pages/ResetPasswordPage.js: -------------------------------------------------------------------------------- 1 | import FormHeader from '../components/FormHeader'; 2 | import PasswordInput from '../components/PasswordInput'; 3 | import FormSubmit from '../components/FormSubmit'; 4 | import Form from '../components/Form'; 5 | 6 | const getTokenData = frameLocation => { 7 | const params = new URLSearchParams(frameLocation.query); 8 | const token = params.get('token') || ''; 9 | return { token }; 10 | 11 | }; 12 | 13 | export default ({ error, frameLocation, handleSubmit }) => ( 14 |
    15 | 16 |
    17 | 18 | 19 | 20 |
    21 | ); 22 | -------------------------------------------------------------------------------- /core/test/unit/lib/mobiledoc/converters/converters_spec.js: -------------------------------------------------------------------------------- 1 | const should = require('should'); 2 | const converters = require('../../../../../server/lib/mobiledoc/converters'); 3 | 4 | describe('Unit: lib/mobiledoc/converters', function () { 5 | describe('htmlToMobiledocConverter should be unsupported in node v6', function () { 6 | before(function () { 7 | if (!process.version.startsWith('v6.')) { 8 | this.skip(); 9 | } 10 | }); 11 | 12 | it('should throw when running on node v6', function () { 13 | try { 14 | const thrower = converters.htmlToMobiledocConverter(); 15 | thrower(); 16 | throw new Error('should not execute'); 17 | } catch (err) { 18 | err.message.should.equal('Unable to convert from source HTML to Mobiledoc'); 19 | } 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /core/server/services/routing/helpers/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | get entryLookup() { 3 | return require('./entry-lookup'); 4 | }, 5 | 6 | get fetchData() { 7 | return require('./fetch-data'); 8 | }, 9 | 10 | get renderEntries() { 11 | return require('./render-entries'); 12 | }, 13 | 14 | get formatResponse() { 15 | return require('./format-response'); 16 | }, 17 | 18 | get renderEntry() { 19 | return require('./render-entry'); 20 | }, 21 | 22 | get renderer() { 23 | return require('./renderer'); 24 | }, 25 | 26 | get templates() { 27 | return require('./templates'); 28 | }, 29 | 30 | get secure() { 31 | return require('./secure'); 32 | }, 33 | 34 | get handleError() { 35 | return require('./error'); 36 | }, 37 | 38 | get context() { 39 | return require('./context'); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /core/test/unit/lib/promise/sequence_spec.js: -------------------------------------------------------------------------------- 1 | const should = require('should'); 2 | const sinon = require('sinon'); 3 | const Promise = require('bluebird'); 4 | const sequence = require('../../../../server/lib/promise/sequence'); 5 | 6 | describe('Unit: lib/promise/sequence', function () { 7 | afterEach(function () { 8 | sinon.restore(); 9 | }); 10 | 11 | it('mixed tasks: promise and none promise', function () { 12 | const tasks = [ 13 | function a() { 14 | return Promise.resolve('hello'); 15 | }, 16 | function b() { 17 | return 'from'; 18 | }, 19 | function c() { 20 | return Promise.resolve('chio'); 21 | }, 22 | ]; 23 | return sequence(tasks) 24 | .then(function (result) { 25 | result.should.eql(['hello','from', 'chio']); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /core/server/lib/mobiledoc/cards/embed.js: -------------------------------------------------------------------------------- 1 | const createCard = require('../create-card'); 2 | 3 | module.exports = createCard({ 4 | name: 'embed', 5 | type: 'dom', 6 | render(opts) { 7 | if (!opts.payload.html) { 8 | return ''; 9 | } 10 | 11 | let {payload, env: {dom}} = opts; 12 | 13 | let figure = dom.createElement('figure'); 14 | figure.setAttribute('class', 'kg-card kg-embed-card'); 15 | 16 | let html = dom.createRawHTMLSection(payload.html); 17 | figure.appendChild(html); 18 | 19 | if (payload.caption) { 20 | let figcaption = dom.createElement('figcaption'); 21 | figcaption.appendChild(dom.createRawHTMLSection(payload.caption)); 22 | figure.appendChild(figcaption); 23 | figure.setAttribute('class', `${figure.getAttribute('class')} kg-card-hascaption`); 24 | } 25 | 26 | return figure; 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /core/server/api/v2/utils/serializers/input/tags.js: -------------------------------------------------------------------------------- 1 | const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:tags'); 2 | const url = require('./utils/url'); 3 | const utils = require('../../index'); 4 | 5 | function setDefaultOrder(frame) { 6 | if (!frame.options.order) { 7 | frame.options.order = 'name asc'; 8 | } 9 | } 10 | 11 | module.exports = { 12 | browse(apiConfig, frame) { 13 | debug('browse'); 14 | 15 | if (utils.isContentAPI(frame)) { 16 | setDefaultOrder(frame); 17 | } 18 | }, 19 | 20 | read() { 21 | debug('read'); 22 | 23 | this.browse(...arguments); 24 | }, 25 | 26 | add(apiConfig, frame) { 27 | debug('add'); 28 | frame.data.tags[0] = url.forTag(Object.assign({}, frame.data.tags[0])); 29 | }, 30 | 31 | edit(apiConfig, frame) { 32 | debug('edit'); 33 | this.add(apiConfig, frame); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/components/FormInput.js: -------------------------------------------------------------------------------- 1 | export default ({type, name, placeholder, value = '', error, onInput, required, className, children, icon}) => ( 2 |
    3 |
    7 | onInput(e, name) } 14 | required={ required } 15 | className={[ 16 | (value ? "gm-input-filled" : ""), 17 | (error ? "gm-error" : "") 18 | ].join(' ')} 19 | /> 20 | { icon } 21 | { children } 22 |
    23 |
    24 | ); 25 | -------------------------------------------------------------------------------- /core/server/lib/mobiledoc/create-card.js: -------------------------------------------------------------------------------- 1 | module.exports = function createCard(card) { 2 | const {name, type} = card; 3 | 4 | return { 5 | name, 6 | type, 7 | render({env, payload, options}) { 8 | const {dom} = env; 9 | const cleanName = name.replace(/^card-/, ''); 10 | 11 | const cardOutput = card.render({env, payload, options}); 12 | 13 | if (!cardOutput) { 14 | return cardOutput; 15 | } 16 | 17 | const beginComment = dom.createComment(`kg-card-begin: ${cleanName}`); 18 | const endComment = dom.createComment(`kg-card-end: ${cleanName}`); 19 | const fragment = dom.createDocumentFragment(); 20 | 21 | fragment.appendChild(beginComment); 22 | fragment.appendChild(cardOutput); 23 | fragment.appendChild(endComment); 24 | 25 | return fragment; 26 | } 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /core/server/models/accesstoken.js: -------------------------------------------------------------------------------- 1 | const ghostBookshelf = require('./base'), 2 | Basetoken = require('./base/token'); 3 | 4 | let Accesstoken, 5 | Accesstokens; 6 | 7 | Accesstoken = Basetoken.extend({ 8 | tableName: 'accesstokens', 9 | 10 | emitChange: function emitChange(event, options) { 11 | const eventToTrigger = 'token' + '.' + event; 12 | ghostBookshelf.Model.prototype.emitChange.bind(this)(this, eventToTrigger, options); 13 | }, 14 | 15 | onCreated: function onCreated(model, attrs, options) { 16 | ghostBookshelf.Model.prototype.onCreated.apply(this, arguments); 17 | model.emitChange('added', options); 18 | } 19 | }); 20 | 21 | Accesstokens = ghostBookshelf.Collection.extend({ 22 | model: Accesstoken 23 | }); 24 | 25 | module.exports = { 26 | Accesstoken: ghostBookshelf.model('Accesstoken', Accesstoken), 27 | Accesstokens: ghostBookshelf.collection('Accesstokens', Accesstokens) 28 | }; 29 | -------------------------------------------------------------------------------- /core/test/unit/helpers/post_class_spec.js: -------------------------------------------------------------------------------- 1 | var should = require('should'), 2 | 3 | // Stuff we are testing 4 | helpers = require('../../../server/helpers'); 5 | 6 | describe('{{post_class}} helper', function () { 7 | it('can render class string', function () { 8 | var rendered = helpers.post_class.call({}); 9 | 10 | should.exist(rendered); 11 | rendered.string.should.equal('post'); 12 | }); 13 | 14 | it('can render featured class', function () { 15 | var post = {featured: true}, 16 | rendered = helpers.post_class.call(post); 17 | 18 | should.exist(rendered); 19 | rendered.string.should.equal('post featured'); 20 | }); 21 | 22 | it('can render page class', function () { 23 | var post = {page: true}, 24 | rendered = helpers.post_class.call(post); 25 | 26 | should.exist(rendered); 27 | rendered.string.should.equal('post page'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /core/server/lib/members/util.js: -------------------------------------------------------------------------------- 1 | function getData(...props) { 2 | return function (req, res, next) { 3 | if (!req.body) { 4 | res.writeHead(400); 5 | return res.end(); 6 | } 7 | 8 | const data = props.concat('origin').reduce((data, prop) => { 9 | if (!data || !req.body[prop]) { 10 | return null; 11 | } 12 | return Object.assign(data, { 13 | [prop]: req.body[prop] 14 | }); 15 | }, {}); 16 | 17 | if (!data) { 18 | res.writeHead(400); 19 | return res.end(`Expected {${props.join(', ')}}`); 20 | } 21 | req.data = data || {}; 22 | next(); 23 | }; 24 | } 25 | 26 | function handleError(status, res) { 27 | return function () { 28 | res.writeHead(status); 29 | res.end(); 30 | }; 31 | } 32 | 33 | module.exports = { 34 | getData, 35 | handleError 36 | }; 37 | -------------------------------------------------------------------------------- /core/test/unit/data/meta/rss_url_spec.js: -------------------------------------------------------------------------------- 1 | const should = require('should'), 2 | sinon = require('sinon'), 3 | routing = require('../../../../server/services/routing'), 4 | getRssUrl = require('../../../../server/data/meta/rss_url'); 5 | 6 | describe('getRssUrl', function () { 7 | beforeEach(function () { 8 | sinon.stub(routing.registry, 'getRssUrl').returns('/rss/'); 9 | }); 10 | 11 | afterEach(function () { 12 | sinon.restore(); 13 | }); 14 | 15 | it('should return rss url', function () { 16 | const rssUrl = getRssUrl({ 17 | secure: false 18 | }); 19 | 20 | should.equal(rssUrl, '/rss/'); 21 | }); 22 | 23 | it('forwards absolute/secure flags', function () { 24 | const rssUrl = getRssUrl({ 25 | secure: false 26 | }, true); 27 | 28 | routing.registry.getRssUrl.calledWith({secure: false, absolute: true}).should.be.true(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /core/server/api/v2/config.js: -------------------------------------------------------------------------------- 1 | const {isPlainObject} = require('lodash'); 2 | const config = require('../../config'); 3 | const labs = require('../../services/labs'); 4 | const ghostVersion = require('../../lib/ghost-version'); 5 | 6 | module.exports = { 7 | docName: 'config', 8 | 9 | read: { 10 | permissions: false, 11 | query() { 12 | return { 13 | version: ghostVersion.full, 14 | environment: config.get('env'), 15 | database: config.get('database').client, 16 | mail: isPlainObject(config.get('mail')) ? config.get('mail').transport : '', 17 | useGravatar: !config.isPrivacyDisabled('useGravatar'), 18 | labs: labs.getAll(), 19 | clientExtensions: config.get('clientExtensions') || {}, 20 | enableDeveloperExperiments: config.get('enableDeveloperExperiments') || false 21 | }; 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/--anything-else.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4A1Anything else" 3 | about: "For help, support, features & ideas - please use https://forum.ghost.org \U0001F46B " 4 | 5 | --- 6 | 7 | --------------^ Click "Preview" for a nicer view! 8 | 9 | We use GitHub only for bug reports 🐛 10 | 11 | Anything else should be posted to https://forum.ghost.org 👫. 12 | 13 | 🚨For support, help & questions use https://forum.ghost.org/c/help 14 | 💡For feature requests & ideas you can post and vote on https://forum.ghost.org/c/Ideas 15 | 16 | Alternatively, check out these resources below. Thanks! 😁. 17 | 18 | - [Forum](https://forum.ghost.org/c/help) 19 | - [Theme Support](https://forum.ghost.org/c/themes) 20 | - [Theme Docs](http://themes.ghost.org/) 21 | - [API Docs](https://api.ghost.org/) 22 | - [Feature Requests / Ideas](https://forum.ghost.org/c/Ideas) 23 | - [Contributing Guide](https://docs.ghost.org/docs/contributing) 24 | - [Self-hoster Docs](https://docs.ghost.org/) 25 | -------------------------------------------------------------------------------- /core/server/data/schema/checks.js: -------------------------------------------------------------------------------- 1 | function isPost(jsonData) { 2 | return jsonData.hasOwnProperty('html') && 3 | jsonData.hasOwnProperty('title') && jsonData.hasOwnProperty('slug'); 4 | } 5 | 6 | function isTag(jsonData) { 7 | return jsonData.hasOwnProperty('name') && jsonData.hasOwnProperty('slug') && 8 | jsonData.hasOwnProperty('description') && jsonData.hasOwnProperty('feature_image'); 9 | } 10 | 11 | function isUser(jsonData) { 12 | return jsonData.hasOwnProperty('bio') && jsonData.hasOwnProperty('website') && 13 | jsonData.hasOwnProperty('profile_image') && jsonData.hasOwnProperty('location'); 14 | } 15 | 16 | function isNav(jsonData) { 17 | return jsonData.hasOwnProperty('label') && jsonData.hasOwnProperty('url') && 18 | jsonData.hasOwnProperty('slug') && jsonData.hasOwnProperty('current'); 19 | } 20 | 21 | module.exports = { 22 | isPost: isPost, 23 | isTag: isTag, 24 | isUser: isUser, 25 | isNav: isNav 26 | }; 27 | -------------------------------------------------------------------------------- /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 proxy = require('./proxy'), 5 | _ = require('lodash'), 6 | logging = proxy.logging, 7 | i18n = proxy.i18n; 8 | 9 | module.exports = function is(context, options) { 10 | options = options || {}; 11 | 12 | var currentContext = options.data.root.context; 13 | 14 | if (!_.isString(context)) { 15 | logging.warn(i18n.t('warnings.helpers.is.invalidAttribute')); 16 | return; 17 | } 18 | 19 | function evaluateContext(expr) { 20 | return expr.split(',').map(function (v) { 21 | return v.trim(); 22 | }).reduce(function (p, c) { 23 | return p || _.includes(currentContext, c); 24 | }, false); 25 | } 26 | 27 | if (evaluateContext(context)) { 28 | return options.fn(this); 29 | } 30 | return options.inverse(this); 31 | }; 32 | 33 | -------------------------------------------------------------------------------- /core/server/web/shared/middlewares/frontend-client.js: -------------------------------------------------------------------------------- 1 | const labs = require('../../../services/labs'); 2 | const common = require('../../../lib/common'); 3 | 4 | module.exports = function getFrontendClient(req, res, next) { 5 | if (labs.isSet('publicAPI') !== true) { 6 | return next(); 7 | } 8 | 9 | const api = require('../../../api')['v0.1']; 10 | 11 | return api.clients 12 | .read({slug: 'ghost-frontend'}) 13 | .then((client) => { 14 | client = client.clients[0]; 15 | 16 | if (client.status === 'enabled') { 17 | res.locals.client = { 18 | id: client.slug, 19 | secret: client.secret 20 | }; 21 | } 22 | 23 | next(); 24 | }) 25 | .catch((err) => { 26 | // Log the error, but carry on as this is non-critical 27 | common.logging.error(err); 28 | next(); 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /core/server/services/themes/list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Store themes after loading them from the file system 3 | */ 4 | var _ = require('lodash'), 5 | themeListCache = {}; 6 | 7 | module.exports = { 8 | get: function get(key) { 9 | return themeListCache[key]; 10 | }, 11 | 12 | getAll: function getAll() { 13 | return themeListCache; 14 | }, 15 | 16 | set: function set(key, theme) { 17 | themeListCache[key] = _.cloneDeep(theme); 18 | return themeListCache[key]; 19 | }, 20 | 21 | del: function del(key) { 22 | delete themeListCache[key]; 23 | }, 24 | 25 | init: function init(themes) { 26 | var self = this; 27 | // First, reset the cache 28 | themeListCache = {}; 29 | // For each theme, call set. Allows us to do processing on set later. 30 | _.each(themes, function (theme, key) { 31 | self.set(key, theme); 32 | }); 33 | 34 | return themeListCache; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /core/server/services/routing/PreviewRouter.js: -------------------------------------------------------------------------------- 1 | const ParentRouter = require('./ParentRouter'); 2 | const urlService = require('../url'); 3 | const controllers = require('./controllers'); 4 | 5 | class PreviewRouter extends ParentRouter { 6 | constructor(RESOURCE_CONFIG) { 7 | super('PreviewRouter'); 8 | 9 | this.RESOURCE_CONFIG = RESOURCE_CONFIG.QUERY.preview; 10 | 11 | this.route = {value: '/p/'}; 12 | 13 | this._registerRoutes(); 14 | } 15 | 16 | _registerRoutes() { 17 | this.router().use(this._prepareContext.bind(this)); 18 | 19 | this.mountRoute(urlService.utils.urlJoin(this.route.value, ':uuid', ':options?'), controllers.preview); 20 | } 21 | 22 | _prepareContext(req, res, next) { 23 | res.routerOptions = { 24 | type: 'entry', 25 | query: this.RESOURCE_CONFIG, 26 | context: ['preview'] 27 | }; 28 | 29 | next(); 30 | } 31 | } 32 | 33 | module.exports = PreviewRouter; 34 | -------------------------------------------------------------------------------- /core/test/unit/api/v2/utils/index_spec.js: -------------------------------------------------------------------------------- 1 | const should = require('should'); 2 | const sinon = require('sinon'); 3 | const utils = require('../../../../../server/api/v2/utils'); 4 | 5 | describe('Unit: v2/utils/index', function () { 6 | afterEach(function () { 7 | sinon.restore(); 8 | }); 9 | 10 | describe('isContentAPI', function () { 11 | it('is true when apiType is "content"', function () { 12 | const frame = { 13 | apiType: 'content' 14 | }; 15 | should(utils.isContentAPI(frame)).equal(true); 16 | }); 17 | 18 | it('is false when apiType is admin', function () { 19 | const frame = { 20 | apiType: 'admin', 21 | options: { 22 | context: { 23 | no: 'public' 24 | } 25 | } 26 | }; 27 | should(utils.isContentAPI(frame)).equal(false); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /core/server/data/migrations/init/2-create-fixtures.js: -------------------------------------------------------------------------------- 1 | var Promise = require('bluebird'), 2 | _ = require('lodash'), 3 | fixtures = require('../../schema/fixtures'), 4 | common = require('../../../lib/common'); 5 | 6 | module.exports.config = { 7 | transaction: true 8 | }; 9 | 10 | module.exports.up = function insertFixtures(options) { 11 | var localOptions = _.merge({ 12 | context: {internal: true}, 13 | migrating: true 14 | }, options); 15 | 16 | return Promise.mapSeries(fixtures.models, function (model) { 17 | common.logging.info('Model: ' + model.name); 18 | 19 | return fixtures.utils.addFixturesForModel(model, localOptions); 20 | }).then(function () { 21 | return Promise.mapSeries(fixtures.relations, function (relation) { 22 | common.logging.info('Relation: ' + relation.from.model + ' to ' + relation.to.model); 23 | return fixtures.utils.addFixturesForRelation(relation, localOptions); 24 | }); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /core/server/lib/members/static/auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ghost-member", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "build": "preact build --template=index.html --src=index.js --dest=dist --service-worker=false --no-prerender", 7 | "dev": "yarn build --no-production && preact watch --port=8080", 8 | "lint": "eslint src" 9 | }, 10 | "eslintIgnore": [ 11 | "build/*" 12 | ], 13 | "devDependencies": { 14 | "autoprefixer": "^9.4.2", 15 | "cssnano": "^4.1.7", 16 | "grunt": "1.0.3", 17 | "grunt-shell": "2.1.0", 18 | "postcss-color-mod-function": "^3.0.3", 19 | "postcss-css-variables": "^0.11.0", 20 | "postcss-custom-properties": "^8.0.9", 21 | "postcss-import": "^12.0.1", 22 | "preact-cli": "https://github.com/TryGhost/preact-cli/tarball/421f03bcf954c79d5f14fd2e278049a9bdfcba83" 23 | }, 24 | "dependencies": { 25 | "preact": "^8.2.1", 26 | "preact-compat": "^3.17.0", 27 | "react-stripe-elements": "^2.0.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /core/test/unit/api/shared/util/options_spec.js: -------------------------------------------------------------------------------- 1 | const should = require('should'); 2 | const optionsUtil = require('../../../../../server/api/shared/utils/options'); 3 | 4 | describe('Unit: api/shared/util/options', () => { 5 | it('returns an array with empty string when no parameters are passed', () => { 6 | optionsUtil.trimAndLowerCase().should.eql(['']); 7 | }); 8 | 9 | it('returns single item array', () => { 10 | optionsUtil.trimAndLowerCase('butter').should.eql(['butter']); 11 | }); 12 | 13 | it('returns multiple items in array', () => { 14 | optionsUtil.trimAndLowerCase('peanut, butter').should.eql(['peanut', 'butter']); 15 | }); 16 | 17 | it('lowercases and trims items in the string', () => { 18 | optionsUtil.trimAndLowerCase(' PeanUt, buTTer ').should.eql(['peanut', 'butter']); 19 | }); 20 | 21 | it('accepts parameters in form of an array', () => { 22 | optionsUtil.trimAndLowerCase([' PeanUt', ' buTTer ']).should.eql(['peanut', 'butter']); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Report reproducible software issues so we can improve 4 | 5 | --- 6 | 7 | Welcome to Ghost's GitHub repo! 👋🎉 8 | 9 | We use GitHub only for bug reports 🐛 10 | 11 | Anything else should be posted to https://forum.ghost.org 👫 12 | 13 | 🚨For support, help & questions use https://forum.ghost.org/c/help 14 | 💡For feature requests & ideas you can post and vote on https://forum.ghost.org/c/Ideas 15 | 16 | If your issue is with Ghost CLI, please report it on the CLI repo ➡️ https://github.com/TryGhost/Ghost-CLI/issues/new. 17 | 18 | ### Issue Summary 19 | 20 | A summary of the issue and the browser/OS environment in which it occurs. 21 | 22 | ### To Reproduce 23 | 24 | 1. This is the first step 25 | 2. This is the second step, etc. 26 | 27 | Any other info e.g. Why do you consider this to be a bug? What did you expect to happen instead? 28 | 29 | ### Technical details: 30 | 31 | * Ghost Version: 32 | * Node Version: 33 | * Browser/OS: 34 | * Database: 35 | -------------------------------------------------------------------------------- /core/server/web/shared/middlewares/log-request.js: -------------------------------------------------------------------------------- 1 | const uuid = require('uuid'); 2 | const common = require('../../../lib/common'); 3 | 4 | /** 5 | * @TODO: 6 | * - move middleware to ignition? 7 | */ 8 | module.exports = function logRequest(req, res, next) { 9 | const startTime = Date.now(), 10 | requestId = req.get('X-Request-ID') || uuid.v1(); 11 | 12 | function logResponse() { 13 | res.responseTime = (Date.now() - startTime) + 'ms'; 14 | req.requestId = requestId; 15 | req.userId = req.user ? (req.user.id ? req.user.id : req.user) : null; 16 | 17 | if (req.err && req.err.statusCode !== 404) { 18 | common.logging.error({req: req, res: res, err: req.err}); 19 | } else { 20 | common.logging.info({req: req, res: res}); 21 | } 22 | 23 | res.removeListener('finish', logResponse); 24 | res.removeListener('close', logResponse); 25 | } 26 | 27 | res.on('finish', logResponse); 28 | res.on('close', logResponse); 29 | next(); 30 | }; 31 | -------------------------------------------------------------------------------- /core/server/analytics-events.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'), 2 | Analytics = require('analytics-node'), 3 | config = require('./config'), 4 | common = require('./lib/common'), 5 | analytics; 6 | 7 | module.exports.init = function () { 8 | analytics = new Analytics(config.get('segment:key')); 9 | var toTrack, 10 | trackDefaults = config.get('segment:trackDefaults') || {}, 11 | prefix = config.get('segment:prefix') || ''; 12 | 13 | toTrack = [ 14 | { 15 | event: 'post.published', 16 | name: 'Post Published' 17 | }, 18 | { 19 | event: 'page.published', 20 | name: 'Page Published' 21 | }, 22 | { 23 | event: 'theme.uploaded', 24 | name: 'Theme Uploaded' 25 | } 26 | ]; 27 | 28 | _.each(toTrack, function (track) { 29 | common.events.on(track.event, function () { 30 | analytics.track(_.extend(trackDefaults, {event: prefix + track.name})); 31 | }); 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /core/server/services/themes/loader.js: -------------------------------------------------------------------------------- 1 | var debug = require('ghost-ignition').debug('themes:loader'), 2 | config = require('../../config'), 3 | packageJSON = require('../../lib/fs/package-json'), 4 | themeList = require('./list'), 5 | loadAllThemes, 6 | loadOneTheme; 7 | 8 | loadAllThemes = function loadAllThemes() { 9 | return packageJSON.read 10 | .all(config.getContentPath('themes')) 11 | .then(function updateThemeList(themes) { 12 | debug('loading themes', Object.keys(themes)); 13 | 14 | themeList.init(themes); 15 | }); 16 | }; 17 | 18 | loadOneTheme = function loadOneTheme(themeName) { 19 | return packageJSON.read 20 | .one(config.getContentPath('themes'), themeName) 21 | .then(function (readThemes) { 22 | debug('loaded one theme', themeName); 23 | return themeList.set(themeName, readThemes[themeName]); 24 | }); 25 | }; 26 | 27 | module.exports = { 28 | loadAllThemes: loadAllThemes, 29 | loadOneTheme: loadOneTheme 30 | }; 31 | -------------------------------------------------------------------------------- /content/adapters/README.md: -------------------------------------------------------------------------------- 1 | # Content / Adapters 2 | 3 | 4 | An adapter is a way to override a default behaviour in Ghost. 5 | The default behaviour in Ghost is as following: 6 | 7 | ### LocalFileStorage 8 | By default Ghost will upload your images to the `content/images` folder. 9 | The LocalFileStorage is using the file system to read or write images. 10 | This default adapter can be found in `core/server/adapters/storage/LocalFileStorage.js`. 11 | 12 | ### SchedulingDefault 13 | By default Ghost will schedule your posts using a pure JavaScript solution. 14 | It doesn't use `cron` or similar. 15 | This default adapter can be found in `core/server/adapters/scheduling/SchedulingDefault.js`. 16 | 17 | ### Custom Adapter 18 | To override any of the default adapters, you have to add a folder (`content/adapters/storage` or `content/adapters/scheduling`) and copy your adapter to it. 19 | 20 | Please follow our detailed guides: 21 | https://docs.ghost.org/v1/docs/using-a-custom-storage-module 22 | https://docs.ghost.org/v1/docs/using-a-custom-scheduling-module 23 | --------------------------------------------------------------------------------