Repository: fdv/typo Branch: master Commit: 62b9c0233a18 Files: 275 Total size: 621.7 KB Directory structure: gitextract_oagz7ovf/ ├── .erb-lint.yml ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ └── ruby.yml ├── .gitignore ├── .hound.yml ├── .metrics ├── .reek.yml ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── TODO.txt ├── app/ │ ├── assets/ │ │ ├── config/ │ │ │ └── manifest.js │ │ └── images/ │ │ └── .keep │ ├── controllers/ │ │ └── application_controller.rb │ ├── helpers/ │ │ └── theme_helper.rb │ ├── mailers/ │ │ └── application_mailer.rb │ ├── models/ │ │ ├── application_record.rb │ │ ├── authors_sidebar.rb │ │ ├── concerns/ │ │ │ └── .keep │ │ ├── livesearch_sidebar.rb │ │ ├── notes_sidebar.rb │ │ ├── popular_sidebar.rb │ │ └── xml_sidebar.rb │ └── views/ │ ├── authors_sidebar/ │ │ └── _content.html.erb │ ├── livesearch_sidebar/ │ │ └── _content.html.erb │ ├── notes_sidebar/ │ │ └── _content.html.erb │ ├── popular_sidebar/ │ │ └── _content.html.erb │ └── xml_sidebar/ │ └── _content.html.erb ├── bin/ │ ├── bundle │ ├── i18n-tasks │ ├── rails │ ├── rake │ ├── rspec │ ├── setup │ ├── update │ └── yarn ├── config/ │ ├── application.rb │ ├── boot.rb │ ├── cable.yml │ ├── database.yml.mysql │ ├── database.yml.postgresql │ ├── environment.rb │ ├── environments/ │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── filemanager.yml │ ├── i18n-tasks.yml │ ├── initializers/ │ │ ├── application_controller_renderer.rb │ │ ├── assets.rb │ │ ├── backtrace_silencers.rb │ │ ├── carrierwave.rb │ │ ├── content_security_policy.rb │ │ ├── cookies_serializer.rb │ │ ├── filter_parameter_logging.rb │ │ ├── flickr.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── new_framework_defaults_7_1.rb │ │ ├── non_digest_assets.rb │ │ ├── permissions_policy.rb │ │ ├── rack_attack.rb │ │ ├── recaptcha.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── locales/ │ │ ├── ar.yml │ │ ├── da.yml │ │ ├── de.yml │ │ ├── en.yml │ │ ├── es-MX.yml │ │ ├── fr.yml │ │ ├── he.yml │ │ ├── it.yml │ │ ├── ja.yml │ │ ├── lt.yml │ │ ├── nb.yml │ │ ├── nl.yml │ │ ├── pl.yml │ │ ├── pt-BR.yml │ │ ├── ro.yml │ │ ├── ru.yml │ │ ├── sidebars.ar.yml │ │ ├── sidebars.da.yml │ │ ├── sidebars.de.yml │ │ ├── sidebars.en.yml │ │ ├── sidebars.es-MX.yml │ │ ├── sidebars.fr.yml │ │ ├── sidebars.he.yml │ │ ├── sidebars.it.yml │ │ ├── sidebars.ja.yml │ │ ├── sidebars.lt.yml │ │ ├── sidebars.nb.yml │ │ ├── sidebars.nl.yml │ │ ├── sidebars.pl.yml │ │ ├── sidebars.pt-BR.yml │ │ ├── sidebars.ro.yml │ │ ├── sidebars.ru.yml │ │ ├── sidebars.zh-CN.yml │ │ ├── sidebars.zh-TW.yml │ │ ├── zh-CN.yml │ │ └── zh-TW.yml │ ├── mail.yml.example │ ├── puma.rb │ ├── routes.rb │ ├── secrets.yml │ ├── spring.rb │ └── storage.yml ├── config.ru ├── db/ │ ├── converters/ │ │ ├── README │ │ ├── blogger.rb │ │ ├── feed.rb │ │ └── rss.rb │ ├── migrate/ │ │ ├── 113_initial_schema.publify_core_engine.rb │ │ ├── 114_fixes_buggy_articles_and_notes.publify_core_engine.rb │ │ ├── 115_drops_categories_for_tags.publify_core_engine.rb │ │ ├── 20150207131657_add_missing_indexes.publify_core_engine.rb │ │ ├── 20150807134129_simplify_redirect_relations.publify_core_engine.rb │ │ ├── 20150808052637_add_blog_ids.publify_core_engine.rb │ │ ├── 20150808191127_add_blog_id_to_redirects.publify_core_engine.rb │ │ ├── 20150810094754_add_blog_id_to_tags.publify_core_engine.rb │ │ ├── 20160108111120_add_devise_to_users.publify_core_engine.rb │ │ ├── 20160108184201_move_last_connection_to_last_sign_in_at.publify_core_engine.rb │ │ ├── 20160110094906_remove_profiles_rights.publify_core_engine.rb │ │ ├── 20160605103918_replace_profile_id_with_string.publify_core_engine.rb │ │ ├── 20160605154632_remove_profiles.publify_core_engine.rb │ │ ├── 20160701061851_demand_blog_id_on_contents.publify_core_engine.rb │ │ ├── 20160701062604_add_blog_id_to_resources.publify_core_engine.rb │ │ ├── 20161030121548_add_sessions_table.rb │ │ ├── 20170528093428_move_resources_to_content.publify_core_engine.rb │ │ ├── 20170528120220_move_tags_to_content.publify_core_engine.rb │ │ ├── 20170530063901_remove_separate_published_flag.publify_core_engine.rb │ │ ├── 20170605103539_remove_extra_state_columns_from_feedback.publify_core_engine.rb │ │ ├── 20170702105518_remove_published_at_from_feedback.publify_core_engine.rb │ │ ├── 20190210121314_add_text_filter_name_fields.publify_core_engine.rb │ │ ├── 20190210121315_move_text_filter_to_name.publify_core_engine.rb │ │ ├── 20190210121316_remove_text_filter_ids.publify_core_engine.rb │ │ ├── 20190210121317_remove_text_filters.publify_core_engine.rb │ │ ├── 20200413141133_add_unique_indexes.publify_core_engine.rb │ │ ├── 20221007091118_remove_table_sitealizer.publify_core_engine.rb │ │ ├── 20221010170801_remove_page_caches_table.publify_core_engine.rb │ │ └── 20221012164027_remove_itunes_fields_from_resources.publify_core_engine.rb │ ├── schema.rb │ └── seeds.rb ├── doc/ │ ├── CACHE.SETUP.README │ └── dependency_decisions.yml ├── lib/ │ ├── generators/ │ │ └── sidebar/ │ │ ├── USAGE │ │ ├── sidebar_generator.rb │ │ └── templates/ │ │ ├── _content.html.erb.erb │ │ ├── model_spec.rb.erb │ │ └── sidebar.rb.erb │ ├── publify_app/ │ │ └── textfilter/ │ │ ├── flickr.rb │ │ ├── htmlfilter.rb │ │ └── lightbox.rb │ ├── publify_plugins/ │ │ ├── avatar_plugin.rb │ │ └── gravatar.rb │ └── tasks/ │ ├── i18n.rake │ └── rubocop.rake ├── log/ │ └── .keep ├── public/ │ ├── 404.html │ ├── 422.html │ └── 500.html ├── spec/ │ ├── controllers/ │ │ ├── articles_controller_spec.rb │ │ ├── feedback_controller_spec.rb │ │ ├── tags_controller_spec.rb │ │ └── xml_controller_spec.rb │ ├── features/ │ │ └── switch_theme_spec.rb │ ├── lib/ │ │ ├── publify_plugins/ │ │ │ └── gravatar_spec.rb │ │ ├── publify_textfilter_flickr_spec.rb │ │ ├── publify_textfilter_lightbox_spec.rb │ │ └── text_filter_plugin_spec.rb │ ├── models/ │ │ ├── authors_sidebar_spec.rb │ │ ├── notes_sidebar_spec.rb │ │ ├── popular_sidebar_spec.rb │ │ ├── sidebar_registry_spec.rb │ │ └── xml_sidebar_spec.rb │ ├── rails_helper.rb │ ├── spec_helper.rb │ └── views/ │ ├── comments/ │ │ └── html_sanitization_spec.rb │ ├── layouts/ │ │ └── default_spec.rb │ └── xml_sidebar/ │ └── _content.html.erb_spec.rb ├── themes/ │ └── bootstrap-2/ │ ├── about.markdown │ ├── javascripts/ │ │ └── bootstrap.js │ ├── less/ │ │ ├── .csscomb.json │ │ ├── .csslintrc │ │ ├── alerts.less │ │ ├── badges.less │ │ ├── bootstrap.less │ │ ├── breadcrumbs.less │ │ ├── button-groups.less │ │ ├── buttons.less │ │ ├── carousel.less │ │ ├── close.less │ │ ├── code.less │ │ ├── component-animations.less │ │ ├── dropdowns.less │ │ ├── forms.less │ │ ├── glyphicons.less │ │ ├── grid.less │ │ ├── input-groups.less │ │ ├── jumbotron.less │ │ ├── labels.less │ │ ├── list-group.less │ │ ├── media.less │ │ ├── mixins/ │ │ │ ├── alerts.less │ │ │ ├── background-variant.less │ │ │ ├── border-radius.less │ │ │ ├── buttons.less │ │ │ ├── center-block.less │ │ │ ├── clearfix.less │ │ │ ├── forms.less │ │ │ ├── gradients.less │ │ │ ├── grid-framework.less │ │ │ ├── grid.less │ │ │ ├── hide-text.less │ │ │ ├── image.less │ │ │ ├── labels.less │ │ │ ├── list-group.less │ │ │ ├── nav-divider.less │ │ │ ├── nav-vertical-align.less │ │ │ ├── opacity.less │ │ │ ├── pagination.less │ │ │ ├── panels.less │ │ │ ├── progress-bar.less │ │ │ ├── reset-filter.less │ │ │ ├── resize.less │ │ │ ├── responsive-visibility.less │ │ │ ├── size.less │ │ │ ├── tab-focus.less │ │ │ ├── table-row.less │ │ │ ├── text-emphasis.less │ │ │ ├── text-overflow.less │ │ │ └── vendor-prefixes.less │ │ ├── mixins.less │ │ ├── modals.less │ │ ├── navbar.less │ │ ├── navs.less │ │ ├── normalize.less │ │ ├── pager.less │ │ ├── pagination.less │ │ ├── panels.less │ │ ├── popovers.less │ │ ├── print.less │ │ ├── progress-bars.less │ │ ├── responsive-embed.less │ │ ├── responsive-utilities.less │ │ ├── scaffolding.less │ │ ├── tables.less │ │ ├── theme.less │ │ ├── thumbnails.less │ │ ├── tooltip.less │ │ ├── type.less │ │ ├── utilities.less │ │ ├── variables.less │ │ └── wells.less │ ├── stylesheets/ │ │ ├── bootstrap.css │ │ └── style.css │ └── views/ │ ├── articles/ │ │ ├── _article.html.erb │ │ ├── _comment_counter.html.erb │ │ ├── _comment_form.html.erb │ │ ├── _comment_list.html.erb │ │ ├── _meta.html.erb │ │ ├── _trackback.html.erb │ │ ├── index.html.erb │ │ ├── read.html.erb │ │ ├── search.html.erb │ │ └── view_page.html.erb │ ├── comments/ │ │ └── _comment.html.erb │ ├── layouts/ │ │ └── default.html.erb │ └── tags/ │ └── show.html.erb └── vendor/ └── .keep ================================================ FILE CONTENTS ================================================ ================================================ FILE: .erb-lint.yml ================================================ --- linters: ErbSafety: enabled: true Rubocop: enabled: true rubocop_config: inherit_from: - .rubocop.yml Layout/InitialIndentation: Enabled: false Layout/TrailingBlankLines: Enabled: false Layout/TrailingWhitespace: Enabled: false Naming/FileName: Enabled: false Style/FrozenStringLiteralComment: Enabled: false Layout/LineLength: Enabled: true Lint/UselessAssignment: Enabled: false Rails/OutputSafety: Enabled: false ================================================ FILE: .github/dependabot.yml ================================================ # Documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "bundler" directory: "/" schedule: interval: "weekly" open-pull-requests-limit: 15 - package-ecosystem: "npm" directory: "/" schedule: interval: "weekly" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" ================================================ FILE: .github/workflows/ruby.yml ================================================ # This workflow will download a prebuilt Ruby version, install dependencies and # run tests with Rake # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby name: CI "on": push: branches: - master - 10-0-stable - 9-2-stable pull_request: branches: - master - 10-0-stable - 9-2-stable workflow_dispatch: jobs: test-postgresql: name: "Test on PostgreSQL" runs-on: ubuntu-latest strategy: matrix: ruby: ["3.2", "3.3", "3.4"] services: postgres: image: postgres:latest env: POSTGRES_PASSWORD: postgres ports: ['5432:5432'] steps: - uses: actions/checkout@v6 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Database Configuration run: cp config/database.yml.postgresql config/database.yml - name: Migrate database run: bundle exec rake db:create db:migrate - name: Run tests run: bundle exec rake test-mysql: name: "Test on MySQL" runs-on: ubuntu-latest strategy: matrix: ruby: ["3.2", "3.3", "3.4"] services: mysql: image: mysql:latest env: MYSQL_ROOT_PASSWORD: mysql ports: ['3306:3306'] steps: - uses: actions/checkout@v6 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Database Configuration run: cp config/database.yml.mysql config/database.yml - name: Migrate database run: bundle exec rake db:create db:migrate - name: Run tests run: bundle exec rake test-sqlite: name: "Test on SQLite" runs-on: ubuntu-latest strategy: matrix: ruby: ["3.2", "3.3", "3.4"] steps: - uses: actions/checkout@v6 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Database Configuration run: cp config/database.yml.sqlite config/database.yml - name: Migrate database run: bundle exec rake db:create db:migrate - name: Run tests run: bundle exec rake lint: name: "Linting" runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: 3.2 bundler-cache: true - name: Run lint task run: bundle exec rake lint ================================================ FILE: .gitignore ================================================ config/database.yml config/mail.yml config/timezone.yml *.log *~ db/db* db/*.sqlite* .*.swp .*.swo .DS_Store .byebug_history installer/rails_installer.yml /public/files /public/images/theme /public/javascripts/ckeditor/config.js /public/javascripts/ckeditor/config.bak /public/javascripts/all.js /public/robots.txt /public/stylesheets/theme /public/stylesheets/all.css /publify_core/spec/dummy/public/files tmp/* coverage/ nbproject public/cache/* Gemfile.lock .rbenv-version .ruby-version .env .bundle/ .sass-cache/ /vendor/bundle /themes/* !/themes/bootstrap-2 !/themes/plain gems/ node_modules/ spec/examples.txt ================================================ FILE: .hound.yml ================================================ ruby: config_file: .rubocop.yml ================================================ FILE: .metrics ================================================ MetricFu::Configuration.run do |config| config.rcov[:test_files] = 'spec/**/*_spec.rb' config.rcov[:rcov_opts] << '-Ispec' config.syntax_highlighting = false end ================================================ FILE: .reek.yml ================================================ exclude_paths: - 'db/migrate' - 'publify_amazon_sidebar/spec/dummy/db/migrate' - 'publify_textfilter_code/spec/dummy/db/migrate' - 'publify_core/db/migrate' directories: "app/controllers": IrresponsibleModule: enabled: false NestedIterators: max_allowed_nesting: 2 UnusedPrivateMethod: enabled: false "app/helpers": IrresponsibleModule: enabled: false UtilityFunction: enabled: false "app/models": IrresponsibleModule: enabled: false ================================================ FILE: .rspec ================================================ --color --require spec_helper ================================================ FILE: .rubocop.yml ================================================ inherit_from: .rubocop_todo.yml inherit_mode: merge: - Exclude plugins: - rubocop-capybara - rubocop-factory_bot - rubocop-performance - rubocop-rails - rubocop-rspec - rubocop-rspec_rails AllCops: Exclude: - 'bin/*' - 'db/converters/*.rb' - 'db/schema.rb' NewCops: enable TargetRubyVersion: 3.2 TargetRailsVersion: 7.1 Rails: Enabled: true # We have duplication due to extra logic in the Gemfile Bundler/DuplicatedGem: Enabled: false # Require tests to specify whether an element is a link or a button Capybara/ClickLinkOrButtonStyle: EnforcedStyle: strict # Match style of regular expectations Capybara/RSpec/NegationMatcher: EnforcedStyle: not_to # Make BeginEndAlignment behavior match EndAlignment Layout/BeginEndAlignment: EnforcedStyleAlignWith: begin # Spaces in strings with line continuations go at the beginning of the line. Layout/LineContinuationLeadingSpace: EnforcedStyle: leading # Lenient line length that fits in pull requests Layout/LineLength: Max: 92 Layout/MultilineMethodCallBraceLayout: EnforcedStyle: same_line # Multi-line operations should be simply indented. Aligning them makes it even # harder to keep a sane line length. Layout/MultilineMethodCallIndentation: EnforcedStyle: indented # Multi-line operations should be simply indented. Aligning them makes it even # harder to keep a sane line length. Layout/MultilineOperationIndentation: EnforcedStyle: indented # Uniform block layout Layout/SpaceBeforeBlockBraces: EnforcedStyleForEmptyBraces: space # Assume the programmer knows how bracketed block syntax works Lint/AmbiguousBlockAssociation: Enabled: false # Allow if (foo = get_foo) style Lint/AssignmentInCondition: AllowSafeAssignment: true # Suppress BlockLength for some files: # - Spec describe blocks can be any length # - Environment configuration blocks can be any length Metrics/BlockLength: Exclude: - 'spec/**/*' - 'config/environments/*' # Initial schema has a very large change method Metrics/MethodLength: Exclude: - 'db/migrate/113_initial_schema.publify_core_engine.rb' Performance/StartWith: AutoCorrect: true Performance/EndWith: AutoCorrect: true # Migrations should not use ApplicationRecord Rails/ApplicationRecord: Exclude: - 'db/migrate/*' # This cop has issues and is too database-specific Rails/BulkChangeTable: Enabled: false # These tasks do not need environment Rails/RakeEnvironment: Exclude: - 'lib/tasks/i18n.rake' - 'lib/tasks/rspec.rake' - 'lib/tasks/rubocop.rake' # Migrations should not do validations in general? Rails/SkipsModelValidations: Exclude: - 'db/migrate/*' # Spec type inference is disabled, so this cop must be disabled too. RSpecRails/InferredSpecType: Enabled: false # Allow the use of 'and' 'or' in control structures. Style/AndOr: EnforcedStyle: conditionals Style/ClassAndModuleChildren: Enabled: false Style/CollectionMethods: Enabled: true # Documenting all classes is not common in Rails projects. Style/Documentation: Enabled: false # This cop doesn't detect use of break yet Style/EachWithObject: Enabled: false # Allows format strings with: format, sprintf or percent. Style/FormatString: Enabled: false # Require at least two dependent lines before suggesting a guard clause Style/GuardClause: MinBodyLength: 2 # TODO: Re-enable? Style/NumericPredicate: Enabled: false # Not all objects that implement #size implement #empty? Style/ZeroLengthPredicate: Enabled: false # Restore previous RuboCop defaults Style/PercentLiteralDelimiters: PreferredDelimiters: '%W': () '%w': () # Always use raise to raise exceptions Style/SignalException: EnforcedStyle: only_raise # Allow meaningful names for single-line reduce etc. Style/SingleLineBlockParams: Enabled: false # Prefer not to commit to need to interpolate Style/StringLiterals: EnforcedStyle: double_quotes # Be consistent with non-interpolated strings Style/StringLiteralsInInterpolation: EnforcedStyle: double_quotes # Prefer symbols to look like symbols Style/SymbolArray: EnforcedStyle: brackets # Allow small arrays of words with quotes Style/WordArray: MinSize: 4 ================================================ FILE: .rubocop_todo.yml ================================================ # This configuration was generated by # `rubocop --auto-gen-config --no-offense-counts --no-auto-gen-timestamp` # using RuboCop version 1.80.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. # URISchemes: http, https Layout/LineLength: Exclude: - 'config/initializers/new_framework_defaults_7_1.rb' # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. Metrics/AbcSize: Max: 157 # Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: Max: 177 # Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/CyclomaticComplexity: Max: 19 # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: Max: 48 # Configuration parameters: AllowedMethods, AllowedPatterns. Metrics/PerceivedComplexity: Max: 22 # Configuration parameters: Prefixes, AllowedPatterns. # Prefixes: when, with, without RSpec/ContextWording: Exclude: - 'spec/views/xml_sidebar/_content.html.erb_spec.rb' # Configuration parameters: CountAsOne. RSpec/ExampleLength: Max: 14 # Configuration parameters: AssignmentOnly. RSpec/InstanceVariable: Exclude: - 'spec/controllers/tags_controller_spec.rb' - 'spec/controllers/xml_controller_spec.rb' - 'spec/views/comments/html_sanitization_spec.rb' - 'spec/views/layouts/default_spec.rb' - 'spec/views/xml_sidebar/_content.html.erb_spec.rb' RSpec/LetSetup: Exclude: - 'spec/controllers/articles_controller_spec.rb' - 'spec/controllers/feedback_controller_spec.rb' RSpec/MultipleExpectations: Max: 10 # Configuration parameters: AllowedGroups. RSpec/NestedGroups: Max: 5 # Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. RSpec/VerifiedDoubles: Exclude: - 'spec/lib/publify_textfilter_flickr_spec.rb' - 'spec/lib/publify_textfilter_lightbox_spec.rb' Rails/CreateTableWithTimestamps: Exclude: - 'db/migrate/113_initial_schema.publify_core_engine.rb' - 'db/migrate/20150807134129_simplify_redirect_relations.publify_core_engine.rb' - 'db/migrate/20160605154632_remove_profiles.publify_core_engine.rb' - 'db/migrate/20190210121317_remove_text_filters.publify_core_engine.rb' - 'db/migrate/20221010170801_remove_page_caches_table.publify_core_engine.rb' Rails/ThreeStateBooleanColumn: Exclude: - 'db/migrate/113_initial_schema.publify_core_engine.rb' Security/Eval: Exclude: - 'Gemfile' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: Exclude: - 'db/migrate/20150808191127_add_blog_id_to_redirects.publify_core_engine.rb' - 'db/migrate/20150810094754_add_blog_id_to_tags.publify_core_engine.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: implicit, explicit Style/RescueStandardError: Exclude: - 'db/migrate/20150808052637_add_blog_ids.publify_core_engine.rb' - 'lib/publify_textfilter_flickr.rb' ================================================ FILE: CHANGELOG.md ================================================ # Changelog See the changelogs for the individual engines for more details for releases 9.0 and up. ## 10.0.1 / 2024-06-28 This is a bug fix and security release. * Update the dependency on `publify_core` from `~> 10.0.0` to `~> 10.0.2`. This includes the following two security updates: * Safely link target URLs for Redirects in admin ([publify_core#148] by [mvz]) * Upgrade jquery-ui-rails to version 7.0 ([publify_core#149] by [mvz]) See the `publify_core` changelog for further details. * Update various other dependencies (various pull requests) [publify_core#148]: https://github.com/publify/publify_core/pull/148 [publify_core#149]: https://github.com/publify/publify_core/pull/149 [mvz]: https://github.com/mvz ## 10.0.0 / 2023-06-25 This major release updates Publify to Rails 6.1 in preparation of the upgrade to Rails 7.0. It also updates the set of supported Rubies to 2.7 through 3.2. ### Security-related changes * Require at least Rails 6.1.6.1 [#1068](https://github.com/publify/publify/pull/1068) * Update puma dependency to require at least version 5.6.4 [#1064](https://github.com/publify/publify/pull/1064) ### Updated dependencies * Upgrade to Rails 6.1 [#987](https://github.com/publify/publify/pull/987), [#1014](https://github.com/publify/publify/pull/1014), * Support only Ruby 2.7 through 3.2 [#1013](https://github.com/publify/publify/pull/1013) [#1041](https://github.com/publify/publify/pull/1041) [#1115](https://github.com/publify/publify/pull/1115) [#1120](https://github.com/publify/publify/pull/1120) * Update various other dependencies (various pull requests) ### Breaking changes * Remove support for Textile as a text format [#1001](https://github.com/publify/publify/pull/1001) * Require email uniqueness to be case-insensitive [#1080](https://github.com/publify/publify/pull/1080) ### Other changes * Add arabic language to the project [#1060](https://github.com/publify/publify/pull/1060) by [ahmedhamid13](https://github.com/ahmedhamid13) * Fix article search rendering in bootstrap theme [#1101](https://github.com/publify/publify/pull/1101) * Remove local copies of engines and use external ones instead [#1099](https://github.com/publify/publify/pull/1099) * Require AWS configuration to be present when choosing AWS storage [#1082](https://github.com/publify/publify/pull/1082) * Replace deprecated non-digest-assets configuration [#1019](https://github.com/publify/publify/pull/1019) ### Internal changes * Remove `sitealizer` table [#1089](https://github.com/publify/publify/pull/1089) by [SupriyaMedankar](https://github.com/SupriyaMedankar) * Remove itunes fields from resources [#1092](https://github.com/publify/publify/pull/1092) by [SupriyaMedankar](https://github.com/SupriyaMedankar) * Remove `page_caches` table [#1090](https://github.com/publify/publify/pull/1090) by [SupriyaMedankar](https://github.com/SupriyaMedankar) * Remove `dynamic_form` dependency [#991](https://github.com/publify/publify/pull/991) See also the changelogs for [publify_core](https://github.com/publify/publify_core/blob/v10.0.0/CHANGELOG.md#1000--2023-06-25), [publify_textfilter_code](https://github.com/publify/publify_textfilter_code/blob/v10.0.0/CHANGELOG.md#1000--2023-06-25) and [publify_amazon_sidebar](https://github.com/publify/publify_amazon_sidebar/blob/v10.0.0/CHANGELOG.md#1000--2023-06-25). ## 9.2.10 / 2023-01-08 This release fixes several security issues: * Bump Rails version to 5.2.8.1 [#1070](https://github.com/publify/publify/pull/1070) * Limit length of settings values [#1072](https://github.com/publify/publify/pull/1072) * Require login to stay unique when updating a User [#1073](https://github.com/publify/publify/pull/1073) * Validate lengths of string attributes [#1077](https://github.com/publify/publify/pull/1077) * Strip EXIF data from resource uploads [#1078](https://github.com/publify/publify/pull/1078) * Require user passwords to be strong [#1086](https://github.com/publify/publify/pull/1086) ## 9.2.9 / 2022-05-22 This release fixes two security issues: * Fix admin article access control [#1065](https://github.com/publify/publify/pull/1065) * Refuse html files as resources even if declared to be plain text [#1066](https://github.com/publify/publify/pull/1066) ## 9.2.8 / 2022-05-14 This release fixes several security issues: * Disallow comments on draft articles [#1048](https://github.com/publify/publify/pull/1048) * Disallow images in comments [#1054](https://github.com/publify/publify/pull/1054) * Hide bodies of password-protected articles in search results [#1057](https://github.com/publify/publify/pull/1057) * Do not create article meta description for password-protected articles [#1061](https://github.com/publify/publify/pull/1061) Additionally, it includes the following changes: * Clean up Feedback validation [#1051](https://github.com/publify/publify/pull/1051) * Bump mimimum puma and Rails versions [#1050](https://github.com/publify/publify/pull/1050) * Fix password reset process [#1055](https://github.com/publify/publify/pull/1055) * Fix password protected article reveal [#1049](https://github.com/publify/publify/pull/1049) * Provide correct `article_id` input in bulkops form [#1058](https://github.com/publify/publify/pull/1058) * Bump minimum required Rails version [#1062](https://github.com/publify/publify/pull/1062) ## 9.2.7 / 2022-02-07 This release fixes a security issue: * Fix setting the article password from the Admin [#1044](https://github.com/publify/publify/pull/1044) ## 9.2.6 / 2022-01-07 This release fixes a minor security issue: * Rate-limit Devise logins and password resets Additionally, it includes the following change: * Add documentation about use of the media library ## 9.2.5 / 2021-10-11 This release fixes several security issues: * Force session cookie to be secure in production * Block ability to switch themes using a GET request; use a POST instead * Disallow user self-registration rather than hiding it * Let the browser not cache admin pages * Limit the set of allowed mime types for uploaded media * Limit allowed HTML in articles, pages and notes Additionally, it includes the following changes: * Fix resource size display in admin resource list * Trigger download of media in the Media Library in admin instead of displaying them directly ## 9.2.4 / 2021-10-02 * Require at least version 1.12.5 of nokogiri to avoid a security issue * Drop support for Ruby 2.4 since it is incompatible with nokogiri 1.12.5 ## 9.2.3 / 2021-05-22 * Bump Rails dependency to 5.2.6 * Replace mimemagic with marcel [#996](https://github.com/publify/publify/pull/996) ## 9.2.2 / 2021-03-21 * Fix the `publify:textile_to_markdown` task. This task failed on feedback and pages. ## 9.2.1 / 2021-03-20 This release updates dependencies due to security issues. * Bump minimum Rails version to 5.2.4.5 * Update `activerecord-session_store` dependency to 2.0.0 ## 9.2.0 / 2021-01-17 ### Breaking changes * Upgrade to Rails 5.2 (mvz) * Drop support for custom mail.yml configuration. Mail settings should now be configured in config/environments/production.rb (mvz) * Drop support for custom timezone.yml configuration. If relevant, the timezone can be set in config/application.rb (mvz) * Drop support for Ruby 2.2 and 2.3 (mvz) * Drop support for humans.txt (mvz) * Deprecate use of Textile. The admin will warn about any content that uses Textile formatting. A task has been added to convert this content to Markdown. The next release of Publify will drop Textile support entirely (mvz) ### Other changes * Fix comment preview (mvz) * Fix gravatar request URL (mvz) * Fix various issues with the bootstrap-2 theme (mvz) * Add support for Ruby 2.7 (mvz) * Update dependencies (mvz) * Allow rake tasks to be loaded when only production gems are present (mvz) ## 9.1.0 / 2018-04-19 * Upgrade to Rails 5.1 (mvz) * Update translations (xy2z, gergu) * Remove outdated converters (mvz) * Fix a bunch of issues (e-tobi) * Fix google analytics tag rendering (mvz) ## 9.0.1 * Use at least Rails 5.0.4 (mvz) * Update Russian translations (biggless) ## 9.0.0 ### Breaking/large changes * Use Rails' default method of setting `secret_key_base` in production: Through an environment variable. This means you will have to update your production environment so this variable is actually set (whithajess) * Break out Publify functionality into several engine gems (mvz) * Update to Rails 5.0 ### Other changes * Hide spinner on `ajax:complete` for article search (nathanallen) * Fix layout for log in form (mvz) * Fix spinner for sidebar editor (mvz) * Update copyright information and contributor list (mvz) * Fix XML sidebar (mvz) ## 8.3.3 * Fix Devise error during sign-in (mvz) * Protect from forgery on all actions (mvz) * Have Devise be paranoid by default (mvz) * Fix resource upload and properly check mime types (mvz) * Store session in the database to prevent session hijacking (mvz) * Correctly escape blog name in devise view (mvz) ## 8.3.2 * Replace deprecated count-with-conditions (mvz) * Loosen/update dependencies (mvz) * Fix google sitemap (mvz) * Restore theme helper loading (mvz) * Fix password edit form (jetware) ## 8.3.1 * Fix live search (mvz) * Introduce `SidebarRegistry` to avoid need to preload all sidebars (mvz) * Avoid use of `String#html_safe` (mvz) * Fix several cases of double-escaped HTML (mvz) * Avoid ambiguous field reference in feedback scopes (apsheronets) * Remove spurious error message when starting a new article (mvz) * Replace bundled bootstrap with bootstrap-sass gem (mvz) * Link Resource directly to Blog in order to make upload of images to media library work again (mvz) * Fix comment order and other feedback scopes (mvz) * Fix autosave (mvz) * Improve russian translation (apsheronets) * Fix note publication date entry (mvz) * Ensure settings update flash has the correct language (mvz) ## 8.3.0 ### Breaking/large changes * Make Publify multiblog-ready (mvz) * Replace custom Publify authentication system with Devise (mvz) * Replace custom Publify authorization system with CanCanCan (mvz) * Remove Profile model (mvz) * Remove long-deprecated `view_root` method for sidebars (mvz) * Provide registration mechanism for themes, allowing them to be stored anywhere (mvz) ### Other changes * Update Akismet API calls (drakontia) * Remove old Rails patches (mvz) * Update dependency on Rails to 4.2.5 (mvz) * Fix issues with missing translations and HTML escaping errors * Clean up helpers and partials (mvz) * Add specs to check for double HTML escaping and fix errors (mvz) * Load JavaScript asynchronously (mvz) * Remove own copies of jQuery files (mvz) * Fixed rake db:seed error (sachiotomita) * Add check for translation keys and fix errors (mvz) * Introduce RuboCop to automatically check style errors (mvz) * Fix many RuboCop offenses (mvz) * Update Travis config to stop testing on MRI 2.0.0, start testing on 2.3 (mvz) * Remove unused `#reset_local_cache` method (mvz) * Load JavaScript asynchronously in supporting layouts (mvz) * Fix translations for labels in Devise views (mvz) * Update dependencies (mvz) * Clean up textfilter code (mvz) * Test and improve setup process (mvz) * Update translations for Dutch (mvz) * Fix syntax error in mailer template (ttibau) * Ensure development dependencies don't break the build (mvz) * Fix spelling (mvz) * Use only the parts from fog needed by Publify (mvz) * Upgrade to mysql2 0.4.x (ttibau) * Remove unused code (mvz) * Load JavaScript asynchronously only in production (priit) * Run tests as a sub-URL installation by default (mvz) * Ensure new sidebars have `blog_id` set (mvz) * Fix bug in article attachment saving (mvz) * Fix broken authors sidebar (mvz) ## 8.2.0 ### Breaking/large changes * Update rails, jquery-rails and web-console to avoid security vulnerabilities (mvz) * Update dependencies (mvz, fdv) * Update to Rails 4.2 (mvz) * Roll up migrations up to 113 according to our [upgrade policy](https://github.com/publify/publify/wiki/Upgrading). You must now first upgrade to at least version 7 before upgrading to the latest version. (mvz) * Replace default theme bootstrap with bootstrap-2 (fdv) * Add a Plain theme demonstrating the use of Publify's default templates (mvz) * Use HTML instead of XHTML in views (fdv) ### Other changes * Restore hiding of automatic redirects from admin interface (mvz) * Fix broken stylesheet link in bootstrap2 theme (hmallett) * Create a fonts folder for themes, to replicate the Rails default (hmallet) * Update rubocop todo and use rubocop in travis (whithajess) * Autocorrect rubocop offenses (mvz) * Support Ruby 2.2 (mvz) * Indicate dependency on external JS runtime (mvz) * Several improvements to the Admin UI * Update pt-BR translations (ramirovjr) * Update nl translations (mvz) * Update fr translations (Stephanyan, giniouxe) * Update en translations (hmallett, stevenwilkin) * Update es-MX translations (hernamvel) * Changed navbar to dropdown onhover (jacemonje) * Improve Publify's default templates * Fix sidebar administration (mvz) * Various cleanups and improvements of code and specs * Fix several vulnerabilities reported by brakeman (mvz) * Use more resourceful routes (hmallett) * Fix editing users in Admin (pacergh) * Add foreign keys and indexes to the schema (hmallett) * Add a CONTRIBUTING.md file to help contributors (randomecho) * Remove test dependency on feedvalidator (mvz) * Remove old API links from RSD view (mvz) * Remove outdated schemas rake task (stevenwilkin) * Improve installation instructions (giniouxe) * Paginate article archives pages (giniouxe) * Handle tags that contain colons (ook) * Ensure cache path exists (pvcarrera) * Use protocol-relative URIs for Flickr images (flameeyes) * Update text filter help texts to use 'publify' (mvz) * Restore hiding/showing of optional comment fields (mvz) * Use rails-timeago to provide time ago display (mvz) * Adding new Publify favicon (fdv) * Fix typo (garethrees) * Various small bug fixes * Several theme fixes ## 8.1.1 Frédéric de Villamil (4): * Fixes broken autosave. * Fixes editor size for pages and articles. * Fixes publishing. Need to investigate why the specs did not break on that one. * Updating Publify version for 8.1.1 ## 8.1.0 Frédéric de Villamil (2): * Adds missing users-style.css in assets precompile. * Updates Rails version Matijs van Zuijlen (47): * Fix spec for sending pings on Article save * Enforce correct join table name * Fix finders * Fix Feedback scopes * Remove invalid attribute in Trackback spec setup * Fix finder in spec * Fix Migrator to match changed ActiveRecord::Migrator * Declare params used for assignment permitted in admin * Fix use of finders in admin and its specs * Move `#text_filter=` override to where it will be picked up * Fix implementation of `assert_xml` * Avoid exception when avatar plugin is undefined * Rewrite use of removed assertions * Fix tests for layoutless rendering * Fix rendered template spec * Avoid attempting to create articles with the same id in spec setup * Avoid attempting to update articles with the wrong id * Fix use of finders outside admin * Make Rails ignore the accept header again * Declare params permitted * Fix finders in migrations * Match files as generated by Rails 4.1 * Remove unused Sidebar methods * Introduce `valid` scope to find Sidebars safely * Remove unused methods * Wrap long comment * Add a TODO * Wrap long lines * Avoid time zone shift * Add a FIXME * Use current time zone for Tweets * Fix check for SQLite connection * Replace webrat with capybara * Replace should contain with should match * Fix usage of `have_selector` matcher * Make sidebar generator Rails 3 compliant. * Declare assets for precompilation * Allow GET to /setup * Include admin assets in precompile list * Fix creation of first article in SetupController * Fix hash rockets and white space in SetupController and its specs * Include admin css in precompile list * Move fonts to their normal position * Precompile font assets without cache buster * Make sure NonStupidDigestAssets is always defined * Fix spec setup * Make rendering notes in article list work Thomas Lecavelier (16): * Upgrade rails stack to 4.1.1 * `eager_loading` mandatory in conf * Page caching removed from Rails4.0, return it as a Gem * Observers removed from Rails4.0, return it as a Gem * WIP deprecated stuff conversion * Can't merge proc, you know… * `default_scope` explicitly require a block, now * `attr_accessible` no longer exist. User `params.require`/`permits` in controller instead. * `default_scope` for Note * I hate you all… T_T match must specify HTTP method(s) * Can't use same alias for 2 differents routes… * Misuse of named route + match / via * Replace every #match by its HTTP verb or define their opened verbs with :via key * Must check searches * Fix deprecation warning for Travis * Fix Blog.default regonn (3): * fix heroku config:set * delete unnecessary command * add heroku server restart command ## 8.0.2 Alexander Markov (1): * `.published_on` changed; see below Benoit C. Sirois (2): * Added some translations * Fixes link caching issue (All cached links are the same basically) Frédéric de Villamil (21): * Replaces the old Prototype based Lightbox with a more modern based on Bootstrap and Jquery. * Fixes bootstrap use in the image gallery. * Porting the lightbox plugin to the new version. * Removing useless Javascript. * Fixes the specs * Fixes that very annoying bug in the editor save bar. * Fixing a bug where the article content is displayed twice when using the more tag. * Encloses the sidebar admin help text in a blue block (like every help text). Also fixes the style on the per widget submit button + removes button class on the cancel link (this should be the default) * Removes the btn class on cancel * Enables the close icon on the help messages * Fixes layout differences betwen the page form and the post form * Fixes the last comments dashboard avatar alignment * Fixes articles search. * Fixes an encoding issue in the inbound link plugin. * Fixes the tag manager display issue. * Apparently, rendering an empty js.erb file on destroy makes the effective destroy work. Not sure why. * Fixes file upload. * Fixes dynamic comment state change. * Removes forgoten debug trace * Replaces the date picker with datetime picker. * Updating README and Publify version for 8.0.2 release Hans de Graaff (1): * Use a relative image path Matijs van Zuijlen (13): * Limit set of allowed comment parameters * Run db setup inside bundle on Travis CI * Upgrade to Rails 3.2.18 * Remove useless gems * Fix indenting * Remove useless #map * Make Travis CI validate the rendered feeds * Avoid symbolizing by stringifying instead * Ensure RSpec 2.99 gets installed * Avoid clearing cache that may not be there in test * Balance tags of Bootstrap theme layout * CarrierWave automatically sets the mime type now * Fix Twitter gem deprecations Soon Van (1): * Typos and capitals in README [ci skip] Thomas Lecavelier (2): * Fix [#423]: stutter article content * Excerpt is not editable anymore. Drop it even for `full_article_content` partial. Close [#423] * Upgrade to Rails 3.2.17 Tor Helland (2): * Synchronised Norwegian translation with the English en.yml. * Revised all of Norwegian translation. Yannick Francois (11): * Just a little cleanup of a spec * Add humans txt settingso * Really write to humans txt file * Add a text controller to manage humans.txt (and other later) * Robots.txt generated by a controller. * Refactor duplication in notes controller * Just clean syntax on specs * Prepare rspec 3 by removing deprecations * Put back condition on cache for archives page * Clean code around notes show and url helper * Fix note helper. Back in application helper. slainer68 (1): * Travis build on 2.0 and 2.1 [#423]: https://github.com/publify/publify/issues/423 ## 8.0.1 * [#398]: the user-style.css stylesheet is not loaded in the Bootstrap theme * [#399]: the note style is not applied. * [#402], [#410], [#411]: deployment crashes on Heroku (thank you @slainer68 for fixing that). * [#412]: the editor locally saves the content of the edited note, which means it reloads it when you edit another note, overwriting the legit content. [#398]: https://github.com/publify/publify/issues/398 [#399]: https://github.com/publify/publify/issues/399 [#402]: https://github.com/publify/publify/issues/402 [#410]: https://github.com/publify/publify/issues/410 [#411]: https://github.com/publify/publify/issues/411 [#412]: https://github.com/publify/publify/issues/412 ## Publify 8.0 It's been 5 months since Publify 7.1, and considering the figures, Publify 8.0 is the biggest release we ever pushed in 9 years: 474 commits, 71 issues closed, 8 contributors, 567 files changed, 60,767 additions and 45,166 deletions. But you probably don't care about numbers that much, except if you're wondering whether or not the project is till alive. TL; DR: it is. The project itself has known one big change, [moving from Fred's personal Github account to a dedicated organization][1]. We have been thinking about it for a while, and we believe it's the best we could do for Publify. ### Simpler, better, faster Last summer, [we started to rethink what we wanted Publify to be][2]. At a time where online publishing is more or less split between Wordpress, hosted platforms and static engines, being "only" a blogging platform had no meaning anymore. We started to extend publishing capabilities, choosing Twitter pushed short notes as a first step before we add more content type. This led to Publify 7.0, and once again we knew it was the way to go. Before adding these feature, we wanted Publify 8.0 to rebuild the whole user experience. It had to be simpler, clearer and better, far from the MS Word 97 style that prevails in Web publishing since more than 10 years. This meant a simpler interface with a single, smaller menu, getting out of the old create / read / update / delete scheme when possible, merging some sections and finally removing lots of things. This also means using the most of large screens capabilities, using _responsive_ layouts as much as we could, even though it made the job more difficult at some point. The editor, it has been completely revamped, following the way opened by both Medium and Ghost. We've pushed aside everything that may distract you from writing. The post settings are 1 click away from the editor so you won't feel lost anyway. We know how much work is left to get a really classy tool, but we're working on it. The notes have got improvement. When replying to a tweet, Publify now displays the original tweet so readers can keep the context this was done. Users profiles have been improved to. Each user now has its own detailed page with avatar, contact links, short bio and indeed the published content. ### Missing in action The old categories VS tags separation is no more. We merged the first into the seconds as a strict categorization has no real meaning on most blogs. Don't worry about your URLs, we took care of everything, eventually creating the redirects you needed. The _excerpt_ has been removed. Excerpt was meant to display a different content on the listing page and on the post itself. It was an interesting feature, but only a handful of people, if none was using it, and it made the editor more complicated than necessary. The old [Typographic theme][3] is not part of the core anymore. It has moved to its own project and will still be maintained. The old XMLRPC backend has been discontinued. This means Publify does not support desktop clients anymore. This choice has been motivated by the fact that the APIs it was relying had not been updated for 10 years, and that most desktop editors are not maintained anymore either. Web browsers capabilities have evolved, and you can now have a fairly decent editor with local saving without the need of a desktop application. ### Under the hood Publify has been around for 9 years now. Rails was not 1.0 yet, and some of our code was older than you can ever imagine. Publify 8.0 got rid of most of that legacy code. The old Prototype based helpers that made Rails famous back then left the building. Prototype itself has finally been replaced by Jquery, and Rails i18n allowed the _Globalize_ based translation system to enjoy a deserved retirement. Most helpers have been removed too, as most of them were only used in one place. This should not affect you unless you're running custom themes and plugins. If so, have a look at the Bootstrap theme to see how we're now working. That's all folks, you can now download Publify, or give it a try on [our demo platform][4]. [1]: http://t37.net/here-comes-the-time-to-hand-over-your-open-source-project.html [2]: http://t37.net/is-coding-a-blogging-engine-still-worth-the-effort-in-2013-and-other-thoughts-about-content-publishing-tools.html [3]: https://github.com/publify/typographic [4]: https://demo-publify.herokuapp.com/ ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Publify We welcome contributions to Publify. Please follow the guidelines below to help the process of handling issues and pull requests go smoothly. ## Issues When creating an issue, please provide as much information as possible, and follow the guidelines below to make it easier for us to figure out what's going on. If you miss any of these points we will probably ask you to improve the ticket. - Include a clear title describing the problem - Describe what you are trying to achieve - Describe what you did, including the URLs of the pages you visited - Describe what you expected to happen - Describe what happened instead, including any relevant output, error logs or screenshots - State the version of Publify you are using - Use [code blocks](https://github.github.com/gfm/#fenced-code-blocks) to format any code and output in your ticket to make it readable. ## Pull Requests We welcome pull requests. Please check first if the problem you're solving is fixed in master, or if the desired feature is already in development. Before starting a large feature, please open a ticket first so we can discuss it. When sending a pull request, please follow **all of** the instructions below: - Make sure `bundle exec rake` runs without reporting any failures. See *Testing your changes* below for more details. - Add tests for your feature. Otherwise, I can't see if it works or if I break it later. - Create a separate branch for your feature based off of latest master. - Write [good commit messages](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). - Do not include changes that are irrelevant to your feature in the same commit. - Keep an eye on the build results in GitHub Actions. If the build fails and it seems due to your changes, please update your pull request with a fix. If you're not sure how to test the problem, or what the best solution is, or get stuck on something else, please open an issue first so that we can discuss the best approach. ### Testing your changes You can run the test suite with the latest version of all dependencies by running the following: - Run `bundle install` if you haven't done so already, or `bundle update` to update the dependencies - Run `bundle exec rake` to run the tests To run the tests suite for **TODO** a particular version of Rails, use [appraisal](https://github.com/thoughtbot/appraisal). For example, to run the specs with Rails 6.1, run `appraisal rails_6_1 rake`. See appraisal's documentation for details. ### The review process - We will try to review your pull request as soon as possible but we can make no guarantees. Feel free to ping us now and again. - I will probably ask you to rebase your branch on current master at some point during the review process. If you are unsure how to do this, [this in-depth guide](https://git-rebase.io/) should help out. - If you have any unclear commit messages, work-in-progress commits, or commits that just fix a mistake in a previous commit, we will ask you to clean up the history. Again, [the git-rebase guide](https://git-rebase.io/) should help out. Note that we will not squash-merge pull requests, since that results in a loss of history. - **At the end of the review process we may still choose not to merge your pull request.** For example, this could happen if we decide the proposed feature should not be part of Publify, or if the technical implementation does not match where I want to go with the architecture of the project. - We will generally not merge any pull requests that make the build fail, unless it's very clearly not related to the changes in the pull request. ================================================ FILE: Gemfile ================================================ # frozen_string_literal: true source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem "rails", ["~> 7.1.5", ">= 7.1.5.2"] gem "mysql2" gem "pg" gem "sqlite3", "~> 2.9.4" # Store sessions in the database gem "activerecord-session_store", "~> 2.2.0" # Use Puma as the app server gem "puma", "~> 8.0" gem "publify_amazon_sidebar", github: "publify/publify_amazon_sidebar" gem "publify_core", github: "publify/publify_core" gem "publify_textfilter_code", github: "publify/publify_textfilter_code" # Use Uglifier as compressor for JavaScript assets gem "uglifier", ">= 1.3.0" # Needed for the lightbox and flickr text filters gem "flickraw", "~> 0.9.8", require: false gem "non-digest-assets", "~> 2.0" gem "rake", "~> 13.0" gem "reverse_markdown", "~> 3.0" # Force newer sprockets gem "sprockets", "~> 4.0" # Allow throttling requests gem "rack-attack", "~> 6.5" gem "net-smtp", "~> 0.5.0" group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem "byebug", platforms: [:mri, :windows] gem "capybara", "~> 3.9" gem "factory_bot", "~> 6.2" gem "i18n-tasks", "~> 1.1.0", require: false gem "rspec-rails", "~> 7.1" gem "rubocop", "~> 1.86.1", require: false gem "rubocop-capybara", "~> 2.23.0", require: false gem "rubocop-factory_bot", "~> 2.28.0", require: false gem "rubocop-performance", "~> 1.26.0", require: false gem "rubocop-rails", "~> 2.35.0", require: false gem "rubocop-rspec", "~> 3.9.0", require: false gem "rubocop-rspec_rails", "~> 2.32.0", require: false gem "simplecov", "~> 0.22.0", require: false end group :development do # Access an interactive console on exception pages or by calling 'console' # anywhere in the code. gem "web-console", "~> 4.1" gem "listen", "~> 3.3" # Display performance information such as SQL time and flame graphs for each # request in your browser. # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md gem "rack-mini-profiler", "~> 4.0" # Spring speeds up development by keeping your application running in the # background. Read more: https://github.com/rails/spring gem "spring", "~> 4.5.0" gem "spring-commands-rspec", "~> 1.0" gem "fast_stack" gem "flamegraph" gem "memory_profiler" gem "stackprof" end group :test do # TODO: Remove when upgrading rails and/or rspec-rails gem "drb", "~> 2.2.3" gem "mutex_m", "~> 0.3.0" gem "feedjira", "~> 4.0" gem "launchy", "~> 3.0" gem "rails-controller-testing", "~> 1.0.1" gem "shoulda-matchers", "~> 7.0" gem "timecop", "~> 0.9.1" gem "webmock", "~> 3.3" end # Install gems from each theme Dir.glob(File.join(File.dirname(__FILE__), "themes", "**", "Gemfile")) do |gemfile| eval(File.read(gemfile), binding) end ================================================ FILE: MIT-LICENSE ================================================ Copyright (c) 2005 Tobias Luetke Copyright (c) 2005-2013 Typo Developers & Contributors Copyright (c) 2013-2017 Publify Developers & Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Publify **The Ruby on Rails publishing software formerly known as Typo** [![Build status](https://github.com/publify/publify/actions/workflows/ruby.yml/badge.svg)](https://github.com/publify/publify/actions/workflows/ruby.yml) [![Code Climate](https://codeclimate.com/github/publify/publify.svg)](https://codeclimate.com/github/publify/publify) [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com) ## What's Publify? Publify is a simple but full featured web publishing software. It's built around a blogging engine and a small message system connected to Twitter. Publify follows the principles of the IndieWeb, which are self hosting your Web site, and Publish On your Own Site, Syndicate Everywhere. Publify has been around since 2004 and is the oldest Ruby on Rails open source project alive. ## Features - A classic multi user blogging engine - Short messages with a Twitter connection - Text filters (Markdown, SmartyPants, @mention to link, #hashtag to link) - A widgets system and a plugin API - Custom themes - Advanced SEO capabilities - Multilingual : Publify is (more or less) translated in English, French, German, Danish, Norwegian, Japanese, Hebrew, Simplified Chinese, Mexican Spanish, Italian, Lithuanian, Dutch, Polish, Romanian… ## Demo site You can give Publify a try at [the demo site](https://publify-demo.fly.dev/). The demo is reset every hour. ## Install ### Download You can download the latest Publify [stable release](https://github.com/publify/publify/releases/latest). If you want to run the master branch, you can [clone the Publify repository](https://github.com/publify/publify.git). However, random things may be broken there at any time, so tread carefully! **Running the master branch in production is not recommended!** ### Install Publify locally To install Publify you need the following: - CRuby (MRI) 2.5, 2.6 or 2.7 - Ruby on Rails 5.2.x - A database engine, MySQL, PgSQL or SQLite3 - A compatible JavaScript installation for asset compilation. See [the execjs readme](https://github.com/sstephenson/execjs#readme) for details. - ImageMagick (used by `mini_magick`). 1. Unzip Publify archive 2. Rename database.yml.yourEngine as database.yml 3. Edit database.yml to add your database name, login and password. ```bash $ bundle install $ rake db:setup $ rake db:migrate $ rake db:seed $ rake assets:precompile $ rails server ``` You can now launch you browser and access 127.0.0.1:3000. ### Install Publify on a server You can use your preferred installation method (e.g., Capistrano) to install Publify on a server. You will also need to set up the environment so it contains at least `SECRET_KEY_BASE`. Your web server may allow you to set this, or you can consider using a tool like `dotenv`. ### Install Publify on Heroku In order to install Publify on Heroku, you’ll need to do some minor tweaks. First of all, you need to set up Amazon S3 storage to be able to upload files on your blog. Set Heroku config vars. ```bash heroku config:set PROVIDER=AWS heroku config:set AWS_ACCESS_KEY_ID= heroku config:set AWS_SECRET_ACCESS_KEY= heroku config:set AWS_BUCKET= ``` Next, you need to update `Gemfile`. You should remove the `mysql2` and `sqlite3` gems, set the Ruby version, and add `rails_12factor`. The top of your `Gemfile` should look something like this: ```ruby source 'https://rubygems.org' ruby '2.7.4' # Or whichever version you're running gem 'pg' gem 'rails_12factor' gem 'rails', '~> 5.2.6' ``` Next, to regenerate the Gemfile.lock, run: ```bash bundle install ``` Commit your updated `Gemfile` and `Gemfile.lock`: ```bash git commit -am 'Update bundle for Heroku' ``` Create a file `Procfile` containing the following: ``` web: bundle exec puma -C config/puma.rb ``` Commit your new `Procfile`: ```bash git add Procfile git ci -m 'Tell Heroku how to run Rails' ``` You also need to set Rails' secret key base. Generate one using `rake secret`, then set the Heroku config var: ```bash heroku config:set SECRET_KEY_BASE= ``` Push the repository to Heroku. When deploying for the first time, Heroku will automatically add a Database plugin to your instance and links it to the application. After the first deployment, don't forget to run the database migration and seed. ```bash heroku run rake db:migrate db:seed ``` If application error has occurred after migration, you need to restart Heroku server. ```bash heroku restart ``` ## Resources - [Sidebar Plugins](https://github.com/publify/publify/wiki/Sidebar-plugins) - [In page Plugins](https://github.com/publify/publify/wiki/In-Page-Plugins) - [**Report a bug**](https://github.com/publify/publify/issues) - [**Frequently Asked Questions**](https://github.com/publify/publify/wiki/frequently-asked-questions) - [Publify on Twitter](https://twitter.com/getpublify) - IRC: \#publify on irc.freenode.net ## Maintainers ### Current Maintainers **Frédéric de Villamil** blog: http://t37.net **Matijs van Zuijlen** blog: http://www.matijs.net/blog/ **Thomas Lecavelier** blog: http://blog.ookook.fr/ **Yannick François** blog: http://elsif.fr ### Previous Maintainers & Notable Contributors **Cyril Mougel** blog: http://blog.shingara.fr **Davide D'Agostino** blog: http://www.lipsiasoft.com **Piers Cawley** blog: http://www.bofh.org.uk/ **Scott Laird** **Kevin Ballard** blog: kevin.sb.org **Patrick Lenz** **Seth Hall** And [many more cool people who’ve one way or another contributed to Publify](https://github.com/publify/publify/graphs/contributors). **Original Author: Tobias Luetke** blog: http://blog.leetsoft.com/ Enjoy, The Publify team ================================================ FILE: Rakefile ================================================ # frozen_string_literal: true # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. require File.expand_path("config/application", __dir__) Rails.application.load_tasks ================================================ FILE: TODO.txt ================================================ AdminSidebar: - On the fly save of params - CSS - Remove on staged/active sidebars - Maybe some animations ================================================ FILE: app/assets/config/manifest.js ================================================ ================================================ FILE: app/assets/images/.keep ================================================ ================================================ FILE: app/controllers/application_controller.rb ================================================ # frozen_string_literal: true class ApplicationController < ActionController::Base protect_from_forgery with: :exception end ================================================ FILE: app/helpers/theme_helper.rb ================================================ # frozen_string_literal: true module ThemeHelper # Adds per theme helpers if file exists. Ugly but at least it works. # Use: just add your methods in yourtheme/helpers/theme_helper.rb # If your theme is a plugin, it's better to just load relevant helpers in the # initialization code instead. Theme.find_all.each do |theme| filename = File.join(theme.path, "helpers", "theme_helper.rb") require filename if File.exist? filename end end ================================================ FILE: app/mailers/application_mailer.rb ================================================ # frozen_string_literal: true class ApplicationMailer < ActionMailer::Base end ================================================ FILE: app/models/application_record.rb ================================================ # frozen_string_literal: true class ApplicationRecord < ActiveRecord::Base self.abstract_class = true end ================================================ FILE: app/models/authors_sidebar.rb ================================================ # frozen_string_literal: true class AuthorsSidebar < Sidebar display_name "Authors" description "Displays a list of authors ordered by name with links to their" \ " articles and profile" setting :count, true, label: "Show articles number", input_type: :checkbox def authors @authors = User.where(state: "active").order(:name) end end SidebarRegistry.register_sidebar AuthorsSidebar ================================================ FILE: app/models/concerns/.keep ================================================ ================================================ FILE: app/models/livesearch_sidebar.rb ================================================ # frozen_string_literal: true class LivesearchSidebar < Sidebar description "Adds livesearch to your Publify blog" setting :title, "Search" end SidebarRegistry.register_sidebar LivesearchSidebar ================================================ FILE: app/models/notes_sidebar.rb ================================================ # frozen_string_literal: true class NotesSidebar < Sidebar description "Displays the latest notes" setting :title, "Notes" setting :count, 5, label: "Number of notes" attr_accessor :notes def parse_request(_contents, params) @notes = Note.published.page(params[:page]).per(count) end end SidebarRegistry.register_sidebar NotesSidebar ================================================ FILE: app/models/popular_sidebar.rb ================================================ # frozen_string_literal: true class PopularSidebar < Sidebar description "Displays the most popular posts" setting :title, "Most popular" setting :count, 5, label: "Number articles" attr_accessor :popular def parse_request(_contents, _params) @popular = Article.bestof.limit(5) end end SidebarRegistry.register_sidebar PopularSidebar ================================================ FILE: app/models/xml_sidebar.rb ================================================ # frozen_string_literal: true class XmlSidebar < Sidebar display_name "XML Syndication" description "RSS and Atom feeds" setting :articles, true, input_type: :checkbox setting :feedback, true, input_type: :checkbox setting :article_comments, true, input_type: :checkbox setting :tag_feeds, false, input_type: :checkbox setting :format, "atom", input_type: :radio, choices: [%w(rss RSS), %w(atom Atom)] def format_strip strip_format = +format strip_format ||= +"atom" strip_format.gsub!(/\d+/, "") strip_format.gsub!("1.0", "") strip_format.gsub!("2.0", "") strip_format end end SidebarRegistry.register_sidebar XmlSidebar ================================================ FILE: app/views/authors_sidebar/_content.html.erb ================================================ <% cache sidebar.authors do %> <% unless sidebar.authors.blank? %> <% end %> <% end %> ================================================ FILE: app/views/livesearch_sidebar/_content.html.erb ================================================ ================================================ FILE: app/views/notes_sidebar/_content.html.erb ================================================ <% unless sidebar.notes.blank? %> <% end %> ================================================ FILE: app/views/popular_sidebar/_content.html.erb ================================================
================================================ FILE: app/views/xml_sidebar/_content.html.erb ================================================ ================================================ FILE: bin/bundle ================================================ #!/usr/bin/env ruby ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) load Gem.bin_path('bundler', 'bundle') ================================================ FILE: bin/i18n-tasks ================================================ #!/usr/bin/env ruby begin load File.expand_path('../spring', __FILE__) rescue LoadError => e raise unless e.message.include?('spring') end require 'bundler/setup' require 'logger' load Gem.bin_path('i18n-tasks', 'i18n-tasks') ================================================ FILE: bin/rails ================================================ #!/usr/bin/env ruby APP_PATH = File.expand_path("../config/application", __dir__) require_relative "../config/boot" require "rails/commands" ================================================ FILE: bin/rake ================================================ #!/usr/bin/env ruby require_relative "../config/boot" require "rake" Rake.application.run ================================================ FILE: bin/rspec ================================================ #!/usr/bin/env ruby begin load File.expand_path('../spring', __FILE__) rescue LoadError => e raise unless e.message.include?('spring') end require 'bundler/setup' load Gem.bin_path('rspec-core', 'rspec') ================================================ FILE: bin/setup ================================================ #!/usr/bin/env ruby require "fileutils" # path to your application root. APP_ROOT = File.expand_path("..", __dir__) def system!(*args) system(*args, exception: true) end FileUtils.chdir APP_ROOT do # This script is a way to set up or update your development environment automatically. # This script is idempotent, so that you can run it at any time and get an expectable outcome. # Add necessary setup steps to this file. puts "== Installing dependencies ==" system! "gem install bundler --conservative" system("bundle check") || system!("bundle install") # puts "\n== Copying sample files ==" # unless File.exist?("config/database.yml") # FileUtils.cp "config/database.yml.sample", "config/database.yml" # end puts "\n== Preparing database ==" system! "bin/rails db:prepare" puts "\n== Removing old logs and tempfiles ==" system! "bin/rails log:clear tmp:clear" puts "\n== Restarting application server ==" system! "bin/rails restart" end ================================================ FILE: bin/update ================================================ #!/usr/bin/env ruby require 'fileutils' include FileUtils # path to your application root. APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") end chdir APP_ROOT do # This script is a way to update your development environment automatically. # Add necessary update steps to this file. puts '== Installing dependencies ==' system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') # Install JavaScript dependencies if using Yarn # system('bin/yarn') puts "\n== Updating database ==" system! 'bin/rails db:migrate' puts "\n== Removing old logs and tempfiles ==" system! 'bin/rails log:clear tmp:clear' puts "\n== Restarting application server ==" system! 'bin/rails restart' end ================================================ FILE: bin/yarn ================================================ #!/usr/bin/env ruby APP_ROOT = File.expand_path('..', __dir__) Dir.chdir(APP_ROOT) do yarn = ENV["PATH"].split(File::PATH_SEPARATOR). select { |dir| File.expand_path(dir) != __dir__ }. product(["yarn", "yarn.cmd", "yarn.ps1"]). map { |dir, file| File.expand_path(file, dir) }. find { |file| File.executable?(file) } if yarn exec yarn, *ARGV else $stderr.puts "Yarn executable was not detected in the system." $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" exit 1 end end ================================================ FILE: config/application.rb ================================================ # frozen_string_literal: true require_relative "boot" require "rails/all" # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module Publify class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 7.0 # Please, add to the `ignore` list any other `lib` subdirectories that do # not contain `.rb` files, or that should not be reloaded or eager loaded. # Common ones are `templates`, `generators`, or `middleware`, for example. config.autoload_lib(ignore: %w(assets tasks generators)) config.add_autoload_paths_to_load_path = false # Configuration for the application, engines, and railties goes here. # # These settings can be overridden in specific environments using the files # in config/environments, which are processed later. # # config.time_zone = "Central Time (US & Canada)" # config.eager_load_paths << Rails.root.join("extras") config.action_dispatch.cookies_serializer = :hybrid end Theme.register_themes Rails.root.join("themes") end ================================================ FILE: config/boot.rb ================================================ # frozen_string_literal: true ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) require "bundler/setup" # Set up gems listed in the Gemfile. ================================================ FILE: config/cable.yml ================================================ development: adapter: async test: adapter: test production: adapter: redis url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> channel_prefix: publify_production ================================================ FILE: config/database.yml.mysql ================================================ login: &login adapter: mysql2 host: 127.0.0.1 username: root password: mysql port: 3306 development: database: publify_dev <<: *login test: database: publify_test <<: *login production: database: publify <<: *login ================================================ FILE: config/database.yml.postgresql ================================================ login: &login adapter: postgresql host: localhost port: 5432 username: postgres password: postgres connection: &connection encoding: unicode pool: 5 development: database: publify_dev <<: *login <<: *connection test: database: publify_test <<: *login <<: *connection production: database: publify <<: *login <<: *connection ================================================ FILE: config/environment.rb ================================================ # frozen_string_literal: true # Load the Rails application. require_relative "application" # Initialize the Rails application. Rails.application.initialize! ================================================ FILE: config/environments/development.rb ================================================ # frozen_string_literal: true require "active_support/core_ext/integer/time" Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded any time # it changes. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.enable_reloading = true # Do not eager load code on boot. config.eager_load = false # Show full error reports. config.consider_all_requests_local = true # Enable server timing config.server_timing = true # Enable/disable caching. By default caching is disabled. # Run rails dev:cache to toggle caching. if Rails.root.join("tmp/caching-dev.txt").exist? config.action_controller.perform_caching = true config.action_controller.enable_fragment_cache_logging = true config.cache_store = :memory_store config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{2.days.to_i}" } else config.action_controller.perform_caching = false config.cache_store = :null_store end # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = :local # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false config.action_mailer.perform_caching = false # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log # Raise exceptions for disallowed deprecations. config.active_support.disallowed_deprecation = :raise # Tell Active Support which deprecation messages to disallow. config.active_support.disallowed_deprecation_warnings = [] # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load # Highlight code that triggered database queries in logs. config.active_record.verbose_query_logs = true # Highlight code that enqueued background job in logs. config.active_job.verbose_enqueue_logs = true # Suppress logger output for asset requests. config.assets.quiet = true # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true # Annotate rendered view with file names. # config.action_view.annotate_rendered_view_with_filenames = true # Uncomment if you wish to allow Action Cable access from any origin. # config.action_cable.disable_request_forgery_protection = true # Raise error when a before_action's only/except options reference missing actions config.action_controller.raise_on_missing_callback_actions = true end ================================================ FILE: config/environments/production.rb ================================================ # frozen_string_literal: true require "active_support/core_ext/integer/time" Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. config.enable_reloading = false # Eager load code on boot. This eager loads most of Rails and # your application in memory, allowing both threaded web servers # and those relying on copy on write to perform better. # Rake tasks automatically ignore this option for performance. config.eager_load = true # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true # Ensures that a master key has been made available in # ENV["RAILS_MASTER_KEY"], config/master.key, or an environment key such as # config/credentials/production.key. This key is used to decrypt credentials # (and other encrypted files). # config.require_master_key = true # Disable serving static files from `public/`, relying on NGINX/Apache to do so instead. # config.public_file_server.enabled = false # Compress CSS using a preprocessor. # config.assets.css_compressor = :sass # Do not fall back to assets pipeline if a precompiled asset is missed. config.assets.compile = false # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.asset_host = "http://assets.example.com" # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX # Store uploaded files on the local file system (see config/storage.yml for options). config.active_storage.service = :local # Mount Action Cable outside main process or domain. # config.action_cable.mount_path = nil # config.action_cable.url = "wss://example.com/cable" # config.action_cable.allowed_request_origins = # [ "http://example.com", /http:\/\/example.*/ ] # Assume all access to the app is happening through a SSL-terminating reverse proxy. # Can be used together with config.force_ssl for Strict-Transport-Security # and secure cookies. # config.assume_ssl = true # Force all access to the app over SSL, use Strict-Transport-Security, and # use secure cookies. config.force_ssl = true # Log to STDOUT by default config.logger = ActiveSupport::Logger.new($stdout) .tap { |logger| logger.formatter = Logger::Formatter.new } .then { |logger| ActiveSupport::TaggedLogging.new(logger) } # Prepend all log lines with the following tags. config.log_tags = [:request_id] # "info" includes generic and useful information about system operation, but # avoids logging too much information to avoid inadvertent exposure of # personally identifiable information (PII). If you want to log everything, # set the level to "debug". config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") # Use a different cache store in production. # config.cache_store = :mem_cache_store # Use a real queuing backend for Active Job (and separate queues per environment). # config.active_job.queue_adapter = :resque # config.active_job.queue_name_prefix = "publify_production" config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to # raise delivery errors. # config.action_mailer.raise_delivery_errors = false # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true # Don't log any deprecations. config.active_support.report_deprecations = false # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false # Enable DNS rebinding protection and other `Host` header attacks. # config.hosts = [ # "example.com", # Allow requests from example.com # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` # ] # Skip DNS rebinding protection for the default health check endpoint. # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } end ================================================ FILE: config/environments/test.rb ================================================ # frozen_string_literal: true require "active_support/core_ext/integer/time" # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # While tests run files are not watched, reloading is not necessary. config.enable_reloading = false # Eager loading loads your entire application. When running a single test locally, # this is usually not necessary, and can slow down your test suite. However, it's # recommended that you enable it in continuous integration systems to ensure eager # loading is working properly before deploying your code. config.eager_load = ENV["CI"].present? # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{1.hour.to_i}" } # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false config.cache_store = :null_store # Render exception templates for rescuable exceptions and raise for other exceptions. config.action_dispatch.show_exceptions = :rescuable # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false # Store uploaded files on the local file system in a temporary directory. config.active_storage.service = :test config.action_mailer.perform_caching = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr # Raise exceptions for disallowed deprecations. config.active_support.disallowed_deprecation = :raise # Tell Active Support which deprecation messages to disallow. config.active_support.disallowed_deprecation_warnings = [] # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true # Annotate rendered view with file names. # config.action_view.annotate_rendered_view_with_filenames = true # Raise error when a before_action's only/except options reference missing actions config.action_controller.raise_on_missing_callback_actions = true end ================================================ FILE: config/filemanager.yml ================================================ image.type: jpg gif png flash.type: swf movie.type: mov avi wmv rm.type: rm rmvb plain.type: txt ini inf html htm rb jsp php asp office.type: doc dot xls xla ppt pps ppz pot hlp chm ods word.type: doc dot excel.type: xls xla ods ppt.type: ppt pps ppz pot help.type: hlp chm none.type: rar zip resources.url: /files resources.path: <%= ::Rails.root.to_s %>/public/files encoding.to: UTF-8 encoding.from: UTF-8 temp.dir: ================================================ FILE: config/i18n-tasks.yml ================================================ # i18n-tasks finds and manages missing and unused translations: https://github.com/glebm/i18n-tasks # The "main" locale. base_locale: en # <% publify_core_path = %x[bundle info --path publify_core].chomp %> # <% rails_i18n_path = %x[bundle info --path rails-i18n].chomp %> # Read and write translations. data: # Locale files or `File.find` patterns where translations are read from: read: - config/locales/%{locale}.yml - config/locales/sidebars.%{locale}.yml # External locale data (e.g. gems). # This data is not considered unused and is never written to. external: - "<%= rails_i18n_path %>/rails/locale/%{locale}.yml" - "<%= publify_core_path %>/config/locales/%{locale}.yml" - "<%= publify_core_path %>/config/locales/sidebars.%{locale}.yml" # Locale files to write new keys to, based on a list of key pattern => file # rules. Matched from top to bottom: # `i18n-tasks normalize -p` will force move the keys according to these rules write: - ['*_sidebar.*', 'config/locales/sidebars.%{locale}.yml'] - config/locales/%{locale}.yml # Find translate calls search: ## Paths or `File.find` patterns to search in: paths: - app/ - lib/ - themes/bootstrap-2/ - <%= publify_core_path %>/app/ ## Root directories for relative keys resolution. relative_roots: - app/controllers - app/helpers - app/mailers - app/presenters - app/views - themes/bootstrap-2/views - <%= publify_core_path %>/app/views/ ## Do not consider these keys missing: ignore_missing: - 'devise.*' ## Consider these keys used: ignore_unused: - 'time.formats.*' - 'layouts.default.designed_by' - 'date.*' - 'activerecord.{attributes,errors,models}.*' ================================================ FILE: config/initializers/application_controller_renderer.rb ================================================ # frozen_string_literal: true # Be sure to restart your server when you modify this file. # ActiveSupport::Reloader.to_prepare do # ApplicationController.renderer.defaults.merge!( # http_host: 'example.org', # https: false # ) # end ================================================ FILE: config/initializers/assets.rb ================================================ # frozen_string_literal: true # Be sure to restart your server when you modify this file. # Version of your assets, change this if you want to expire all your assets. Rails.application.config.assets.version = "1.0" # Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in the app/assets # folder are already added. # Rails.application.config.assets.precompile += %w( admin.js admin.css ) ================================================ FILE: config/initializers/backtrace_silencers.rb ================================================ # frozen_string_literal: true # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't # wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) } # You can also remove all the silencers if you're trying to debug a problem # that might stem from framework code by setting BACKTRACE=1 before calling # your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'". Rails.backtrace_cleaner.remove_silencers! if ENV["BACKTRACE"] ================================================ FILE: config/initializers/carrierwave.rb ================================================ # frozen_string_literal: true if Rails.env.in?(%(test cucumber)) CarrierWave.configure do |config| config.storage = :file config.enable_processing = false end else CarrierWave.configure do |config| if ENV["provider"] == "AWS" config.storage = :fog config.fog_credentials = { provider: "AWS", aws_access_key_id: ENV.fetch("aws_access_key_id"), aws_secret_access_key: ENV.fetch("aws_secret_access_key") } config.fog_directory = ENV.fetch("aws_bucket") config.fog_public = true config.fog_attributes = { "Cache-Control" => "max-age=315576000" } else config.storage = :file config.permissions = 0o666 config.directory_permissions = 0o777 end end end ================================================ FILE: config/initializers/content_security_policy.rb ================================================ # frozen_string_literal: true # Be sure to restart your server when you modify this file. # Define an application-wide content security policy. # See the Securing Rails Applications Guide for more information: # https://guides.rubyonrails.org/security.html#content-security-policy-header # Rails.application.configure do # config.content_security_policy do |policy| # policy.default_src :self, :https # policy.font_src :self, :https, :data # policy.img_src :self, :https, :data # policy.object_src :none # policy.script_src :self, :https # policy.style_src :self, :https # # Specify URI for violation reports # # policy.report_uri "/csp-violation-report-endpoint" # end # # # Generate session nonces for permitted importmap, inline scripts, and inline styles. # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } # config.content_security_policy_nonce_directives = %w(script-src style-src) # # # Report violations without enforcing the policy. # # config.content_security_policy_report_only = true # end ================================================ FILE: config/initializers/cookies_serializer.rb ================================================ # frozen_string_literal: true # Be sure to restart your server when you modify this file. # Specify a serializer for the signed and encrypted cookie jars. # Valid options are :json, :marshal, and :hybrid. Rails.application.config.action_dispatch.cookies_serializer = :json ================================================ FILE: config/initializers/filter_parameter_logging.rb ================================================ # frozen_string_literal: true # Be sure to restart your server when you modify this file. # Configure parameters to be partially matched (e.g. passw matches password) # and filtered from the log file. # Use this to limit dissemination of sensitive information. # See the ActiveSupport::ParameterFilter documentation for supported notations # and behaviors. Rails.application.config.filter_parameters += [ :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn ] ================================================ FILE: config/initializers/flickr.rb ================================================ # frozen_string_literal: true require "flickraw" # Prepare needed FlickRaw methods FlickRaw::Flickr.build(["flickr.photos.getInfo", "flickr.photos.getSizes"]) # Please get your own API key for setting up this. # You can request it at # https://www.flickr.com/services/api/misc.api_keys.html FlickRaw.api_key = "" FlickRaw.shared_secret = "" ================================================ FILE: config/initializers/inflections.rb ================================================ # frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections # are locale specific, and you may define rules for as many different # locales as you wish. All of these examples are active by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.plural /^(ox)$/i, "\\1en" # inflect.singular /^(ox)en/i, "\\1" # inflect.irregular "person", "people" # inflect.uncountable %w( fish sheep ) # end # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.acronym "RESTful" # end ================================================ FILE: config/initializers/mime_types.rb ================================================ # frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf ================================================ FILE: config/initializers/new_framework_defaults_7_1.rb ================================================ # frozen_string_literal: true # Be sure to restart your server when you modify this file. # # This file eases your Rails 7.1 framework defaults upgrade. # # Uncomment each configuration one by one to switch to the new default. # Once your application is ready to run with all new defaults, you can remove # this file and set the `config.load_defaults` to `7.1`. # # Read the Guide for Upgrading Ruby on Rails for more info on each option. # https://guides.rubyonrails.org/upgrading_ruby_on_rails.html ### # No longer add autoloaded paths into `$LOAD_PATH`. This means that you won't be able # to manually require files that are managed by the autoloader, which you shouldn't do anyway. # # This will reduce the size of the load path, making `require` faster if you don't use bootsnap, or reduce the size # of the bootsnap cache if you use it. # # To set this configuration, add the following line to `config/application.rb` (NOT this file): # config.add_autoload_paths_to_load_path = false ### # Remove the default X-Download-Options headers since it is used only by Internet Explorer. # If you need to support Internet Explorer, add back `"X-Download-Options" => "noopen"`. #++ Rails.application.config.action_dispatch.default_headers = { "X-Frame-Options" => "SAMEORIGIN", "X-XSS-Protection" => "0", "X-Content-Type-Options" => "nosniff", "X-Permitted-Cross-Domain-Policies" => "none", "Referrer-Policy" => "strict-origin-when-cross-origin" } ### # Do not treat an `ActionController::Parameters` instance # as equal to an equivalent `Hash` by default. #++ Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality = false ### # Active Record Encryption now uses SHA-256 as its hash digest algorithm. # # There are 3 scenarios to consider. # # 1. If you have data encrypted with previous Rails versions, and you have # +config.active_support.key_generator_hash_digest_class+ configured as SHA1 (the default # before Rails 7.0), you need to configure SHA-1 for Active Record Encryption too: #++ # Rails.application.config.active_record.encryption.hash_digest_class = OpenSSL::Digest::SHA1 # # 2. If you have +config.active_support.key_generator_hash_digest_class+ configured as SHA256 (the new default # in 7.0), then you need to configure SHA-256 for Active Record Encryption: #++ # Rails.application.config.active_record.encryption.hash_digest_class = OpenSSL::Digest::SHA256 # # 3. If you don't currently have data encrypted with Active Record encryption, you can disable this setting to # configure the default behavior starting 7.1+: #++ Rails.application.config.active_record.encryption.support_sha1_for_non_deterministic_encryption = false ### # No longer run after_commit callbacks on the first of multiple Active Record # instances to save changes to the same database row within a transaction. # Instead, run these callbacks on the instance most likely to have internal # state which matches what was committed to the database, typically the last # instance to save. #++ Rails.application.config.active_record.run_commit_callbacks_on_first_saved_instances_in_transaction = false ### # Configures SQLite with a strict strings mode, which disables double-quoted string literals. # # SQLite has some quirks around double-quoted string literals. # It first tries to consider double-quoted strings as identifier names, but if they don't exist # it then considers them as string literals. Because of this, typos can silently go unnoticed. # For example, it is possible to create an index for a non existing column. # See https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted for more details. #++ Rails.application.config.active_record.sqlite3_adapter_strict_strings_by_default = true ### # Disable deprecated singular associations names. #++ Rails.application.config.active_record.allow_deprecated_singular_associations_name = false ### # Enable the Active Job `BigDecimal` argument serializer, which guarantees # roundtripping. Without this serializer, some queue adapters may serialize # `BigDecimal` arguments as simple (non-roundtrippable) strings. # # When deploying an application with multiple replicas, old (pre-Rails 7.1) # replicas will not be able to deserialize `BigDecimal` arguments from this # serializer. Therefore, this setting should only be enabled after all replicas # have been successfully upgraded to Rails 7.1. #++ # Rails.application.config.active_job.use_big_decimal_serializer = true ### # Specify if an `ArgumentError` should be raised if `Rails.cache` `fetch` or # `write` are given an invalid `expires_at` or `expires_in` time. # Options are `true`, and `false`. If `false`, the exception will be reported # as `handled` and logged instead. #++ Rails.application.config.active_support.raise_on_invalid_cache_expiration_time = true ### # Specify whether Query Logs will format tags using the SQLCommenter format # (https://open-telemetry.github.io/opentelemetry-sqlcommenter/), or using the legacy format. # Options are `:legacy` and `:sqlcommenter`. #++ Rails.application.config.active_record.query_log_tags_format = :sqlcommenter ### # Specify the default serializer used by `MessageEncryptor` and `MessageVerifier` # instances. # # The legacy default is `:marshal`, which is a potential vector for # deserialization attacks in cases where a message signing secret has been # leaked. # # In Rails 7.1, the new default is `:json_allow_marshal` which serializes and # deserializes with `ActiveSupport::JSON`, but can fall back to deserializing # with `Marshal` so that legacy messages can still be read. # # In Rails 7.2, the default will become `:json` which serializes and # deserializes with `ActiveSupport::JSON` only. # # Alternatively, you can choose `:message_pack` or `:message_pack_allow_marshal`, # which serialize with `ActiveSupport::MessagePack`. `ActiveSupport::MessagePack` # can roundtrip some Ruby types that are not supported by JSON, and may provide # improved performance, but it requires the `msgpack` gem. # # For more information, see # https://guides.rubyonrails.org/v7.1/configuring.html#config-active-support-message-serializer # # If you are performing a rolling deploy of a Rails 7.1 upgrade, wherein servers # that have not yet been upgraded must be able to read messages from upgraded # servers, first deploy without changing the serializer, then set the serializer # in a subsequent deploy. #++ Rails.application.config.active_support.message_serializer = :json_allow_marshal ### # Enable a performance optimization that serializes message data and metadata # together. This changes the message format, so messages serialized this way # cannot be read by older versions of Rails. However, messages that use the old # format can still be read, regardless of whether this optimization is enabled. # # To perform a rolling deploy of a Rails 7.1 upgrade, wherein servers that have # not yet been upgraded must be able to read messages from upgraded servers, # leave this optimization off on the first deploy, then enable it on a # subsequent deploy. #++ Rails.application.config.active_support.use_message_serializer_for_metadata = true ### # Set the maximum size for Rails log files. # # `config.load_defaults 7.1` does not set this value for environments other than # development and test. #++ Rails.application.config.log_file_size = 100 * 1024 * 1024 if Rails.env.local? ### # Enable raising on assignment to attr_readonly attributes. The previous # behavior would allow assignment but silently not persist changes to the # database. #++ Rails.application.config.active_record.raise_on_assign_to_attr_readonly = true ### # Enable validating only parent-related columns for presence when the parent is mandatory. # The previous behavior was to validate the presence of the parent record, which performed an extra query # to get the parent every time the child record was updated, even when parent has not changed. #++ Rails.application.config.active_record.belongs_to_required_validates_foreign_key = false ### # Enable precompilation of `config.filter_parameters`. Precompilation can # improve filtering performance, depending on the quantity and types of filters. #++ Rails.application.config.precompile_filter_parameters = true ### # Enable before_committed! callbacks on all enrolled records in a transaction. # The previous behavior was to only run the callbacks on the first copy of a record # if there were multiple copies of the same record enrolled in the transaction. #++ Rails.application.config.active_record.before_committed_on_all_records = true ### # Disable automatic column serialization into YAML. # To keep the historic behavior, you can set it to `YAML`, however it is # recommended to explicitly define the serialization method for each column # rather than to rely on a global default. #++ Rails.application.config.active_record.default_column_serializer = nil ### # Enable a performance optimization that serializes Active Record models # in a faster and more compact way. # # To perform a rolling deploy of a Rails 7.1 upgrade, wherein servers that have # not yet been upgraded must be able to read caches from upgraded servers, # leave this optimization off on the first deploy, then enable it on a # subsequent deploy. #++ # Rails.application.config.active_record.marshalling_format_version = 7.1 ### # Run `after_commit` and `after_*_commit` callbacks in the order they are defined in a model. # This matches the behaviour of all other callbacks. # In previous versions of Rails, they ran in the inverse order. #++ Rails.application.config.active_record.run_after_transaction_callbacks_in_order_defined = true ### # Whether a `transaction` block is committed or rolled back when exited via `return`, `break` or `throw`. #++ Rails.application.config.active_record.commit_transaction_on_non_local_return = true ### # Controls when to generate a value for has_secure_token declarations. #++ Rails.application.config.active_record.generate_secure_token_on = :initialize ### # ** Please read carefully, this must be configured in config/application.rb ** # # Change the format of the cache entry. # # Changing this default means that all new cache entries added to the cache # will have a different format that is not supported by Rails 7.0 # applications. # # Only change this value after your application is fully deployed to Rails 7.1 # and you have no plans to rollback. # When you're ready to change format, add this to `config/application.rb` (NOT # this file): # config.active_support.cache_format_version = 7.1 ### # Configure Action View to use HTML5 standards-compliant sanitizers when they are supported on your # platform. # # `Rails::HTML::Sanitizer.best_supported_vendor` will cause Action View to use HTML5-compliant # sanitizers if they are supported, else fall back to HTML4 sanitizers. # # In previous versions of Rails, Action View always used `Rails::HTML4::Sanitizer` as its vendor. #++ Rails.application.config.action_view.sanitizer_vendor = Rails::HTML::Sanitizer.best_supported_vendor ### # Configure Action Text to use an HTML5 standards-compliant sanitizer when it is supported on your # platform. # # `Rails::HTML::Sanitizer.best_supported_vendor` will cause Action Text to use HTML5-compliant # sanitizers if they are supported, else fall back to HTML4 sanitizers. # # In previous versions of Rails, Action Text always used `Rails::HTML4::Sanitizer` as its vendor. #++ Rails.application.config.action_text.sanitizer_vendor = Rails::HTML::Sanitizer.best_supported_vendor ### # Configure the log level used by the DebugExceptions middleware when logging # uncaught exceptions during requests. #++ Rails.application.config.action_dispatch.debug_exception_log_level = :error ### # Configure the test helpers in Action View, Action Dispatch, and rails-dom-testing to use HTML5 # parsers. # # Nokogiri::HTML5 isn't supported on JRuby, so JRuby applications must set this to :html4. # # In previous versions of Rails, these test helpers always used an HTML4 parser. #++ Rails.application.config.dom_testing_default_html_version = :html5 ================================================ FILE: config/initializers/non_digest_assets.rb ================================================ # frozen_string_literal: true NonDigestAssets.asset_selectors = [/\.(?:svg|eot|woff|ttf)$/] ================================================ FILE: config/initializers/permissions_policy.rb ================================================ # frozen_string_literal: true # Be sure to restart your server when you modify this file. # Define an application-wide HTTP permissions policy. For further # information see: https://developers.google.com/web/updates/2018/06/feature-policy # Rails.application.config.permissions_policy do |policy| # policy.camera :none # policy.gyroscope :none # policy.microphone :none # policy.usb :none # policy.fullscreen :self # policy.payment :self, "https://secure.example.com" # end ================================================ FILE: config/initializers/rack_attack.rb ================================================ # frozen_string_literal: true # Throttle login attempts Rack::Attack.throttle("logins/ip", limit: 20, period: 1.hour) do |req| req.ip if req.post? && req.path.start_with?("/users/sign_in") end # Throttle password reset attempts Rack::Attack.throttle("password-reset-requests/ip", limit: 20, period: 1.hour) do |req| req.ip if req.post? && req.path.start_with?("/users/password") end ActiveSupport::Notifications .subscribe("rack.attack") do |_name, _start, _finish, _request_id, req| Rails.logger.info "Throttled #{req.env["rack.attack.match_discriminator"]}" end ================================================ FILE: config/initializers/recaptcha.rb ================================================ # frozen_string_literal: true Recaptcha.configure do |config| config.site_key = "YourAPIkeysHere_yyyyyyyyyyyyyyyyy" config.secret_key = "YourAPIkeysHere_xxxxxxxxxxxxxxxxx" end ================================================ FILE: config/initializers/session_store.rb ================================================ # frozen_string_literal: true # Be sure to restart your server when you modify this file. Rails.application.config.session_store(:active_record_store, key: "_publify_blog_session", secure: Rails.env.production?) ================================================ FILE: config/initializers/wrap_parameters.rb ================================================ # frozen_string_literal: true # Be sure to restart your server when you modify this file. # This file contains settings for ActionController::ParamsWrapper which # is enabled by default. # Enable parameter wrapping for JSON. You can disable this by setting :format # to an empty array. ActiveSupport.on_load(:action_controller) do wrap_parameters format: [:json] end # To enable root element in JSON for ActiveRecord objects. # ActiveSupport.on_load(:active_record) do # self.include_root_in_json = true # end ================================================ FILE: config/locales/ar.yml ================================================ --- ar: articles: comment_counter: comments: few: بعض التعليقات many: أكثر من تعليق one: 1 تعليق other: "%{count} تعليقات" two: تعليقان zero: لا توجد تعليقات comment_form: comment: تعليق email_address: عنوان البريد الإلكتروني your_website: موقعك meta: published_on_html: تم النشر في %{publish_date_and_time} بواسطة %{by}, علامات %{tags} read: add_me_to_twitter: تابعني على تويتر if_you_liked_this_article_you_can_html: لو أعجبتك هذه المقالة يمكنك %{add_twitter} trackbacks_for: التتبع لـ search: search_results_for: نتائج البحث لـ '%{query}' comments: comment: by: بواسطة layouts: default: archives: أرشيفات statuses: حالات pagination: next: القادم previous: السابق ================================================ FILE: config/locales/da.yml ================================================ --- da: articles: comment_counter: comments: one: 1 kommentar other: "%{count} kommentarer" zero: Ingen kommentarer comment_form: comment: Kommenter email_address: Email adresse your_website: Din hjemmeside meta: published_on_html: Udgivet %{publish_date_and_time} af %{by}, tags %{tags} read: add_me_to_twitter: Tilføj mig på Twitter if_you_liked_this_article_you_can_html: Hvis du kan lide denne artikel så %{add_twitter} trackbacks_for: Trackbacks for search: search_results_for: Søgeresultater for '%{query}' comments: comment: by: af layouts: default: archives: Arkiver statuses: Statuser pagination: next: Næste previous: Forrige ================================================ FILE: config/locales/de.yml ================================================ --- de: articles: comment_counter: comments: one: 1 kommentar other: "%{count} kommentare" zero: no kommentare comment_form: comment: comment email_address: Email address your_website: Your website meta: published_on_html: Published on %{publish_date_and_time} by %{by}, tags %{tags} read: add_me_to_twitter: add me to Twitter if_you_liked_this_article_you_can_html: If you liked this article you can %{add_twitter} trackbacks_for: Trackbacks for search: search_results_for: Search results for '%{query}' comments: comment: by: bei layouts: default: archives: Archives statuses: Statuses pagination: next: Next previous: Previous ================================================ FILE: config/locales/en.yml ================================================ --- en: articles: comment_counter: comments: one: 1 comment other: "%{count} comments" zero: no comments comment_form: comment: comment email_address: Email address your_website: Your website meta: published_on_html: Published on %{publish_date_and_time} by %{by}, tags %{tags} read: add_me_to_twitter: follow me on Twitter if_you_liked_this_article_you_can_html: If you liked this article you can %{add_twitter} trackbacks_for: Trackbacks for search: search_results_for: Search results for '%{query}' comments: comment: by: by layouts: default: archives: Archives statuses: Statuses pagination: next: Next previous: Previous ================================================ FILE: config/locales/es-MX.yml ================================================ --- es-MX: articles: comment_counter: comments: one: 1 Comentario other: "%{count} Comentarios" zero: Sin Comentarios comment_form: comment: comentario email_address: Email your_website: Tu sitio web meta: published_on_html: Publicado el %{publish_date_and_time} por %{by}, tags %{tags} read: add_me_to_twitter: agregame en Twitter if_you_liked_this_article_you_can_html: Si le gustó este artículo, usted puede %{add_twitter} trackbacks_for: Trackbacks para search: search_results_for: Search results for '%{query}' comments: comment: by: por layouts: default: archives: Archives statuses: Statuses pagination: next: Next previous: Previous ================================================ FILE: config/locales/fr.yml ================================================ --- fr: articles: comment_counter: comments: one: 1 commentaire other: "%{count} commentaires" zero: aucun commentaire comment_form: comment: commentaire email_address: Adresse mail your_website: Votre site meta: published_on_html: Publié le %{publish_date_and_time} par %{by}, tags %{tags} read: add_me_to_twitter: me suivre sur Twitter if_you_liked_this_article_you_can_html: Si vous avez aimé cet article, vous pouvez %{add_twitter} trackbacks_for: Rétroliens pour search: search_results_for: Search results for '%{query}' comments: comment: by: par layouts: default: archives: Archives statuses: Statuts pagination: next: Suivant previous: Previous ================================================ FILE: config/locales/he.yml ================================================ --- he: articles: comment_counter: comments: one: 1 comment other: "%{count} comments" zero: ללא תגובות comment_form: comment: תגובה email_address: Email address your_website: Your website meta: published_on_html: Published on %{publish_date_and_time} by %{by}, tags %{tags} read: add_me_to_twitter: add me to Twitter if_you_liked_this_article_you_can_html: If you liked this article you can %{add_twitter} trackbacks_for: Trackbacks for search: search_results_for: Search results for '%{query}' comments: comment: by: by layouts: default: archives: Archives statuses: Statuses pagination: next: Next previous: Previous ================================================ FILE: config/locales/it.yml ================================================ --- it: articles: comment_counter: comments: one: 1 Commento other: "%{count} Commenti" zero: no comments comment_form: comment: comment email_address: Email address your_website: Your website meta: published_on_html: Published on %{publish_date_and_time} by %{by}, tags %{tags} read: add_me_to_twitter: add me to Twitter if_you_liked_this_article_you_can_html: If you liked this article you can %{add_twitter} trackbacks_for: Trackbacks for search: search_results_for: Search results for '%{query}' comments: comment: by: da layouts: default: archives: Archives statuses: Statuses pagination: next: Next previous: Previous ================================================ FILE: config/locales/ja.yml ================================================ --- ja: articles: comment_counter: comments: one: 1 comment other: "%{count} comments" zero: no comments comment_form: comment: comment email_address: Email address your_website: Your website meta: published_on_html: Published on %{publish_date_and_time} by %{by}, tags %{tags} read: add_me_to_twitter: add me to Twitter if_you_liked_this_article_you_can_html: If you liked this article you can %{add_twitter} trackbacks_for: Trackbacks for search: search_results_for: Search results for '%{query}' comments: comment: by: by layouts: default: archives: Archives statuses: Statuses pagination: next: Next previous: Previous ================================================ FILE: config/locales/lt.yml ================================================ --- lt: articles: comment_counter: comments: few: "%{count} Komentarai" one: 1 Komentaras other: "%{count} Komentarai" zero: no comments comment_form: comment: comment email_address: Email address your_website: Your website meta: published_on_html: Published on %{publish_date_and_time} by %{by}, tags %{tags} read: add_me_to_twitter: add me to Twitter if_you_liked_this_article_you_can_html: If you liked this article you can %{add_twitter} trackbacks_for: Trackbacks for search: search_results_for: Search results for '%{query}' comments: comment: by: bei layouts: default: archives: Archives statuses: Statuses pagination: next: Next previous: Previous ================================================ FILE: config/locales/nb.yml ================================================ --- nb: articles: comment_counter: comments: one: 1 kommentar other: "%{count} kommentarer" zero: ingen kommentarer comment_form: comment: kommentar email_address: Epostadresse your_website: Din hjemmeside meta: published_on_html: Publisert den %{publish_date_and_time} av %{by}, tagger %{tags} read: add_me_to_twitter: legge meg til på Twitter if_you_liked_this_article_you_can_html: Hvis du likte denne artikkelen, kan du %{add_twitter} trackbacks_for: Trackbacks for search: search_results_for: Search results for '%{query}' comments: comment: by: av layouts: default: archives: Archives statuses: Statusoppdateringer pagination: next: Neste previous: Forrige ================================================ FILE: config/locales/nl.yml ================================================ --- nl: articles: comment_counter: comments: one: 1 comment other: "%{count} comments" zero: Geen reacties comment_form: comment: reactie email_address: E-mailadres your_website: Je website meta: published_on_html: Published on %{publish_date_and_time} by %{by}, tags %{tags} read: add_me_to_twitter: add me to Twitter if_you_liked_this_article_you_can_html: If you liked this article you can %{add_twitter} trackbacks_for: Trackbacks for search: search_results_for: Search results for '%{query}' comments: comment: by: by layouts: default: archives: Archieven statuses: Statuses pagination: next: Next previous: Previous ================================================ FILE: config/locales/pl.yml ================================================ --- pl: articles: comment_counter: comments: few: "%{count} komentarzy" many: "%{count} komentarzy" one: 1 komentarz other: "%{count} komentarzy" zero: brak komentarzy comment_form: comment: komentarz email_address: Adres email your_website: Twoja strona www meta: published_on_html: 'Opublikowano %{publish_date_and_time} przez %{by}, tagi: %{tags}' read: add_me_to_twitter: obserwuj mnie na Twitterze if_you_liked_this_article_you_can_html: Jeżeli podobał Ci się ten wpis, %{add_twitter} trackbacks_for: Trackbacks for search: search_results_for: Wyniki wyszukiwania dla '%{query}' comments: comment: by: przez layouts: default: archives: Archiwum statuses: Statusy pagination: next: Następne previous: Poprzednie ================================================ FILE: config/locales/pt-BR.yml ================================================ --- pt-BR: articles: comment_counter: comments: one: 1 comentário other: "%{count} comentários" zero: nenhum comentário comment_form: comment: Comentário email_address: Endereço de e-mail your_website: Seu website meta: published_on_html: Publicado em %{publish_date_and_time} por %{by}, tags %{tags} read: add_me_to_twitter: me adicionar no Twitter if_you_liked_this_article_you_can_html: Se você gostou deste artigo, você pode %{add_twitter} trackbacks_for: Trackbacks para search: search_results_for: Resultados de busca para '%{query}' comments: comment: by: por layouts: default: archives: Arquivos statuses: Status pagination: next: Próxima previous: Anterior ================================================ FILE: config/locales/ro.yml ================================================ --- ro: articles: comment_counter: comments: few: "%{count} comentarii" one: 1 comentariu other: "%{count} comentarii" zero: no comentarii comment_form: comment: comment email_address: Email address your_website: Your website meta: published_on_html: Published on %{publish_date_and_time} by %{by}, tags %{tags} read: add_me_to_twitter: add me to Twitter if_you_liked_this_article_you_can_html: If you liked this article you can %{add_twitter} trackbacks_for: Trackbacks for search: search_results_for: Search results for '%{query}' comments: comment: by: de layouts: default: archives: Archives statuses: Statuses pagination: next: Next previous: Previous ================================================ FILE: config/locales/ru.yml ================================================ --- ru: articles: comment_counter: comments: few: 'Комментариев: %{count}' many: 'Комментариев: %{count}' one: 1 комментарий other: 'Комментариев: %{count}' zero: Нет комментариев comment_form: comment: Ответить на пост email_address: Электронная почта your_website: Веб-сайт meta: published_on_html: Опубликовано %{publish_date_and_time} автором %{by} под тегами %{tags} read: add_me_to_twitter: отправить в Twitter if_you_liked_this_article_you_can_html: Если Вам понравилась эта статья Вы можете %{add_twitter} trackbacks_for: Обратные ссылки для search: search_results_for: Результаты поиска для '%{query}' comments: comment: by: от layouts: default: archives: Архивы statuses: Статусы pagination: next: Следующая previous: Предыдущая ================================================ FILE: config/locales/sidebars.ar.yml ================================================ --- ar: authors_sidebar: content: authors: مؤلفين notes_sidebar: content: view_on_twitter: شاهد على تويتر popular_sidebar: content: comments: تعليقات nothing_to_show_yet: لا يوجد شئ لعرضه xml_sidebar: content: articles: مقالات feedback: ردود الفعل syndicate: نقابة tag: علامة ================================================ FILE: config/locales/sidebars.da.yml ================================================ --- da: authors_sidebar: content: authors: Authors notes_sidebar: content: view_on_twitter: View on Twitter popular_sidebar: content: comments: Comments nothing_to_show_yet: Nothing to show yet xml_sidebar: content: articles: Articles feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/sidebars.de.yml ================================================ --- de: authors_sidebar: content: authors: Authors notes_sidebar: content: view_on_twitter: View on Twitter popular_sidebar: content: comments: Comments nothing_to_show_yet: Nothing to show yet xml_sidebar: content: articles: Articles feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/sidebars.en.yml ================================================ --- en: authors_sidebar: content: authors: Authors notes_sidebar: content: view_on_twitter: View on Twitter popular_sidebar: content: comments: Comments nothing_to_show_yet: Nothing to show yet xml_sidebar: content: articles: Articles feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/sidebars.es-MX.yml ================================================ --- es-MX: authors_sidebar: content: authors: Authors notes_sidebar: content: view_on_twitter: View on Twitter popular_sidebar: content: comments: Comments nothing_to_show_yet: Nothing to show yet xml_sidebar: content: articles: Articles feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/sidebars.fr.yml ================================================ --- fr: authors_sidebar: content: authors: Authors notes_sidebar: content: view_on_twitter: View on Twitter popular_sidebar: content: comments: Comments nothing_to_show_yet: Nothing to show yet xml_sidebar: content: articles: Articles feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/sidebars.he.yml ================================================ --- he: authors_sidebar: content: authors: Authors notes_sidebar: content: view_on_twitter: View on Twitter popular_sidebar: content: comments: Comments nothing_to_show_yet: Nothing to show yet xml_sidebar: content: articles: Articles feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/sidebars.it.yml ================================================ --- it: authors_sidebar: content: authors: Authors notes_sidebar: content: view_on_twitter: View on Twitter popular_sidebar: content: comments: Comments nothing_to_show_yet: Nothing to show yet xml_sidebar: content: articles: Articles feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/sidebars.ja.yml ================================================ --- ja: authors_sidebar: content: authors: Authors notes_sidebar: content: view_on_twitter: View on Twitter popular_sidebar: content: comments: Comments nothing_to_show_yet: Nothing to show yet xml_sidebar: content: articles: Articles feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/sidebars.lt.yml ================================================ --- lt: authors_sidebar: content: authors: Authors notes_sidebar: content: view_on_twitter: View on Twitter popular_sidebar: content: comments: Comments nothing_to_show_yet: Nothing to show yet xml_sidebar: content: articles: Articles feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/sidebars.nb.yml ================================================ --- nb: authors_sidebar: content: authors: Authors notes_sidebar: content: view_on_twitter: View on Twitter popular_sidebar: content: comments: Comments nothing_to_show_yet: Nothing to show yet xml_sidebar: content: articles: Articles feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/sidebars.nl.yml ================================================ --- nl: authors_sidebar: content: authors: Authors notes_sidebar: content: view_on_twitter: View on Twitter popular_sidebar: content: comments: Comments nothing_to_show_yet: Nothing to show yet xml_sidebar: content: articles: Articles feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/sidebars.pl.yml ================================================ --- pl: authors_sidebar: content: authors: Autorzy notes_sidebar: content: view_on_twitter: Zobacz na Twitterze popular_sidebar: content: comments: Komentarze nothing_to_show_yet: Brak komentarzy xml_sidebar: content: articles: Artykuły feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/sidebars.pt-BR.yml ================================================ --- pt-BR: authors_sidebar: content: authors: Autores notes_sidebar: content: view_on_twitter: Ver no Twitter popular_sidebar: content: comments: Comentários nothing_to_show_yet: Nada a exibir no momento xml_sidebar: content: articles: Artigos feedback: Feedback syndicate: Sindicato tag: Tag ================================================ FILE: config/locales/sidebars.ro.yml ================================================ --- ro: authors_sidebar: content: authors: Authors notes_sidebar: content: view_on_twitter: View on Twitter popular_sidebar: content: comments: Comments nothing_to_show_yet: Nothing to show yet xml_sidebar: content: articles: Articles feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/sidebars.ru.yml ================================================ --- ru: authors_sidebar: content: authors: Авторы notes_sidebar: content: view_on_twitter: Просмотреть в Twitter popular_sidebar: content: comments: Комментарии nothing_to_show_yet: Пока что тут ничего нет xml_sidebar: content: articles: Статьи feedback: Отзыв syndicate: Синдикат tag: Тег ================================================ FILE: config/locales/sidebars.zh-CN.yml ================================================ --- zh-CN: authors_sidebar: content: authors: Authors notes_sidebar: content: view_on_twitter: View on Twitter popular_sidebar: content: comments: Comments nothing_to_show_yet: Nothing to show yet xml_sidebar: content: articles: Articles feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/sidebars.zh-TW.yml ================================================ --- zh-TW: authors_sidebar: content: authors: Authors notes_sidebar: content: view_on_twitter: View on Twitter popular_sidebar: content: comments: Comments nothing_to_show_yet: Nothing to show yet xml_sidebar: content: articles: Articles feedback: Feedback syndicate: Syndicate tag: Tag ================================================ FILE: config/locales/zh-CN.yml ================================================ --- zh-CN: articles: comment_counter: comments: one: 1 comment other: "%{count} comments" zero: no comments comment_form: comment: comment email_address: Email address your_website: Your website meta: published_on_html: Published on %{publish_date_and_time} by %{by}, tags %{tags} read: add_me_to_twitter: add me to Twitter if_you_liked_this_article_you_can_html: If you liked this article you can %{add_twitter} trackbacks_for: Trackbacks for search: search_results_for: Search results for '%{query}' comments: comment: by: by layouts: default: archives: Archives statuses: Statuses pagination: next: Next previous: Previous ================================================ FILE: config/locales/zh-TW.yml ================================================ --- zh-TW: articles: comment_counter: comments: one: 1 comment other: "%{count} comments" zero: no comments comment_form: comment: comment email_address: Email address your_website: Your website meta: published_on_html: Published on %{publish_date_and_time} by %{by}, tags %{tags} read: add_me_to_twitter: add me to Twitter if_you_liked_this_article_you_can_html: If you liked this article you can %{add_twitter} trackbacks_for: Trackbacks for search: search_results_for: Search results for '%{query}' comments: comment: by: by layouts: default: archives: Archives statuses: Statuses pagination: next: Next previous: Previous ================================================ FILE: config/mail.yml.example ================================================ delivery_method: :smtp settings: :address: mail.example.com :port: 25 :domain: example.com # :authentication: :login # :user_name: bob # :password: bobsyouruncle sendmail_settings: :location: /usr/bin/sendmail ================================================ FILE: config/puma.rb ================================================ # frozen_string_literal: true # Puma can serve each request in a thread from an internal thread pool. # The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5) min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } threads min_threads_count, max_threads_count # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # port ENV.fetch("PORT", 3000) # Specifies the `environment` that Puma will run in. # environment ENV.fetch("RAILS_ENV", "development") # Specifies the `pidfile` that Puma will use. pidfile ENV.fetch("PIDFILE", "tmp/pids/server.pid") # Specifies the number of `workers` to boot in clustered mode. # Workers are forked web server processes. If using threads and workers together # the concurrency of the application would be max `threads` * `workers`. # Workers do not work on JRuby or Windows (both of which do not support # processes). # # workers ENV.fetch("WEB_CONCURRENCY") { 2 } # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code # before forking the application. This takes advantage of Copy On Write # process behavior so workers use less memory. # # preload_app! # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart ================================================ FILE: config/routes.rb ================================================ # frozen_string_literal: true Rails.application.routes.draw do # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end ================================================ FILE: config/secrets.yml ================================================ # Be sure to restart your server when you modify this file. # Your secret key is used for verifying the integrity of signed cookies. # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. # You can use `rails secret` to generate a secure secret key. # Make sure the secrets in this file are kept private # if you're sharing your code publicly. development: secret_key_base: 189eafa373f70c3f770798b832dd5e48e3042c03639a6a1dc952c6b88eaed9b418d010874932a5f1b1d1d0453102160190729d1a04c453f9eb3639073c645311 test: secret_key_base: 81a64773cabdd99b20a666b0d1cbe69b27cd3eb6ea3213740016c8dff97f69d56ac092fb50c9a20b6c64e32b066d4970e4a1dc2d44c87b0dd86b5eea98a0a181 # Do not keep production secrets in the repository, # instead read values from the environment. production: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> ================================================ FILE: config/spring.rb ================================================ # frozen_string_literal: true %w( .ruby-version .rbenv-vars tmp/restart.txt tmp/caching-dev.txt ).each { |path| Spring.watch(path) } ================================================ FILE: config/storage.yml ================================================ test: service: Disk root: <%= Rails.root.join("tmp/storage") %> local: service: Disk root: <%= Rails.root.join("storage") %> # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) # amazon: # service: S3 # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> # region: us-east-1 # bucket: your_own_bucket # Remember not to checkin your GCS keyfile to a repository # google: # service: GCS # project: your_project # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> # bucket: your_own_bucket # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) # microsoft: # service: AzureStorage # storage_account_name: your_account_name # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> # container: your_container_name # mirror: # service: Mirror # primary: local # mirrors: [ amazon, google, microsoft ] ================================================ FILE: config.ru ================================================ # frozen_string_literal: true # This file is used by Rack-based servers to start the application. require_relative "config/environment" run Rails.application Rails.application.load_server ================================================ FILE: db/converters/README ================================================ These are converters for migrating from other blog systems to Publify. In general, you're best off using the converter that best-matches your blog system, and then falling back to the RSS or Feed converter if the others fail. These converters aren't always well tested, so please back up your data before converting, and please file bugs at https://github.com/publify/publify. Available converters: * feed.rb: Converts from any RSS or Atom feed. Requires the 'feed_tools' gem. * blogger.rb: Converts from an exported Blogger XML. Requires the 'feedzirra' gem. ================================================ FILE: db/converters/blogger.rb ================================================ #!/usr/bin/env ruby # Blogger converter to publify # Shamelessly copied from the RSS/Atom converter by Lennon Day-Reynolds require File.dirname(__FILE__) + '/../../config/environment' require 'optparse' require 'feedzirra' class BloggerConverter BLOGGER_POST = 'http://schemas.google.com/blogger/2008/kind#post'.freeze attr_accessor :options def initialize self.options = {} parse_options convert_entries end def convert_entries feed = Feedzirra::Feed.fetch_and_parse(options[:url]) puts "Converting #{feed.entries.length} entries..." # TODO: Allow setting the blog to be used. blog = Blog.first ping_store = blog.send_outbound_pings blog.send_outbound_pings = false # pings have to be diabled or the script crashes because too many connections are opened blog.save feed.entries.each do |item| create_article item if item.categories.include? BLOGGER_POST end blog.send_outbound_pings = ping_store blog.save end # FIXME: This is probably broken def create_article(entry) a = Article.new entry_author = create_author entry.author a.set_author entry_author a.title = entry.title a.body = entry.content a.created_at = entry.published a.published_at = entry.published a.text_filter_id = 1 a.add_category Category.first a.keywords = entry.categories - [BLOGGER_POST] a.allow_pings = false a.allow_comments = false puts "Converted '#{entry.title}'" if a.save end def create_author(name) unless User.exists? name: name user = User.new para = { 'login' => name, 'name' => name, 'nickname' => name, 'email' => name + '@fake.com', 'password' => 'password', 'profile_id' => 2, 'notify_via_email' => false, 'notify_on_comments' => false, 'notify_on_new_articles' => false, 'text_filter_id' => 1 } user.attributes = para user.save end User.where(name: name).first end def parse_options OptionParser.new do |opt| opt.banner = 'Usage: feed.rb [options]' opt.on('-u', '--url URL', 'URL of RSS feed to import.') do |u| options[:url] = u end opt.on_tail('-h', '--help', 'Show this message.') do puts opt exit end opt.parse!(ARGV) end unless options.include?(:url) puts 'See feed.rb --help for help.' exit end end end BloggerConverter.new ================================================ FILE: db/converters/feed.rb ================================================ #!/usr/bin/env ruby # RSS 0.2/2.0/Atom converter to publify by Lennon Day-Reynolds # Shamelessly copied from RSS-only converter by Chris Lee require File.dirname(__FILE__) + '/../../config/environment' require 'optparse' begin require 'feed_tools' rescue LoadError STDERR.puts <<-EOF.strip_heredoc This converter requires feedtools to be installed. Please run `gem install feedtools` and try again. EOF exit 1 end class FeedMigrate attr_accessor :options def initialize self.options = {} parse_options convert_entries end def convert_entries feed = FeedTools::Feed.open(options[:url]) puts "Converting #{feed.items.length} entries..." feed.items.each do |item| puts "Converting '#{item.title}'" a = Article.new a.author = options[:author] a.title = item.title a.body = item.description a.created_at = item.published a.save end end def parse_options OptionParser.new do |opt| opt.banner = 'Usage: feed.rb [options]' opt.on('-a', '--author AUTHOR', 'Username of author in publify') do |a| options[:author] = a end opt.on('-u', '--url URL', 'URL of RSS feed to import.') do |u| options[:url] = u end opt.on_tail('-h', '--help', 'Show this message.') do puts opt exit end opt.parse!(ARGV) end unless options.include?(:author) && options.include?(:url) puts 'See feed.rb --help for help.' exit end end end FeedMigrate.new ================================================ FILE: db/converters/rss.rb ================================================ #!/usr/bin/env ruby # RSS 0.9/2.0 converter for publify by Chris Lee # # No need to make a backup of the original blog, really. This takes a URL for a # read-only import, so there's not really any chance of it munging the original # blog's data, unless somehow an HTTP GET causes your blog server to ignite. # # Even so, this script is still PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. require File.dirname(__FILE__) + '/../../config/environment' require 'optparse' require 'net/http' require 'rss/2.0' class RSSMigrate attr_accessor :options def initialize self.options = {} parse_options convert_entries end def convert_entries feed = Net::HTTP.get(URI.parse(options[:url])) rss = RSS::Parser.parse(feed) puts "Converting #{rss.items.length} entries..." rss.items.each do |item| puts "Converting '#{item.title}'" a = Article.new a.author = options[:author] a.title = item.title a.body = item.description a.created_at = item.pubDate a.save end end def parse_options OptionParser.new do |opt| opt.banner = 'Usage: rss.rb [options]' opt.on('-a', '--author AUTHOR', 'Username of author in publify') do |a| options[:author] = a end opt.on('-u', '--url URL', 'URL of RSS feed to import.') do |u| options[:url] = u end opt.on_tail('-h', '--help', 'Show this message.') do puts opt exit end opt.parse!(ARGV) end unless options.include?(:author) && options.include?(:url) puts 'See rss.rb --help for help.' exit end end end RSSMigrate.new ================================================ FILE: db/migrate/113_initial_schema.publify_core_engine.rb ================================================ # frozen_string_literal: true class InitialSchema < ActiveRecord::Migration[4.2] def change create_table "articles_tags", id: false, force: true do |t| t.integer "article_id" t.integer "tag_id" end create_table "blogs", force: true do |t| t.text "settings" t.string "base_url" end create_table "categories", force: true do |t| t.string "name" t.integer "position" t.string "permalink" t.text "keywords" t.text "description" t.integer "parent_id" end add_index "categories", ["permalink"] create_table "categorizations", force: true do |t| t.integer "article_id" t.integer "category_id" t.boolean "is_primary" end create_table "contents", force: true do |t| t.string "type" t.string "title" t.string "author" t.text "body" t.text "extended" t.text "excerpt" t.datetime "created_at" t.datetime "updated_at" t.integer "user_id" t.string "permalink" t.string "guid" t.integer "text_filter_id" t.text "whiteboard" t.string "name" t.boolean "published", default: false t.boolean "allow_pings" t.boolean "allow_comments" t.datetime "published_at" t.string "state" t.integer "parent_id" t.text "settings" t.string "post_type", default: "read" end add_index "contents", ["published"] add_index "contents", ["text_filter_id"] create_table "feedback", force: true do |t| t.string "type" t.string "title" t.string "author" t.text "body" t.text "excerpt" t.datetime "created_at" t.datetime "updated_at" t.integer "user_id" t.string "guid" t.integer "text_filter_id" t.text "whiteboard" t.integer "article_id" t.string "email" t.string "url" t.string "ip", limit: 40 t.string "blog_name" t.boolean "published", default: false t.datetime "published_at" t.string "state" t.boolean "status_confirmed" t.string "user_agent" end add_index "feedback", ["article_id"] add_index "feedback", ["text_filter_id"] create_table "page_caches", force: true do |t| t.string "name" end add_index "page_caches", ["name"] create_table "pings", force: true do |t| t.integer "article_id" t.string "url" t.datetime "created_at" end add_index "pings", ["article_id"] create_table "post_types", force: true do |t| t.string "name" t.string "permalink" t.string "description" end create_table "profiles", force: true do |t| t.string "label" t.string "nicename" t.text "modules" end create_table "profiles_rights", id: false, force: true do |t| t.integer "profile_id" t.integer "right_id" end create_table "redirections", force: true do |t| t.integer "content_id" t.integer "redirect_id" end create_table "redirects", force: true do |t| t.string "from_path" t.string "to_path" t.string "origin" t.datetime "created_at" t.datetime "updated_at" end create_table "resources", force: true do |t| t.integer "size" t.string "upload" t.string "mime" t.datetime "created_at" t.datetime "updated_at" t.integer "article_id" t.boolean "itunes_metadata" t.string "itunes_author" t.string "itunes_subtitle" t.integer "itunes_duration" t.text "itunes_summary" t.string "itunes_keywords" t.string "itunes_category" t.boolean "itunes_explicit" end create_table "sidebars", force: true do |t| t.integer "active_position" t.text "config" t.integer "staged_position" t.string "type" end create_table "sitealizer", force: true do |t| t.string "path" t.string "ip" t.string "referer" t.string "language" t.string "user_agent" t.datetime "created_at" t.date "created_on" end create_table "tags", force: true do |t| t.string "name" t.datetime "created_at" t.datetime "updated_at" t.string "display_name" end create_table "text_filters", force: true do |t| t.string "name" t.string "description" t.string "markup" t.text "filters" t.text "params" end create_table "triggers", force: true do |t| t.integer "pending_item_id" t.string "pending_item_type" t.datetime "due_at" t.string "trigger_method" end create_table "users", force: true do |t| t.string "login" t.string "password" t.text "email" t.text "name" t.boolean "notify_via_email" t.boolean "notify_on_new_articles" t.boolean "notify_on_comments" t.integer "profile_id" t.string "remember_token" t.datetime "remember_token_expires_at" t.string "text_filter_id", default: "1" t.string "state", default: "active" t.datetime "last_connection" t.text "settings" t.integer "resource_id" end end end ================================================ FILE: db/migrate/114_fixes_buggy_articles_and_notes.publify_core_engine.rb ================================================ # frozen_string_literal: true class FixesBuggyArticlesAndNotes < ActiveRecord::Migration[4.2] class Content < ActiveRecord::Base end class Article < Content def set_permalink return if state == "draft" || permalink.present? self.permalink = title.to_permalink end end class Note < Content def set_permalink self.permalink = "#{id}-#{body.to_permalink[0..79]}" if permalink.blank? save end def create_guid return true if guid.present? self.guid = UUIDTools::UUID.random_create.to_s end end class Page < Content def set_permalink self.name = title.to_permalink if name.blank? end end def self.up say "Fixing contents permalinks, this may take some time" contents = Content.where("permalink is ? or permalink = ?", nil, "") contents.each do |c| c.set_permalink c.save end say "Fixes empty notes GUID" notes = Note.where("guid is ? or guid = ?", nil, "") notes.each do |n| n.create_guid n.save end end def self.down say "Nothing to do here" end end ================================================ FILE: db/migrate/115_drops_categories_for_tags.publify_core_engine.rb ================================================ # frozen_string_literal: true class DropsCategoriesForTags < ActiveRecord::Migration[4.2] class Categorization < ActiveRecord::Base belongs_to :article belongs_to :category end class Category < ActiveRecord::Base has_many :categorizations has_many :articles, through: :categorizations end def up # First, we migrate categories into tags Category.find_each do |cat| # Does a tag with the same permalink exist? tag = Tag.find_or_create_by(name: cat.permalink) do |tg| tg.display_name = cat.name end Redirect.create(from_path: "category/#{cat.permalink}", to_path: File.join(Blog.first.base_url, "tag", tag.name)) cat.articles.each do |article| article.tags << tag article.save end end drop_table :categorizations drop_table :categories end def down; end end ================================================ FILE: db/migrate/20150207131657_add_missing_indexes.publify_core_engine.rb ================================================ # frozen_string_literal: true class AddMissingIndexes < ActiveRecord::Migration[4.2] def change add_index :feedback, [:id, :type] add_index :feedback, :user_id add_index :sidebars, [:id, :type] add_index :contents, :user_id add_index :contents, [:id, :type] add_index :articles_tags, :tag_id add_index :articles_tags, :article_id add_index :profiles_rights, :profile_id add_index :users, :profile_id add_index :users, :text_filter_id add_index :users, :resource_id add_index :triggers, [:pending_item_id, :pending_item_type] add_index :redirections, :content_id add_index :redirections, :redirect_id add_index :resources, :article_id end end ================================================ FILE: db/migrate/20150807134129_simplify_redirect_relations.publify_core_engine.rb ================================================ # frozen_string_literal: true class SimplifyRedirectRelations < ActiveRecord::Migration[4.2] class Redirect < ActiveRecord::Base; end class Redirection < ActiveRecord::Base; end def up add_column :redirects, :content_id, :integer Redirect.find_each do |redirect| redirections = Redirection.where(redirect_id: redirect.id) if redirections.many? raise "Expected zero or one redirections, found #{redirections.count}" end redirection = redirections.first next unless redirection redirect.content_id = redirection.content_id redirect.save! end remove_column :redirects, :origin drop_table :redirections end def down create_table :redirections do |t| t.integer :content_id t.integer :redirect_id end add_index :redirections, [:content_id] add_index :redirections, [:redirect_id] add_column :redirects, :origin, :string Redirect.find_each do |redirect| next unless redirect.content_id Redirection.create(redirect_id: redirect.id, content_id: redirect.content_id) end remove_column :redirects, :content_id end end ================================================ FILE: db/migrate/20150808052637_add_blog_ids.publify_core_engine.rb ================================================ # frozen_string_literal: true class AddBlogIds < ActiveRecord::Migration[4.2] class Blog < ActiveRecord::Base; end class Content < ActiveRecord::Base; end class Sidebar < ActiveRecord::Base; end def up add_column :contents, :blog_id, :integer add_column :sidebars, :blog_id, :integer if Content.any? || Sidebar.any? default_blog_id = Blog.order(:id).first.id Content.update_all("blog_id = #{default_blog_id}") Sidebar.update_all("blog_id = #{default_blog_id}") end change_column :sidebars, :blog_id, :integer, null: false end def down if adapter_name == "PostgreSQL" indexes(:contents).each do |index| remove_index(:contents, name: index.name) if index.name.include?("blog_id") end else begin remove_index :contents, :blog_id rescue nil end end remove_column :contents, :blog_id remove_column :sidebars, :blog_id end end ================================================ FILE: db/migrate/20150808191127_add_blog_id_to_redirects.publify_core_engine.rb ================================================ # frozen_string_literal: true class AddBlogIdToRedirects < ActiveRecord::Migration[4.2] class Redirect < ActiveRecord::Base; end def up add_column :redirects, :blog_id, :integer if Redirect.any? default_blog_id = Blog.order(:id).first.id Redirect.update_all("blog_id = #{default_blog_id}") end end def down remove_column :redirects, :blog_id end end ================================================ FILE: db/migrate/20150810094754_add_blog_id_to_tags.publify_core_engine.rb ================================================ # frozen_string_literal: true class AddBlogIdToTags < ActiveRecord::Migration[4.2] class Tag < ActiveRecord::Base; end def up add_column :tags, :blog_id, :integer if Tag.any? default_blog_id = Blog.order(:id).first.id Tag.update_all("blog_id = #{default_blog_id}") end end def down remove_column :tags, :blog_id end end ================================================ FILE: db/migrate/20160108111120_add_devise_to_users.publify_core_engine.rb ================================================ # frozen_string_literal: true class AddDeviseToUsers < ActiveRecord::Migration[4.2] def self.up ## Database authenticatable change_column :users, :email, :string, null: false, default: "" rename_column :users, :password, :encrypted_password change_column :users, :encrypted_password, :string, null: false, default: "" change_table(:users) do |t| ## Recoverable t.string :reset_password_token t.datetime :reset_password_sent_at ## Rememberable t.datetime :remember_created_at ## Trackable t.integer :sign_in_count, default: 0, null: false t.datetime :current_sign_in_at t.datetime :last_sign_in_at t.string :current_sign_in_ip t.string :last_sign_in_ip # Timestamps were not included in our original model. t.timestamps end add_index :users, :email, unique: true add_index :users, :reset_password_token, unique: true end def self.down remove_index :users, :email remove_index :users, :reset_password_token remove_column :users, :created_at remove_column :users, :updated_at remove_column :users, :sign_in_count remove_column :users, :current_sign_in_at remove_column :users, :last_sign_in_at remove_column :users, :current_sign_in_ip remove_column :users, :last_sign_in_ip remove_column :users, :remember_created_at remove_column :users, :reset_password_token remove_column :users, :reset_password_sent_at change_column :users, :encrypted_password, :string, null: true, default: nil rename_column :users, :encrypted_password, :password change_column :users, :email, :text, null: true, default: nil end end ================================================ FILE: db/migrate/20160108184201_move_last_connection_to_last_sign_in_at.publify_core_engine.rb ================================================ # frozen_string_literal: true class MoveLastConnectionToLastSignInAt < ActiveRecord::Migration[4.2] class User < ActiveRecord::Base end def up User.find_each do |user| user.update_attribute(:last_sign_in_at, user.last_connection) end end def down User.find_each do |user| user.update_attribute(:last_connection, user.last_sign_in_at) end end end ================================================ FILE: db/migrate/20160110094906_remove_profiles_rights.publify_core_engine.rb ================================================ # frozen_string_literal: true class RemoveProfilesRights < ActiveRecord::Migration[4.2] def up drop_table :profiles_rights end def down create_table :profiles_rights, id: false do |t| t.integer :profile_id t.integer :right_id end add_index :profiles_rights, [:profile_id] end end ================================================ FILE: db/migrate/20160605103918_replace_profile_id_with_string.publify_core_engine.rb ================================================ # frozen_string_literal: true class ReplaceProfileIdWithString < ActiveRecord::Migration[4.2] class Profile < ActiveRecord::Base end class User < ActiveRecord::Base end def up add_column :users, :profile, :string User.reset_column_information User.find_each do |user| user.update_attribute(:profile, Profile.find(user.profile_id).label) end remove_column :users, :profile_id end def down add_column :users, :profile_id, :integer User.reset_column_information User.find_each do |user| user.update_attribute(:profile_id, Profile.find_by(label: user.profile).id) end remove_column :users, :profile end end ================================================ FILE: db/migrate/20160605154632_remove_profiles.publify_core_engine.rb ================================================ # frozen_string_literal: true class RemoveProfiles < ActiveRecord::Migration[4.2] class Profile < ActiveRecord::Base serialize :modules, coder: YAML end def up drop_table :profiles end def down create_table :profiles do |t| t.string :label t.string :nicename t.text :modules end Profile.create!(label: "admin", nicename: "Publify administrator", modules: [:dashboard, :articles, :notes, :pages, :feedback, :media, :themes, :sidebar, :profile, :users, :settings, :seo]) Profile.create!(label: "publisher", nicename: "Blog publisher", modules: [:dashboard, :articles, :notes, :pages, :feedback, :media, :profile]) Profile.create!(label: "contributor", nicename: "Contributor", modules: [:dashboard, :profile]) end end ================================================ FILE: db/migrate/20160701061851_demand_blog_id_on_contents.publify_core_engine.rb ================================================ # frozen_string_literal: true class DemandBlogIdOnContents < ActiveRecord::Migration[4.2] def up change_column :contents, :blog_id, :integer, null: false end def down change_column :contents, :blog_id, :integer, null: true end end ================================================ FILE: db/migrate/20160701062604_add_blog_id_to_resources.publify_core_engine.rb ================================================ # frozen_string_literal: true class AddBlogIdToResources < ActiveRecord::Migration[4.2] class Blog < ActiveRecord::Base; end class Content < ActiveRecord::Base; end class Resource < ActiveRecord::Base; end def up add_column :resources, :blog_id, :integer if Resource.any? default_blog_id = Blog.order(:id).first.id Resource.find_each do |resource| content_id = resource.article_id blog_id = if content_id Content.find(content_id).blog_id else default_blog_id end resource.update_column(:blog_id, blog_id) end end change_column :resources, :blog_id, :integer, null: false end def down remove_column :resources, :blog_id end end ================================================ FILE: db/migrate/20161030121548_add_sessions_table.rb ================================================ # frozen_string_literal: true class AddSessionsTable < ActiveRecord::Migration[4.2] def change create_table :sessions do |t| t.string :session_id, null: false t.text :data t.timestamps null: false end add_index :sessions, :session_id, unique: true add_index :sessions, :updated_at end end ================================================ FILE: db/migrate/20170528093428_move_resources_to_content.publify_core_engine.rb ================================================ # frozen_string_literal: true # This migration comes from publify_core_engine (originally 20170528093024) class MoveResourcesToContent < ActiveRecord::Migration[5.0] def change rename_column :resources, :article_id, :content_id end end ================================================ FILE: db/migrate/20170528120220_move_tags_to_content.publify_core_engine.rb ================================================ # frozen_string_literal: true # This migration comes from publify_core_engine (originally 20170528094923) class MoveTagsToContent < ActiveRecord::Migration[5.0] def change rename_column :articles_tags, :article_id, :content_id rename_table :articles_tags, :contents_tags end end ================================================ FILE: db/migrate/20170530063901_remove_separate_published_flag.publify_core_engine.rb ================================================ # frozen_string_literal: true # This migration comes from publify_core_engine (originally 20170528201606) class RemoveSeparatePublishedFlag < ActiveRecord::Migration[5.0] def change remove_column :contents, :published, :boolean, default: false end end ================================================ FILE: db/migrate/20170605103539_remove_extra_state_columns_from_feedback.publify_core_engine.rb ================================================ # frozen_string_literal: true # This migration comes from publify_core_engine (originally 20170605071626) class RemoveExtraStateColumnsFromFeedback < ActiveRecord::Migration[5.0] def change remove_column :feedback, :published, :boolean, default: false remove_column :feedback, :status_confirmed, :boolean end end ================================================ FILE: db/migrate/20170702105518_remove_published_at_from_feedback.publify_core_engine.rb ================================================ # frozen_string_literal: true # This migration comes from publify_core_engine (originally 20170702105201) class RemovePublishedAtFromFeedback < ActiveRecord::Migration[5.0] def change remove_column :feedback, :published_at, :datetime end end ================================================ FILE: db/migrate/20190210121314_add_text_filter_name_fields.publify_core_engine.rb ================================================ # frozen_string_literal: true # This migration comes from publify_core_engine (originally 20190208151235) class AddTextFilterNameFields < ActiveRecord::Migration[5.2] def change add_column :contents, :text_filter_name, :string add_column :feedback, :text_filter_name, :string add_column :users, :text_filter_name, :string end end ================================================ FILE: db/migrate/20190210121315_move_text_filter_to_name.publify_core_engine.rb ================================================ # frozen_string_literal: true # This migration comes from publify_core_engine (originally 20190208152646) class MoveTextFilterToName < ActiveRecord::Migration[5.2] class Content < ActiveRecord::Base self.inheritance_column = :bogus belongs_to :text_filter, optional: true end class Feedback < ActiveRecord::Base self.table_name = "feedback" self.inheritance_column = :bogus belongs_to :text_filter, optional: true end class User < ActiveRecord::Base belongs_to :text_filter, optional: true end class TextFilter < ActiveRecord::Base serialize :filters, Array, coder: YAML serialize :params, Hash, coder: YAML end def up Content.find_each do |content| filter = content.text_filter filter_name = filter&.name || "none" content.update!(text_filter_name: filter_name, text_filter_id: nil) end Feedback.find_each do |feedback| filter = feedback.text_filter filter_name = filter&.name || "none" feedback.update!(text_filter_name: filter_name, text_filter_id: nil) end User.find_each do |user| filter = user.text_filter filter_name = filter&.name || "none" user.update!(text_filter_name: filter_name, text_filter_id: nil) end TextFilter.destroy_all end def down TextFilter .create_with(description: "None", markup: "none", filters: [], params: {}) .find_or_create_by!(name: "none") TextFilter .create_with(description: "Markdown", markup: "markdown", filters: [], params: {}) .find_or_create_by!(name: "markdown") TextFilter .create_with(description: "SmartyPants", markup: "none", filters: [:smartypants], params: {}) .find_or_create_by!(name: "smartypants") TextFilter .create_with(description: "Markdown with SmartyPants", markup: "markdown", filters: [:smartypants], params: {}) .find_or_create_by!(name: "markdown smartypants") TextFilter .create_with(description: "Textile", markup: "textile", filters: [], params: {}) .find_or_create_by!(name: "textile") Content.find_each do |content| filter_name = content.text_filter_name next unless filter_name filter = TextFilter.find(name: filter_name) raise "Filter #{filter_name} not found" unless filter content.update!(text_filter: filter) end Feedback.find_each do |feedback| filter_name = feedback.text_filter_name next unless filter_name filter = TextFilter.find(name: filter_name) raise "Filter #{filter_name} not found" unless filter feedback.update!(text_filter: filter) end User.find_each do |user| filter_name = user.text_filter_name next unless filter_name filter = TextFilter.find(name: filter_name) raise "Filter #{filter_name} not found" unless filter user.update!(text_filter: filter) end end end ================================================ FILE: db/migrate/20190210121316_remove_text_filter_ids.publify_core_engine.rb ================================================ # frozen_string_literal: true # This migration comes from publify_core_engine (originally 20190209155717) class RemoveTextFilterIds < ActiveRecord::Migration[5.2] def up remove_column :contents, :text_filter_id remove_column :feedback, :text_filter_id remove_column :users, :text_filter_id end def down add_column :contents, :text_filter_id, :integer add_column :feedback, :text_filter_id, :integer add_column :users, :text_filter_id, :string, default: "1" add_index :contents, [:text_filter_id] add_index :feedback, [:text_filter_id] add_index :users, [:text_filter_id] end end ================================================ FILE: db/migrate/20190210121317_remove_text_filters.publify_core_engine.rb ================================================ # frozen_string_literal: true # This migration comes from publify_core_engine (originally 20190209160610) class RemoveTextFilters < ActiveRecord::Migration[5.2] def up drop_table :text_filters end def down create_table :text_filters do |t| t.string :name t.string :description t.string :markup t.text :filters t.text :params end end end ================================================ FILE: db/migrate/20200413141133_add_unique_indexes.publify_core_engine.rb ================================================ # frozen_string_literal: true # This migration comes from publify_core_engine (originally 20200413140440) class AddUniqueIndexes < ActiveRecord::Migration[5.2] def change add_index :post_types, :name, unique: true add_index :redirects, :from_path, unique: true add_index :tags, [:blog_id, :name], unique: true add_index :users, :login, unique: true end end ================================================ FILE: db/migrate/20221007091118_remove_table_sitealizer.publify_core_engine.rb ================================================ # frozen_string_literal: true # This migration comes from publify_core_engine (originally 20221007091049) class RemoveTableSitealizer < ActiveRecord::Migration[6.1] def up drop_table :sitealizer end def down create_table :sitealizer do |t| t.string :path t.string :ip t.string :referer t.string :language t.string :user_agent t.datetime :created_at t.date :created_on end end end ================================================ FILE: db/migrate/20221010170801_remove_page_caches_table.publify_core_engine.rb ================================================ # frozen_string_literal: true # This migration comes from publify_core_engine (originally 20221010092846) class RemovePageCachesTable < ActiveRecord::Migration[6.1] def up drop_table :page_caches end def down create_table :page_caches do |t| t.string :name end add_index :page_caches, :name end end ================================================ FILE: db/migrate/20221012164027_remove_itunes_fields_from_resources.publify_core_engine.rb ================================================ # frozen_string_literal: true # This migration comes from publify_core_engine (originally 20221012163214) class RemoveItunesFieldsFromResources < ActiveRecord::Migration[6.1] def change remove_column :resources, :itunes_metadata, :boolean remove_column :resources, :itunes_author, :string remove_column :resources, :itunes_subtitle, :string remove_column :resources, :itunes_duration, :integer remove_column :resources, :itunes_summary, :text remove_column :resources, :itunes_keywords, :string remove_column :resources, :itunes_category, :string remove_column :resources, :itunes_explicit, :boolean end end ================================================ FILE: db/schema.rb ================================================ # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # # This file is the source Rails uses to define your schema when running `bin/rails # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to # be faster and is potentially less error prone than running all of your # migrations from scratch. Old migrations may fail to apply correctly if those # migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema[7.1].define(version: 2022_10_12_164027) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" create_table "blogs", id: :serial, force: :cascade do |t| t.text "settings" t.string "base_url" end create_table "contents", id: :serial, force: :cascade do |t| t.string "type" t.string "title" t.string "author" t.text "body" t.text "extended" t.text "excerpt" t.datetime "created_at", precision: nil t.datetime "updated_at", precision: nil t.integer "user_id" t.string "permalink" t.string "guid" t.text "whiteboard" t.string "name" t.boolean "allow_pings" t.boolean "allow_comments" t.datetime "published_at", precision: nil t.string "state" t.integer "parent_id" t.text "settings" t.string "post_type", default: "read" t.integer "blog_id", null: false t.string "text_filter_name" t.index ["id", "type"], name: "index_contents_on_id_and_type" t.index ["user_id"], name: "index_contents_on_user_id" end create_table "contents_tags", id: false, force: :cascade do |t| t.integer "content_id" t.integer "tag_id" t.index ["content_id"], name: "index_contents_tags_on_content_id" t.index ["tag_id"], name: "index_contents_tags_on_tag_id" end create_table "feedback", id: :serial, force: :cascade do |t| t.string "type" t.string "title" t.string "author" t.text "body" t.text "excerpt" t.datetime "created_at", precision: nil t.datetime "updated_at", precision: nil t.integer "user_id" t.string "guid" t.text "whiteboard" t.integer "article_id" t.string "email" t.string "url" t.string "ip", limit: 40 t.string "blog_name" t.string "state" t.string "user_agent" t.string "text_filter_name" t.index ["article_id"], name: "index_feedback_on_article_id" t.index ["id", "type"], name: "index_feedback_on_id_and_type" t.index ["user_id"], name: "index_feedback_on_user_id" end create_table "pings", id: :serial, force: :cascade do |t| t.integer "article_id" t.string "url" t.datetime "created_at", precision: nil t.index ["article_id"], name: "index_pings_on_article_id" end create_table "post_types", id: :serial, force: :cascade do |t| t.string "name" t.string "permalink" t.string "description" t.index ["name"], name: "index_post_types_on_name", unique: true end create_table "redirects", id: :serial, force: :cascade do |t| t.string "from_path" t.string "to_path" t.datetime "created_at", precision: nil t.datetime "updated_at", precision: nil t.integer "content_id" t.integer "blog_id" t.index ["from_path"], name: "index_redirects_on_from_path", unique: true end create_table "resources", id: :serial, force: :cascade do |t| t.integer "size" t.string "upload" t.string "mime" t.datetime "created_at", precision: nil t.datetime "updated_at", precision: nil t.integer "content_id" t.integer "blog_id", null: false t.index ["content_id"], name: "index_resources_on_content_id" end create_table "sessions", id: :serial, force: :cascade do |t| t.string "session_id", null: false t.text "data" t.datetime "created_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false t.index ["session_id"], name: "index_sessions_on_session_id", unique: true t.index ["updated_at"], name: "index_sessions_on_updated_at" end create_table "sidebars", id: :serial, force: :cascade do |t| t.integer "active_position" t.text "config" t.integer "staged_position" t.string "type" t.integer "blog_id", null: false t.index ["id", "type"], name: "index_sidebars_on_id_and_type" end create_table "tags", id: :serial, force: :cascade do |t| t.string "name" t.datetime "created_at", precision: nil t.datetime "updated_at", precision: nil t.string "display_name" t.integer "blog_id" t.index ["blog_id", "name"], name: "index_tags_on_blog_id_and_name", unique: true end create_table "triggers", id: :serial, force: :cascade do |t| t.integer "pending_item_id" t.string "pending_item_type" t.datetime "due_at", precision: nil t.string "trigger_method" t.index ["pending_item_id", "pending_item_type"], name: "index_triggers_on_pending_item_id_and_pending_item_type" end create_table "users", id: :serial, force: :cascade do |t| t.string "login" t.string "encrypted_password", default: "", null: false t.string "email", default: "", null: false t.text "name" t.boolean "notify_via_email" t.boolean "notify_on_new_articles" t.boolean "notify_on_comments" t.string "remember_token" t.datetime "remember_token_expires_at", precision: nil t.string "state", default: "active" t.datetime "last_connection", precision: nil t.text "settings" t.integer "resource_id" t.string "reset_password_token" t.datetime "reset_password_sent_at", precision: nil t.datetime "remember_created_at", precision: nil t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at", precision: nil t.datetime "last_sign_in_at", precision: nil t.string "current_sign_in_ip" t.string "last_sign_in_ip" t.datetime "created_at", precision: nil t.datetime "updated_at", precision: nil t.string "profile" t.string "text_filter_name" t.index ["email"], name: "index_users_on_email", unique: true t.index ["login"], name: "index_users_on_login", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true t.index ["resource_id"], name: "index_users_on_resource_id" end end ================================================ FILE: db/seeds.rb ================================================ # frozen_string_literal: true PublifyCore::Engine.load_seed ================================================ FILE: doc/CACHE.SETUP.README ================================================ To fully profit from Publify static caching capabilities, you need to add the following rewrite rules to either your .htaccess or your virtualhost after declaring the document root. == Rewrite Rules For Apache === If Publify is installed at your URL root. RewriteEngine On RewriteCond %{DOCUMENT_ROOT}/cache/index.html -f RewriteRule ^/$ /cache/index.html [PT] RewriteCond %{DOCUMENT_ROOT}/cache/%{REQUEST_FILENAME} -f RewriteRule ^/(.*)$ /cache/$1 [PT] RewriteCond %{DOCUMENT_ROOT}/cache/%{REQUEST_FILENAME}.html -f RewriteRule ^/(.*)$ /cache/$1.html [PT] === If Publify is installed in a sub-URL, e.g., /blog: # Note that 'blog' appears twice in the rewritten URL. RewriteEngine On RewriteCond %{DOCUMENT_ROOT}/blog/cache/blog.html -f RewriteRule ^/blog$ /blog/cache/blog.html [PT] RewriteCond %{DOCUMENT_ROOT}/blog/cache/%{REQUEST_FILENAME} -f RewriteRule ^/blog/(.*)$ /blog/cache/blog/$1 [PT] RewriteCond %{DOCUMENT_ROOT}/blog/cache/%{REQUEST_FILENAME}.html -f RewriteRule ^/blog/(.*)$ /blog/cache/blog/$1.html [PT] == Rewrite Rules For Nginx if (-f $request_filename) { break; } if (-f $document_root/cache/index.html) { rewrite ^/$ /cache/index.html break; } if (-f $document_root/cache$uri) { rewrite (.*) /cache$1 break; break; } if (-f $document_root/cache$uri.html) { rewrite (.*) /cache$1.html break; break; } ================================================ FILE: doc/dependency_decisions.yml ================================================ --- - - :permit - MIT - :who: :why: :versions: [] :when: 2017-07-22 20:10:47.517185110 Z - - :permit - New BSD - :who: :why: :versions: [] :when: 2017-07-22 20:10:58.423002290 Z - - :permit - ruby - :who: :why: :versions: [] :when: 2017-07-28 12:17:13.166276946 Z - - :permit - Apache 2.0 - :who: :why: :versions: [] :when: 2017-07-28 12:17:23.516443145 Z - - :permit - Simplified BSD - :who: :why: :versions: [] :when: 2017-07-28 12:17:39.295508085 Z - - :approve - easy_translate - :who: :why: Licensed under the MIT License :versions: - 0.5.0 :when: 2017-07-28 12:37:43.703529091 Z - - :approve - bluecloth - :who: :why: :versions: [] :when: 2018-08-31 11:04:41.169889983 Z - - :approve - unf - :who: :why: :versions: [] :when: 2018-08-31 11:04:56.051894107 Z - - :permit - ISC - :who: :why: :versions: [] :when: 2018-08-31 11:05:29.553665934 Z - - :permit - WTFPL - :who: :why: :versions: [] :when: 2020-09-13 15:15:06.072195607 Z ================================================ FILE: lib/generators/sidebar/USAGE ================================================ Description: Generate Sidebar plugin for Typo Example: rails generate sidebar ThingSidebar This will create: lib/thing_sidebar/lib/thing_sidebar.rb lib/thing_sidebar/app/views/thing_sidebar/_content.html.erb spec/models/thing_sidebar_spec.rb ================================================ FILE: lib/generators/sidebar/sidebar_generator.rb ================================================ # frozen_string_literal: true class SidebarGenerator < Rails::Generators::NamedBase source_root File.expand_path("templates", __dir__) def copy_stuff template "sidebar.rb.erb", "#{plugin_path}/lib/#{file_name}.rb" template "_content.html.erb.erb", "#{plugin_path}/app/views/#{file_name}/_content.html.erb" template "model_spec.rb.erb", "spec/models/#{file_name}_spec.rb" end private def plugin_path @plugin_path ||= "lib/#{file_name}" end end ================================================ FILE: lib/generators/sidebar/templates/_content.html.erb.erb ================================================
'-body>

Replace this text with the real sidebar view code

You should be able to access request, controller, params etc, as well as local variables set from the sidebar settings. So if you've done setting :title, 'Whatever', you can use <%= title %> to get at its value. The current sidebar object is accessible as sidebar.

================================================ FILE: lib/generators/sidebar/templates/model_spec.rb.erb ================================================ require 'rails_helper' RSpec.describe <%= class_name %> do it 'is available' do expect(SidebarRegistry.available_sidebars).to include(<%= class_name %>) end end ================================================ FILE: lib/generators/sidebar/templates/sidebar.rb.erb ================================================ class <%= class_name %> < Sidebar # display_name "<%= class_name.underscore.humanize %>" # Default description "Describe your sidebar here" # Check the other sidebars for the sort of thing you can do with setting # declarations def parse_request(contents, params) # contents is a list of the items being rendered on the current page # params is the params hash for the current request # Take a look at (eg) the amazon sidebar for examples of what gets done here # If your sidebar doesn't depend on the request or the contents, you don't # need to do anything here. end end ================================================ FILE: lib/publify_app/textfilter/flickr.rb ================================================ # frozen_string_literal: true class PublifyApp class Textfilter class Flickr < TextFilterPlugin::MacroPost plugin_display_name "Flickr" plugin_description "Automatically generate image tags for Flickr images" def self.help_text <<~TXT You can use `` to display images from [Flickr](http://flickr.com). Example: will produce an `` tag showing image number 31367273 from Flickr. This image will be linked to the Flickr page for this image, so you can zoom in and see larger versions. It will also have a comment block attached if a description has been attached to the picture in Flickr. This macro takes a number of parameters: * **img** The Flickr image ID of the picture that you wish to use. This shows up in the URL whenever you're viewing a picture in Flickr; for example, the image ID for is 31367273. * **size** The image size that you'd like to display. Options are: * square (75x75) * thumbnail (maximum size 100 pixels) * small (maximum size 240 pixels) * medium (maximum size 500 pixels) * large (maximum size 1024 pixels) * original * **style** This is passed through to the enclosing `
` that this macro generates. To float the flickr image on the right, use `style="float:right"`. * **caption** The caption displayed below the image. By default, this is Flickr's description of the image. to disable, use `caption=""`. * **title** The tooltip title associated with the image. Defaults to Flickr's image title. * **alt** The alt text associated with the image. By default, this is the same as the title. TXT end def self.macrofilter(attrib, _text = "") img = attrib["img"] size = attrib["size"] || "square" style = attrib["style"] caption = attrib["caption"] title = attrib["title"] alt = attrib["alt"] begin flickrimage = flickr.photos.getInfo(photo_id: img) sizes = flickr.photos.getSizes(photo_id: img) details = sizes.find { |s| s["label"].casecmp(size.downcase).zero? } || sizes.first width = details["width"] height = details["height"] # use protocol-relative URL after getting the source address # so not to break HTTPS support imageurl = details["source"].sub(/^https?:/, "") imagelink = flickrimage.urls.find { |u| u.type == "photopage" }.to_s description = flickrimage.description caption ||= sanitize(CGI.unescapeHTML(description)) if description.present? title ||= flickrimage.title alt ||= title captioncode = if caption.blank? "" else "

#{caption}

" end "
" \ "\"#{alt}\"#{captioncode}
" rescue StandardError => e logger.info e.message "" end end end end end ================================================ FILE: lib/publify_app/textfilter/htmlfilter.rb ================================================ # frozen_string_literal: true class PublifyApp class Textfilter class Htmlfilter < TextFilterPlugin plugin_display_name "HTML Filter" plugin_description "Strip HTML tags" def self.filtertext(text) text.to_s.gsub("<", "<").gsub(">", ">") end end end end ================================================ FILE: lib/publify_app/textfilter/lightbox.rb ================================================ # frozen_string_literal: true class PublifyApp class Textfilter class Lightbox < TextFilterPlugin::MacroPost plugin_display_name "Lightbox" plugin_description "Automatically generate tags for images displayed in a lightbox" def self.help_text <<~TXT You can use `` to display images from [Flickr](http://flickr.com) or a provided URL which, when clicked, will be shown in a lightbox using Lokesh Dhakar's [Lightbox](http://www.huddletogether.com/projects/lightbox/) Javascript Example: The first will produce an `` tag showing image number 31367273 from Flickr using the thumbnail image size. The image will be linked to the original image file from Flickr. When the link is clicked, the larger picture will be overlaid on top of the existing page instead of taking you over to the Flickr site. The second will do the same but use the `src` URL as the large picture and the `thumbsrc` URL as the thumbnail image. To understand what this looks like, have a peek at Lokesh Dhakar's [examples](http://www.huddletogether.com/projects/lightbox/). It will also have a comment block attached if a description has been attached to the picture in Flickr or the caption attribute is used. For theme writers, the link is enclosed in a div tag with a "lightboxplugin" class. Because this filter requires javascript and css include files, it will only work with themes using the `<%= page_header %>` convenience function in their layouts. As of this writing only Azure does this. This macro takes a number of parameters: Flickr attributes: * **img** The Flickr image ID of the picture that you wish to use. This shows up in the URL whenever you're viewing a picture in Flickr; for example, the image ID for is 31367273. * **thumbsize** The image size that you'd like to display. Typically you would use square, thumbnail or small. Options are: * square (75x75) * thumbnail (maximum size 100 pixels) * small (maximum size 240 pixels) * medium (maximum size 500 pixels) * large (maximum size 1024 pixels) * original * **displaysize** The image size for the lightbox overlay shown when the user clicks the thumbnail. Options are the same as for thumbsize, but typically you would use medium or large. If your image files are quite large on Flickr you probably want to avoid using original. Direct URL attributes: * **src** The URL to the picture you wish to use. * **thumbsrc** The URL to the thumbnail you would like to use. If this is not provided, the original picture will be used with the width and height properties of the `` tag set to 100x100. Common attributes: * **style** This is passed through to the enclosing `
` that this macro generates. To float the image on the right, use `style="float:right"`. * **caption** The caption displayed below the image. By default, this is Flickr's description of the image. to disable, use `caption=""`. * **title** The tooltip title associated with the image. Defaults to Flickr's image title. * **alt** The alt text associated with the image. By default, this is the same as the title. * **set** Add image to a set * **class** adds an existing CSS class TXT end def self.macrofilter(attrib, _text = "") # FIXME: style is not used # style = attrib['style'] caption = attrib["caption"] title = attrib["title"] alt = attrib["alt"] theclass = attrib["class"] set = attrib["set"] thumburl = "" displayurl = "" img = attrib["img"] if img thumbsize = attrib["thumbsize"] || "square" displaysize = attrib["displaysize"] || "original" flickrimage = flickr.photos.getInfo(photo_id: img) sizes = flickr.photos.getSizes(photo_id: img) thumbdetails = sizes.find { |s| s["label"].casecmp(thumbsize.downcase).zero? } || sizes.first displaydetails = sizes.find { |s| s["label"].casecmp(displaysize.downcase).zero? } || sizes.first width = thumbdetails["width"] height = thumbdetails["height"] # use protocol-relative URL after getting the source address # so not to break HTTPS support thumburl = thumbdetails["source"].sub(/^https?:/, "") displayurl = displaydetails["source"].sub(/^https?:/, "") caption ||= flickrimage.description title ||= flickrimage.title alt ||= title else thumburl = attrib["thumbsrc"] unless attrib["thumbsrc"].nil? displayurl = attrib["src"] unless attrib["src"].nil? if thumburl.empty? thumburl = displayurl width = 100 height = 100 else width = height = nil end end rel = set.blank? ? "lightbox" : "lightbox[#{set}]" captioncode = if caption.blank? "" else "

#{caption}

" end img_attrs = %(src="#{thumburl}") img_attrs << %( class="#{theclass}") if theclass img_attrs << %( width="#{width}") if width img_attrs << %( height="#{height}") if height img_attrs << %( alt="#{alt}" title="#{title}") "" \ "#{captioncode}" end end end end ================================================ FILE: lib/publify_plugins/avatar_plugin.rb ================================================ # frozen_string_literal: true require "publify_plugins" module PublifyPlugins class AvatarPlugin < Base def self.kind :avatar end def self.get_avatar(_options = {}) raise NotImplementedError end def self.name raise NotImplementedError end end end ================================================ FILE: lib/publify_plugins/gravatar.rb ================================================ # frozen_string_literal: true require "publify_plugins" require "publify_plugins/avatar_plugin" # PublifyAvatarGravatar module PublifyPlugins class Gravatar < AvatarPlugin extend ActionView::Helpers::TagHelper @description = "Provide user avatar image throught the http://gravatar.com service." class << self def get_avatar(options = {}) email = options.delete(:email) || "" gravatar_tag(email, options) end def name "Gravatar" end private # Generate the image tag for a commenters gravatar based on their email address # Valid options are described at http://www.gravatar.com/implement.php def gravatar_tag(email, options = {}) opts = {} opts[:gravatar_id] = Digest::MD5.hexdigest(email.strip) opts[:default] = CGI.escape(options[:default]) if options.include?(:default) opts[:size] = options[:size] || 48 klass = options[:class] || "avatar gravatar" url = +"https://www.gravatar.com/avatar.php?" url << opts.map { |key, value| "#{key}=#{value}" }.sort.join("&") tag.img(src: url, class: klass, alt: "Gravatar") end end end end PublifyPlugins::Keeper.register(PublifyPlugins::Gravatar) ================================================ FILE: lib/tasks/i18n.rake ================================================ # frozen_string_literal: true require "English" namespace :i18n do desc "Check translation health" task :health do `bin/i18n-tasks health` abort("Translation problems found") unless $CHILD_STATUS.success? end end task lint: "i18n:health" ================================================ FILE: lib/tasks/rubocop.rake ================================================ # frozen_string_literal: true begin require "rubocop/rake_task" RuboCop::RakeTask.new do |task| task.options << "--display-cop-names" end task lint: :rubocop desc "Regenerate RuboCop to-do file" task "rubocop:regenerate_todos" do sh "rubocop --regenerate-todo" end rescue LoadError # No rubocop available nil end ================================================ FILE: log/.keep ================================================ ================================================ FILE: public/404.html ================================================ The page you were looking for doesn't exist (404)

The page you were looking for doesn't exist.

You may have mistyped the address or the page may have moved.

If you are the application owner check the logs for more information.

================================================ FILE: public/422.html ================================================ The change you wanted was rejected (422)

The change you wanted was rejected.

Maybe you tried to change something you didn't have access to.

If you are the application owner check the logs for more information.

================================================ FILE: public/500.html ================================================ We're sorry, but something went wrong (500)

We're sorry, but something went wrong.

If you are the application owner check the logs for more information.

================================================ FILE: spec/controllers/articles_controller_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" # Test article rendering for installed themes RSpec.describe ArticlesController, type: :controller do render_views with_each_theme do |theme, _view_path| context "with theme #{theme}" do let!(:blog) { create(:blog, theme: theme) } describe "#redirect" do let(:article) { create(:article) } let(:from_param) { article.permalink_url.sub(%r{#{blog.base_url}/}, "") } it "successfully renders an article" do get :redirect, params: { from: from_param } expect(response).to be_successful end context "when the article has an excerpt" do let(:article) { create(:article, excerpt: "foo", body: "bar") } it "does not render a continue reading link" do get :redirect, params: { from: from_param } aggregate_failures do expect(response.body).to have_text "bar" expect(response.body).not_to have_text "foo" expect(response.body) .not_to have_text I18n.t!("articles.article_excerpt.continue_reading") end end end describe "accessing an article" do let!(:article) { create(:article) } before do get :redirect, params: { from: from_param } end it "has good rss feed link" do expect(response.body) .to have_css("head>link[href=\"#{article.permalink_url}.rss\"]", visible: :all) end it "has good atom feed link" do expect(response.body) .to have_css("head>link[href=\"#{article.permalink_url}.atom\"]", visible: :all) end it "has a canonical url" do expect(response.body) .to have_css("head>link[href='#{article.permalink_url}']", visible: :all) end it "has a good title" do expect(response.body) .to have_css("title", text: "A big article | test blog", visible: :all) end end describe "theme rendering" do let!(:article) { create(:article) } it "renders without errors when no comments or trackbacks are present" do get :redirect, params: { from: from_param } expect(response).to be_successful end it "renders without errors when recaptcha is enabled" do Recaptcha.configure do |config| config.site_key = "YourAPIkeysHere_yyyyyyyyyyyyyyyyy" config.secret_key = "YourAPIkeysHere_xxxxxxxxxxxxxxxxx" end blog.use_recaptcha = true blog.save! get :redirect, params: { from: from_param } expect(response).to be_successful end it "renders without errors when comments and trackbacks are present" do create(:trackback, article: article) create(:comment, article: article) get :redirect, params: { from: from_param } expect(response).to be_successful end end context "when the article is password protected" do let(:article) do create(:article, title: "Secretive", body: "protected foobar", password: "password") end it "shows a password form for the article" do get :redirect, params: { from: from_param } expect(response.body).to have_field "article_password" end it "does not include the article body anywhere" do get :redirect, params: { from: from_param } expect(response.body).not_to include article.body end end end describe "#index" do let!(:user) { create(:user) } context "without any parameters" do let!(:article) { create(:article) } let!(:note) { create(:note) } before do get :index end it "has good link feed rss" do expect(response.body) .to have_css('head>link[href="http://test.host/articles.rss"]', visible: :all) end it "has good link feed atom" do expect(response.body) .to have_css('head>link[href="http://test.host/articles.atom"]', visible: :all) end it "has a canonical url" do expect(response.body) .to have_css("head>link[href='#{blog.base_url}/']", visible: :all) end it "has good title" do expect(response.body) .to have_css("title", text: "test blog | test subtitle", visible: :all) end end context "when an article has an excerpt" do let!(:article) { create(:article, excerpt: "foo", body: "bar") } it "renders a continue reading link" do get :index aggregate_failures do expect(response.body).not_to have_text "bar" expect(response.body).to have_text "foo" expect(response.body) .to have_text I18n.t!("articles.article_excerpt.continue_reading") end end end context "when requesting archives for a month" do before do create(:article, published_at: Time.utc(2004, 4, 23)) get "index", params: { year: 2004, month: 4 } end it "has a canonical url" do expect(response.body) .to have_css("head>link[href='#{blog.base_url}/2004/4']", visible: :all) end it "has a good title" do expect(response.body) .to have_css("title", text: "Archives for test blog", visible: :all) end end end describe "#search", "with a markdown formatted article" do let!(:user) { create(:user) } before do create(:article, body: <<~MARKDOWN, in markdown format * we * use [ok](http://blog.ok.com) to define a link MARKDOWN text_filter_name: "markdown") create(:article, body: "xyz") get :search, params: { q: "a" } end it "renders content with markdown interpreted and html tags removed" do expect(response.body) .to have_css( "div", text: /in markdown format\s+we\s+use\s+ok to define a link/) end end describe "#search" do render_views let!(:blog) { create(:blog) } let!(:user) { create(:user) } let!(:matching_article) { create(:article, body: "public foobar") } let!(:not_matching_article) { create(:article, body: "barbaz") } let!(:protected_article) do create(:article, body: "protected foobar", password: "secret!") end it "renders result with only matching articles" do get :search, params: { q: "oba" } aggregate_failures do expect(response).to render_template(:search) expect(assigns[:articles]) .to contain_exactly matching_article, protected_article expect(response.body).to have_text "public foobar" expect(response.body).not_to have_text "protected foobar" end end it "has good rss feed link" do get :search, params: { q: "oba" } expect(response.body) .to have_css('head>link[href="http://test.host/search/oba.rss"]', visible: :all) end it "has good atom feed link" do get :search, params: { q: "oba" } expect(response.body) .to have_css('head>link[href="http://test.host/search/oba.atom"]', visible: :all) end it "has a canonical url" do get :search, params: { q: "oba" } expect(response.body) .to have_css("head>link[href='#{blog.base_url}/search/oba']", visible: :all) end it "has a good title" do get :search, params: { q: "oba" } expect(response.body) .to have_css("title", text: "Results for oba | test blog", visible: :all) end it "renders feed rss by search" do get "search", params: { q: "oba", format: "rss" } aggregate_failures do expect(response).to be_successful expect(response).to render_template("index_rss_feed", layout: false) expect(response.body).to have_text "public foobar" expect(response.body).not_to have_text "protected foobar" end end it "renders feed atom by search" do get "search", params: { q: "oba", format: "atom" } aggregate_failures do expect(response).to be_successful expect(response).to render_template("index_atom_feed", layout: false) expect(response.body).to have_text "public foobar" expect(response.body).not_to have_text "protected foobar" end end it "search with empty result" do get "search", params: { q: "abcdefghijklmnopqrstuvwxyz" } expect(response).to render_template("articles/error", layout: false) expect(assigns[:articles]).to eq [] end end describe "#livesearch" do before do create(:article, body: "hello world and im herer") create(:article, title: "hello", body: "worldwide") create(:article) get :live_search, params: { q: "hello world" } end it "does not have h3 tag" do expect(response.body).to have_css("h3") end end describe "#archives" do context "with several articles" do let!(:articles) { create_list(:article, 3) } before do get "archives" end it "has the correct self-link and title" do expect(response.body) .to have_css("head>link[href='#{blog.base_url}/archives']", visible: :all) .and have_css("title", text: "Archives for test blog", visible: :all) end it "shows the current month only once" do expect(response.body) .to have_css("h3", count: 1) .and have_text I18n.l(articles.first.published_at, format: :letters_month_with_year) end end context "with an article with tags" do it "renders correctly" do create(:article, keywords: "foo, bar") get "archives" expect(response.body).to have_text "foo" expect(response.body).to have_text "bar" end end end describe "#preview" do context "with logged user" do let(:admin) { create(:user, :as_admin) } let(:article) { create(:article, user: admin) } before do sign_in admin end it "renders the regular read template" do get :preview, params: { id: article.id } expect(response).to render_template("articles/read") end context "when the article has an excerpt" do let(:article) { create(:article, excerpt: "foo", body: "bar", user: admin) } it "does not render a continue reading link" do get :preview, params: { id: article.id } aggregate_failures do expect(response.body).to have_text "bar" expect(response.body).not_to have_text "foo" expect(response.body) .not_to have_text I18n.t!("articles.article_excerpt.continue_reading") end end end end end describe "#check_password" do let!(:article) { create(:article, password: "password") } it "shows article when given correct password" do post :check_password, xhr: true, params: { article: { id: article.id, password: article.password } } expect(response.body).not_to have_field "article_password" end it "shows password form when given incorrect password" do post :check_password, xhr: true, params: { article: { id: article.id, password: "wrong password" } } expect(response.body).to have_field "article_password" end end end end end ================================================ FILE: spec/controllers/feedback_controller_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe FeedbackController, type: :controller do render_views describe "index" do let!(:items) do [ create(:comment, state: :presumed_ham), create(:comment), create(:trackback, title: "some"), create(:trackback, title: "items") ] end let!(:spammy_items) do [ create(:spam_comment), create(:trackback, state: "spam") ] end context "with atom format" do before { get "index", params: { format: "atom" } } it "renders a valid atom feed with 4 items" do assert_atom10 response.body, 4 end it "renders each item with the correct template" do expect(response) .to render_template(partial: "shared/_atom_item_comment", count: 2) .and render_template(partial: "shared/_atom_item_trackback", count: 2) end end context "with rss format" do before { get "index", params: { format: "rss" } } it "renders a valid rss feed with 4 items" do assert_rss20 response.body, 4 end it "renders each item with the correct template" do expect(response) .to render_template(partial: "shared/_rss_item_comment", count: 2) .and render_template(partial: "shared/_rss_item_trackback", count: 2) end end end end ================================================ FILE: spec/controllers/tags_controller_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe TagsController, type: :controller do render_views let(:blog) { create(:blog) } with_each_theme do |theme, _view_path| context "with theme #{theme}" do before do blog.theme = theme blog.save end describe "#index" do before do @tag = create(:tag) @tag.contents << create(:article) end it "lists tags" do get "index" expect(response.body).to have_text @tag.name end end describe "#show" do let(:parsed_body) { Capybara.string(response.body) } let(:article) { create(:article) } before do create(:tag, name: "foo", contents: [article]) get "show", params: { id: "foo" } end it "has good rss feed link in head" do rss_link = parsed_body .find "head>link[href='http://test.host/tag/foo.rss']", visible: false aggregate_failures do expect(rss_link["rel"]).to eq "alternate" expect(rss_link["type"]).to eq "application/rss+xml" expect(rss_link["title"]).to eq "RSS" end end it "has good atom feed link in head" do atom_link = parsed_body .find "head>link[href='http://test.host/tag/foo.atom']", visible: false aggregate_failures do expect(atom_link["rel"]).to eq "alternate" expect(atom_link["type"]).to eq "application/atom+xml" expect(atom_link["title"]).to eq "Atom" end end it "has a canonical URL" do expect(response.body) .to have_css("head>link[href='#{blog.base_url}/tag/foo']", visible: :all) end context "with a password protected article" do let(:article) { create(:article, password: "password") } it "article in tag should be password protected" do get "show", params: { id: "foo" } assert_select('input[id="article_password"]') end end end end end end ================================================ FILE: spec/controllers/xml_controller_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe XmlController, type: :controller do render_views before do create(:blog, base_url: "http://myblog.net") end describe "#sitemap" do before do tag = create(:tag) article = create(:article) article.tags = [tag] end it "returns a valid XML response" do get :sitemap, format: :googlesitemap assert_xml @response.body end end end ================================================ FILE: spec/features/switch_theme_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.feature "Changing themes", type: :feature do let(:admin) { create(:user, :as_admin) } let(:blog) { Blog.first } before do load Rails.root.join("db/seeds.rb") Blog.first.update blog_name: "Awesome!", base_url: "http://www.example.com/" end scenario "switching themes by clicking links in the themes admin" do sign_in admin visit "/admin/themes" expect(page).to have_text "plain - Active theme" click_link_or_button "Use this theme" expect(page).to have_text "bootstrap-2 - Active theme" end end ================================================ FILE: spec/lib/publify_plugins/gravatar_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe PublifyPlugins::Gravatar do describe ".get_avatar" do let(:email) { "foo@bar.baz" } let(:digest) { Digest::MD5.hexdigest(email) } let(:gravatar_tag) { described_class.get_avatar(email: email) } it "returns an html safe string" do expect(gravatar_tag).to be_html_safe end it "returns image tag with the correct URL" do doc = Nokogiri.parse gravatar_tag element = doc.root aggregate_failures do expect(element.name).to eq "img" expect(element.attr("src")) .to eq "https://www.gravatar.com/avatar.php?gravatar_id=#{digest}&size=48" end end end end ================================================ FILE: spec/lib/publify_textfilter_flickr_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe "the Flickr text filter plugin", type: :model do let(:flickr_photos) { double "flickr_photos" } let(:flickr_photo_info_url) do double("flickr_photo_info_url", type: "photopage", to_s: "http://www.flickr.com/users/scottlaird/31366117") end let(:flickr_photo_info) do double("flickr_photo_info", description: "This is Matz, Ruby's creator", title: "Matz", urls: [flickr_photo_info_url]) end let(:flickr_photo_sizes) do [ { "url" => "http://www.flickr.com/photo_zoom.gne?id=31366117&size=sq", "height" => "75", "source" => "http://photos23.flickr.com/31366117_b1a791d68e_s.jpg", "label" => "Square", "width" => "75" }, { "url" => "http://www.flickr.com/photo_zoom.gne?id=31366117&size=t", "height" => "100", "source" => "http://photos23.flickr.com/31366117_b1a791d68e_t.jpg", "label" => "Thumbnail", "width" => "67" }, { "url" => "http://www.flickr.com/photo_zoom.gne?id=31366117&size=s", "height" => "240", "source" => "http://photos23.flickr.com/31366117_b1a791d68e_m.jpg", "label" => "Small", "width" => "160" }, { "url" => "http://www.flickr.com/photo_zoom.gne?id=31366117&size=m", "height" => "500", "source" => "http://photos23.flickr.com/31366117_b1a791d68e.jpg", "label" => "Medium", "width" => "333" }, { "url" => "http://www.flickr.com/photo_zoom.gne?id=31366117&size=l", "height" => "1024", "source" => "http://photos23.flickr.com/31366117_b1a791d68e_b.jpg", "label" => "Large", "width" => "683" }, { "url" => "http://www.flickr.com/photo_zoom.gne?id=31366117&size=o", "height" => "1536", "source" => "http://photos23.flickr.com/31366117_b1a791d68e_o.jpg", "label" => "Original", "width" => "1024" } ] end before do allow(flickr).to receive(:photos).and_return flickr_photos allow(flickr_photos).to receive(:getInfo).and_raise "Photo not found" allow(flickr_photos).to receive(:getInfo).with(photo_id: "31366117") .and_return flickr_photo_info allow(flickr_photos).to receive(:getSizes).and_return flickr_photo_sizes end context "when combined the plain text filter" do let(:filter) { TextFilter.none } it "shows with default settings" do result = filter .filter_text('') expect(result).to eq '
' \ '' \ 'Matz' \ "

This is Matz," \ " Ruby's creator

" end it "uses default image size" do result = filter.filter_text('') expect(result).to eq '
' \ '' \ 'Matz' \ "

This is Matz," \ " Ruby's creator

" end it "uses caption" do result = filter.filter_text('') expect(result).to eq '
' \ '' \ 'Matz
' end it "broken_flickr_link" do result = filter.filter_text('') expect(result).to eq "" end end context "when combined with markdown" do let(:filter) { TextFilter.markdown } it "correctly interprets the macro" do result = filter .filter_text('') expect(result).to eq '
' \ '' \ 'Matz' \ "

This is Matz," \ " Ruby's creator

" end end end ================================================ FILE: spec/lib/publify_textfilter_lightbox_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe "the Lightbox text filter plugin", type: :model do let(:flickr_photos) { double "flickr_photos" } let(:flickr_photo_info_url) do double("flickr_photo_info_url", type: "photopage", to_s: "http://www.flickr.com/users/scottlaird/31366117") end let(:flickr_photo_info) do double("flickr_photo_info", description: "This is Matz, Ruby's creator", title: "Matz", urls: [flickr_photo_info_url]) end let(:flickr_photo_sizes) do [ { "url" => "http://www.flickr.com/photo_zoom.gne?id=31366117&size=sq", "height" => "75", "source" => "http://photos23.flickr.com/31366117_b1a791d68e_s.jpg", "label" => "Square", "width" => "75" }, { "url" => "http://www.flickr.com/photo_zoom.gne?id=31366117&size=t", "height" => "100", "source" => "http://photos23.flickr.com/31366117_b1a791d68e_t.jpg", "label" => "Thumbnail", "width" => "67" }, { "url" => "http://www.flickr.com/photo_zoom.gne?id=31366117&size=s", "height" => "240", "source" => "http://photos23.flickr.com/31366117_b1a791d68e_m.jpg", "label" => "Small", "width" => "160" }, { "url" => "http://www.flickr.com/photo_zoom.gne?id=31366117&size=m", "height" => "500", "source" => "http://photos23.flickr.com/31366117_b1a791d68e.jpg", "label" => "Medium", "width" => "333" }, { "url" => "http://www.flickr.com/photo_zoom.gne?id=31366117&size=l", "height" => "1024", "source" => "http://photos23.flickr.com/31366117_b1a791d68e_b.jpg", "label" => "Large", "width" => "683" }, { "url" => "http://www.flickr.com/photo_zoom.gne?id=31366117&size=o", "height" => "1536", "source" => "http://photos23.flickr.com/31366117_b1a791d68e_o.jpg", "label" => "Original", "width" => "1024" } ] end before do allow(flickr).to receive(:photos).and_return flickr_photos allow(flickr_photos).to receive(:getInfo).and_raise "Photo not found" allow(flickr_photos).to receive(:getInfo).with(photo_id: "31366117") .and_return flickr_photo_info allow(flickr_photos).to receive(:getSizes).and_return flickr_photo_sizes end context "when combined the plain text filter" do let(:filter) { TextFilter.none } it "uses the given thumb image size" do result = filter .filter_text('') expect(result) .to eq '' \ 'Matz' \ "

This is Matz, Ruby's creator

" end it "uses default thumb image size if none is given" do result = filter .filter_text('') expect(result) .to eq '' \ 'Matz' \ "

This is Matz, Ruby's creator

" end it "uses default display image size if none is given" do result = filter.filter_text('') expect(result) .to eq '' \ 'Matz' \ "

This is Matz, Ruby's creator

" end it "works with caption" do result = filter.filter_text('') expect(result) .to eq '' \ 'Matz' end end end ================================================ FILE: spec/lib/text_filter_plugin_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe TextFilterPlugin do describe ".available_filters" do subject { described_class.available_filters } it "lists usable filters" do expect(described_class.available_filters).to contain_exactly( PublifyCore::TextFilter::None, PublifyCore::TextFilter::Markdown, PublifyCore::TextFilter::Smartypants, PublifyCore::TextFilter::MarkdownSmartquotes, PublifyApp::Textfilter::Htmlfilter, PublifyApp::Textfilter::Flickr, PublifyApp::Textfilter::Code, PublifyApp::Textfilter::Lightbox) end end describe ".macro_filters" do subject { described_class.macro_filters } it "lists the macro filters" do expect(described_class.macro_filters).to contain_exactly( PublifyApp::Textfilter::Flickr, PublifyApp::Textfilter::Code, PublifyApp::Textfilter::Lightbox) end end end ================================================ FILE: spec/models/authors_sidebar_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe AuthorsSidebar do let(:sidebar) { described_class.new } it "is included in the list of available sidebars" do expect(SidebarRegistry.available_sidebars).to include(described_class) end describe "#authors" do let!(:authors) { create_list(:user, 2) } it "returns a list of users" do expect(sidebar.authors).to match_array authors end end end ================================================ FILE: spec/models/notes_sidebar_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe NotesSidebar do it "is available" do expect(SidebarRegistry.available_sidebars).to include(described_class) end end ================================================ FILE: spec/models/popular_sidebar_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe PopularSidebar do it "is available" do expect(SidebarRegistry.available_sidebars).to include(described_class) end end ================================================ FILE: spec/models/sidebar_registry_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe SidebarRegistry do describe "#available_sidebars" do it "finds at least the standard sidebars" do expect(described_class.available_sidebars) .to include(AmazonSidebar, ArchivesSidebar, AuthorsSidebar, LivesearchSidebar, MetaSidebar, NotesSidebar, PageSidebar, PopularSidebar, SearchSidebar, StaticSidebar, TagSidebar, XmlSidebar) end end end ================================================ FILE: spec/models/xml_sidebar_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe XmlSidebar do it "is available" do expect(SidebarRegistry.available_sidebars).to include(described_class) end end ================================================ FILE: spec/rails_helper.rb ================================================ # frozen_string_literal: true # This file is copied to spec/ when you run 'rails generate rspec:install' require "spec_helper" ENV["RAILS_ENV"] ||= "test" require File.expand_path("../config/environment", __dir__) # Prevent database truncation if the environment is production abort("The Rails environment is running in production mode!") if Rails.env.production? require "rspec/rails" # Add additional requires below this line. Rails is not loaded until this point! require "factory_bot" require "publify_core/testing_support/feed_assertions" require "publify_core/testing_support/factories/articles" require "publify_core/testing_support/factories/blogs" require "publify_core/testing_support/factories/comments" require "publify_core/testing_support/factories/contents" require "publify_core/testing_support/factories/notes" require "publify_core/testing_support/factories/pages" require "publify_core/testing_support/factories/post_types" require "publify_core/testing_support/factories/redirects" require "publify_core/testing_support/factories/resources" require "publify_core/testing_support/factories/sequences" require "publify_core/testing_support/factories/sidebars" require "publify_core/testing_support/factories/tags" require "publify_core/testing_support/factories/trackbacks" require "publify_core/testing_support/factories/users" # Requires supporting ruby files with custom matchers and macros, etc, in # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are # run as spec files by default. This means that files in spec/support that end # in _spec.rb will both be required and run as specs, causing the specs to be # run twice. It is recommended that you do not name files matching this glob to # end with _spec.rb. You can configure this pattern with the --pattern # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. # # The following line is provided for convenience purposes. It has the downside # of increasing the boot-up time by auto-requiring all files in the support # directory. Alternatively, in the individual `*_spec.rb` files, manually # require only the support files necessary. Rails.root.glob("spec/support/**/*.rb").each { |f| require f } # Checks for pending migrations and applies them before tests are run. # If you are not using ActiveRecord, you can remove this line. ActiveRecord::Migration.maintain_test_schema! class ActionView::TestCase::TestController include Rails.application.routes.url_helpers end RSpec.configure do |config| # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures config.fixture_path = Rails.root.join("spec/fixtures") # If you're not using ActiveRecord, or you'd prefer not to run each of your # examples within a transaction, remove the following line or assign false # instead of true. config.use_transactional_fixtures = true # You can uncomment this line to turn off ActiveRecord support entirely. # config.use_active_record = false # RSpec Rails can automatically mix in different behaviours to your tests # based on their file location, for example enabling you to call `get` and # `post` in specs under `spec/controllers`. # # You can disable this behaviour by removing the line below, and instead # explicitly tag your specs with their type, e.g.: # # RSpec.describe UsersController, type: :controller do # # ... # end # # The different available types are documented in the features, such as in # https://relishapp.com/rspec/rspec-rails/docs # config.infer_spec_type_from_file_location! # Filter lines from Rails gems in backtraces. config.filter_rails_from_backtrace! # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") # shortcuts for factory_bot to use: create / build / build_stubbed config.include FactoryBot::Syntax::Methods # Test helpers needed for Devise config.include Devise::Test::ControllerHelpers, type: :controller config.include Devise::Test::IntegrationHelpers, type: :feature # Test helpers to check feed contents config.include PublifyCore::TestingSupport::FeedAssertions, type: :controller config.after :each, type: :controller do if response.media_type == "text/html" && response.body =~ /(<[a-z]+)/ raise "Double escaped HTML in text (#{Regexp.last_match(1)})" end end config.include FactoryBot::Syntax::Methods end # Test installed themes def with_each_theme Theme.find_all.each do |theme| theme_dir = theme.path view_path = "#{theme_dir}/views" helper_file = "#{theme_dir}/helpers/theme_helper.rb" require helper_file if File.exist?(helper_file) yield theme.name, view_path end end ================================================ FILE: spec/spec_helper.rb ================================================ # frozen_string_literal: true # This file was generated by the `rails generate rspec:install` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause # this file to always be loaded, without a need to explicitly require it in any # files. # # Given that it is always loaded, you are encouraged to keep this file as # light-weight as possible. Requiring heavyweight dependencies from this file # will add to the boot time of your test suite on EVERY test run, even for an # individual file that may not need all of that loaded. Instead, consider making # a separate helper file that requires the additional dependencies and performs # the additional setup, and require it from the spec files that actually need # it. # # The `.rspec` file also contains a few flags that are not defaults but that # users commonly want. # # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration require "simplecov" SimpleCov.start "rails" RSpec.configure do |config| # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest # assertions if you prefer. config.expect_with :rspec do |expectations| # This option will default to `true` in RSpec 4. It makes the `description` # and `failure_message` of custom matchers include text for helper methods # defined using `chain`, e.g.: # be_bigger_than(2).and_smaller_than(4).description # # => "be bigger than 2 and smaller than 4" # ...rather than: # # => "be bigger than 2" expectations.include_chain_clauses_in_custom_matcher_descriptions = true end # rspec-mocks config goes here. You can use an alternate test double # library (such as bogus or mocha) by changing the `mock_with` option here. config.mock_with :rspec do |mocks| # Prevents you from mocking or stubbing a method that does not exist on # a real object. This is generally recommended, and will default to # `true` in RSpec 4. mocks.verify_partial_doubles = true end # This option will default to `:apply_to_host_groups` in RSpec 4 (and will # have no way to turn it off -- the option exists only for backwards # compatibility in RSpec 3). It causes shared context metadata to be # inherited by the metadata hash of host groups and examples, rather than # triggering implicit auto-inclusion in groups with matching metadata. config.shared_context_metadata_behavior = :apply_to_host_groups # This allows you to limit a spec run to individual examples or groups # you care about by tagging them with `:focus` metadata. When nothing # is tagged with `:focus`, all examples get run. RSpec also provides # aliases for `it`, `describe`, and `context` that include `:focus` # metadata: `fit`, `fdescribe` and `fcontext`, respectively. config.filter_run_when_matching :focus # Allows RSpec to persist some state between runs in order to support # the `--only-failures` and `--next-failure` CLI options. We recommend # you configure your source control system to ignore this file. config.example_status_persistence_file_path = "spec/examples.txt" # Limits the available syntax to the non-monkey patched syntax that is # recommended. For more details, see: # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode config.disable_monkey_patching! # Allows RSpec to persist some state between runs in order to support # the `--only-failures` and `--next-failure` CLI options. We recommend # you configure your source control system to ignore this file. config.example_status_persistence_file_path = "spec/examples.txt" # Many RSpec users commonly either run the entire suite or an individual # file, and it's useful to allow more verbose output when running an # individual spec file. if config.files_to_run.one? # Use the documentation formatter for detailed output, # unless a formatter has already been configured # (e.g. via a command-line flag). config.default_formatter = "doc" end # Print the 10 slowest examples and example groups at the # end of the spec run, to help surface which specs are running # particularly slow. config.profile_examples = 10 # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = :random # Seed global randomization in this process using the `--seed` CLI option. # Setting this allows you to use `--seed` to deterministically reproduce # test failures related to randomization by passing the same `--seed` value # as the one that triggered the failure. Kernel.srand config.seed end ================================================ FILE: spec/views/comments/html_sanitization_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe "comments/_comment.html.erb", type: :view do shared_examples_for "CommentSanitization" do let(:blog) { build_stubbed(:blog) } let(:article) do build_stubbed(:article, created_at: Time.zone.now, published_at: Time.zone.now, blog: blog) end let(:base_options) do { body: "test foo ", author: "Bob", article: article, created_at: Time.zone.now } end before do blog.plugin_avatar = "" blog.lang = "en_US" @comment = Comment.new base_options.merge(comment_options) allow(@comment).to receive(:id).and_return(1) assign(:comment, @comment) end with_each_theme do |theme, _view_path| context "with theme #{theme}" do before do blog.theme = theme end ["", "markdown", "smartypants", "markdown smartypants"].each do |value| it "sanitizes content rendered with the #{value} textfilter" do blog.comment_text_filter = value ActiveSupport::Deprecation.silence do render partial: "comments/comment", locals: { comment: @comment } end expect(rendered).to have_css(".content") expect(rendered).to have_css(".author") expect(rendered).not_to have_css(".content script") expect(rendered).not_to have_css(".content a:not([rel=nofollow])") # No links with javascript expect(rendered).not_to have_css(".content a[onclick]") expect(rendered).not_to have_css('.content a[href^="javascript:"]') expect(rendered).not_to have_css(".author script") expect(rendered).not_to have_css(".author a:not([rel=nofollow])") # No links with javascript expect(rendered).not_to have_css(".author a[onclick]") expect(rendered).not_to have_css('.author a[href^="javascript:"]') end end end end end describe "First dodgy comment", type: :view do it_behaves_like "CommentSanitization" def comment_options { body: "test foo " } end end describe "Second dodgy comment", type: :view do it_behaves_like "CommentSanitization" def comment_options { body: "link to [spammy goodness](http://spammer.example.com)" } end end describe "Dodgy comment #3", type: :view do it_behaves_like "CommentSanitization" def comment_options { body: 'link to spammy goodness' } end end describe "Extra Dodgy comment", type: :view do it_behaves_like "CommentSanitization" def comment_options { body: 'spam', author: 'its all spam' } end end describe "XSS with script tag", type: :view do it_behaves_like "CommentSanitization" def comment_options { body: 'Have you ever ' \ " been hacked?" } end end describe "XSS with onclick attribute", type: :view do it_behaves_like "CommentSanitization" def comment_options { body: %(bad link) } end end describe "XSS with javascript url", type: :view do it_behaves_like "CommentSanitization" def comment_options { body: %(bad link) } end end describe "Comment with bare http URL", type: :view do it_behaves_like "CommentSanitization" def comment_options { body: %(http://www.example.com) } end end describe "Comment with bare email address", type: :view do it_behaves_like "CommentSanitization" def comment_options { body: %(foo@example.com) } end end shared_examples_for "CommentSanitizationWithDofollow" do let(:blog) { create(:blog) } let(:article) do create(:article, created_at: Time.zone.now, published_at: Time.zone.now, blog: blog) end let(:base_options) do { body: "test foo ", author: "Bob", article: article, created_at: Time.zone.now } end before do blog.plugin_avatar = "" blog.lang = "en_US" blog.dofollowify = true blog.save @comment = Comment.new base_options.merge(comment_options) allow(@comment).to receive(:id).and_return(1) assign(:comment, @comment) end with_each_theme do |theme, _view_path| context "with theme #{theme}" do before do blog.theme = theme end ["", "markdown", "smartypants", "markdown smartypants"].each do |value| it "sanitizes content rendered with the #{value} textfilter" do blog.comment_text_filter = value ActiveSupport::Deprecation.silence do render partial: "comments/comment", locals: { comment: @comment } end expect(rendered).to have_css(".content") expect(rendered).to have_css(".author") expect(rendered).not_to have_css(".content script") expect(rendered).not_to have_css(".content a[rel=nofollow]") # No links with javascript expect(rendered).not_to have_css(".content a[onclick]") expect(rendered).not_to have_css('.content a[href^="javascript:"]') expect(rendered).not_to have_css(".author script") expect(rendered).not_to have_css(".author a[rel=nofollow]") # No links with javascript expect(rendered).not_to have_css(".author a[onclick]") expect(rendered).not_to have_css('.author a[href^="javascript:"]') end end end end end describe "First dodgy comment with dofollow", type: :view do it_behaves_like "CommentSanitizationWithDofollow" def comment_options { body: "test foo " } end end describe "Second dodgy comment with dofollow", type: :view do it_behaves_like "CommentSanitizationWithDofollow" def comment_options { body: "link to [spammy goodness](http://spammer.example.com)" } end end describe "Dodgy comment #3 with dofollow", type: :view do it_behaves_like "CommentSanitizationWithDofollow" def comment_options { body: 'link to spammy goodness' } end end describe "Extra Dodgy comment with dofollow", type: :view do it_behaves_like "CommentSanitizationWithDofollow" def comment_options { body: 'spam', author: 'its all spam' } end end describe "XSS with script tag and dofollow", type: :view do it_behaves_like "CommentSanitizationWithDofollow" def comment_options { body: 'Have you ever ' \ " been hacked?" } end end describe "XSS with onclick attribute and dofollow", type: :view do it_behaves_like "CommentSanitizationWithDofollow" def comment_options { body: %(bad link) } end end describe "XSS with javascript url and dofollow", type: :view do it_behaves_like "CommentSanitizationWithDofollow" def comment_options { body: %(bad link) } end end describe "Comment with bare http URL with dofollow", type: :view do it_behaves_like "CommentSanitizationWithDofollow" def comment_options { body: %(http://www.example.com) } end end describe "Comment with bare email address with dofollow", type: :view do it_behaves_like "CommentSanitizationWithDofollow" def comment_options { body: %(foo@example.com) } end end end ================================================ FILE: spec/views/layouts/default_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe "layouts/default.html.erb", type: :view do with_each_theme do |theme, view_path| describe "with theme #{theme}" do before do assign(:keywords, %w(foo bar)) assign(:auto_discovery_url_atom, "") assign(:auto_discovery_url_rss, "") controller.prepend_view_path view_path if theme end it "has keyword meta tag when use_meta_keyword set to true" do create(:blog, use_meta_keyword: true) render expect(rendered).to have_css('head>meta[name="keywords"]', visible: :all) end it "does not have keyword meta tag when use_meta_keyword set to false" do create(:blog, use_meta_keyword: false) render expect(rendered).not_to have_css('head>meta[name="keywords"]', visible: :all) end end end end ================================================ FILE: spec/views/xml_sidebar/_content.html.erb_spec.rb ================================================ # frozen_string_literal: true require "rails_helper" RSpec.describe "xml_sidebar/_content.html.erb", type: :view do let(:sidebar) { XmlSidebar.new } context "by default" do before do render partial: sidebar.content_partial, locals: sidebar.to_locals_hash end it "renders a link to the articles feed" do expect(rendered).to have_css("a[href='#{articles_feed_path(format: "atom")}']") end it "renders a link to the feedback feed" do expect(rendered).to have_css("a[href='#{feedback_index_path(format: "atom")}']") end end context "on an article page" do before do allow(controller).to receive_messages(controller_name: "articles", action_name: "redirect") @article = create(:article) render partial: sidebar.content_partial, locals: sidebar.to_locals_hash end it "renders a link to the article comments feed" do expect(rendered).to have_css("a[href='#{@article.feed_url("atom")}']") end end context "on a tags page" do before do sidebar.tag_feeds = true allow(controller).to receive_messages(controller_name: "tags", action_name: "show") @tag = create(:tag) @auto_discovery_url_atom = "foofoo" render partial: sidebar.content_partial, locals: sidebar.to_locals_hash end it "renders a link to the tag feed" do expect(rendered).to have_css("a[href='#{@tag.feed_url("atom")}']") end end end ================================================ FILE: themes/bootstrap-2/about.markdown ================================================ #### Bootstrap theme for Publify Bootstrap 2 is a theme designed for the [Publify][1] blogging engine by [Frédéric de Villamil][2] using [Bootstrap][3] as a framework for other theme development. It was released under the MIT Licence. Any other material such as sample photos used by the default themes are the property of their creator and licenced under the Creative Commons No Commercial Share Alike licence. ##### Background Credits * [Startup Stock Photos][4] Please view the README.md for theme customization. [1]: http://publify.github.io/ [2]: http://t37.net/ [3]: http://getbootstrap.com/ [4]: http://startupstockphotos.com/ ================================================ FILE: themes/bootstrap-2/javascripts/bootstrap.js ================================================ /*! * Bootstrap v3.2.0 (http://getbootstrap.com) * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ if (typeof jQuery === 'undefined') { throw new Error('Bootstrap\'s JavaScript requires jQuery') } /* ======================================================================== * Bootstrap: transition.js v3.2.0 * http://getbootstrap.com/javascript/#transitions * ======================================================================== * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) // ============================================================ function transitionEnd() { var el = document.createElement('bootstrap') var transEndEventNames = { WebkitTransition : 'webkitTransitionEnd', MozTransition : 'transitionend', OTransition : 'oTransitionEnd otransitionend', transition : 'transitionend' } for (var name in transEndEventNames) { if (el.style[name] !== undefined) { return { end: transEndEventNames[name] } } } return false // explicit for ie8 ( ._.) } // http://blog.alexmaccaw.com/css-transitions $.fn.emulateTransitionEnd = function (duration) { var called = false var $el = this $(this).one('bsTransitionEnd', function () { called = true }) var callback = function () { if (!called) $($el).trigger($.support.transition.end) } setTimeout(callback, duration) return this } $(function () { $.support.transition = transitionEnd() if (!$.support.transition) return $.event.special.bsTransitionEnd = { bindType: $.support.transition.end, delegateType: $.support.transition.end, handle: function (e) { if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) } } }) }(jQuery); /* ======================================================================== * Bootstrap: alert.js v3.2.0 * http://getbootstrap.com/javascript/#alerts * ======================================================================== * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // ALERT CLASS DEFINITION // ====================== var dismiss = '[data-dismiss="alert"]' var Alert = function (el) { $(el).on('click', dismiss, this.close) } Alert.VERSION = '3.2.0' Alert.prototype.close = function (e) { var $this = $(this) var selector = $this.attr('data-target') if (!selector) { selector = $this.attr('href') selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 } var $parent = $(selector) if (e) e.preventDefault() if (!$parent.length) { $parent = $this.hasClass('alert') ? $this : $this.parent() } $parent.trigger(e = $.Event('close.bs.alert')) if (e.isDefaultPrevented()) return $parent.removeClass('in') function removeElement() { // detach from parent, fire event then clean up data $parent.detach().trigger('closed.bs.alert').remove() } $.support.transition && $parent.hasClass('fade') ? $parent .one('bsTransitionEnd', removeElement) .emulateTransitionEnd(150) : removeElement() } // ALERT PLUGIN DEFINITION // ======================= function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.alert') if (!data) $this.data('bs.alert', (data = new Alert(this))) if (typeof option == 'string') data[option].call($this) }) } var old = $.fn.alert $.fn.alert = Plugin $.fn.alert.Constructor = Alert // ALERT NO CONFLICT // ================= $.fn.alert.noConflict = function () { $.fn.alert = old return this } // ALERT DATA-API // ============== $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) }(jQuery); /* ======================================================================== * Bootstrap: button.js v3.2.0 * http://getbootstrap.com/javascript/#buttons * ======================================================================== * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // BUTTON PUBLIC CLASS DEFINITION // ============================== var Button = function (element, options) { this.$element = $(element) this.options = $.extend({}, Button.DEFAULTS, options) this.isLoading = false } Button.VERSION = '3.2.0' Button.DEFAULTS = { loadingText: 'loading...' } Button.prototype.setState = function (state) { var d = 'disabled' var $el = this.$element var val = $el.is('input') ? 'val' : 'html' var data = $el.data() state = state + 'Text' if (data.resetText == null) $el.data('resetText', $el[val]()) $el[val](data[state] == null ? this.options[state] : data[state]) // push to event loop to allow forms to submit setTimeout($.proxy(function () { if (state == 'loadingText') { this.isLoading = true $el.addClass(d).attr(d, d) } else if (this.isLoading) { this.isLoading = false $el.removeClass(d).removeAttr(d) } }, this), 0) } Button.prototype.toggle = function () { var changed = true var $parent = this.$element.closest('[data-toggle="buttons"]') if ($parent.length) { var $input = this.$element.find('input') if ($input.prop('type') == 'radio') { if ($input.prop('checked') && this.$element.hasClass('active')) changed = false else $parent.find('.active').removeClass('active') } if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change') } if (changed) this.$element.toggleClass('active') } // BUTTON PLUGIN DEFINITION // ======================== function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.button') var options = typeof option == 'object' && option if (!data) $this.data('bs.button', (data = new Button(this, options))) if (option == 'toggle') data.toggle() else if (option) data.setState(option) }) } var old = $.fn.button $.fn.button = Plugin $.fn.button.Constructor = Button // BUTTON NO CONFLICT // ================== $.fn.button.noConflict = function () { $.fn.button = old return this } // BUTTON DATA-API // =============== $(document).on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { var $btn = $(e.target) if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') Plugin.call($btn, 'toggle') e.preventDefault() }) }(jQuery); /* ======================================================================== * Bootstrap: carousel.js v3.2.0 * http://getbootstrap.com/javascript/#carousel * ======================================================================== * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // CAROUSEL CLASS DEFINITION // ========================= var Carousel = function (element, options) { this.$element = $(element).on('keydown.bs.carousel', $.proxy(this.keydown, this)) this.$indicators = this.$element.find('.carousel-indicators') this.options = options this.paused = this.sliding = this.interval = this.$active = this.$items = null this.options.pause == 'hover' && this.$element .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) } Carousel.VERSION = '3.2.0' Carousel.DEFAULTS = { interval: 5000, pause: 'hover', wrap: true } Carousel.prototype.keydown = function (e) { switch (e.which) { case 37: this.prev(); break case 39: this.next(); break default: return } e.preventDefault() } Carousel.prototype.cycle = function (e) { e || (this.paused = false) this.interval && clearInterval(this.interval) this.options.interval && !this.paused && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) return this } Carousel.prototype.getItemIndex = function (item) { this.$items = item.parent().children('.item') return this.$items.index(item || this.$active) } Carousel.prototype.to = function (pos) { var that = this var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) if (pos > (this.$items.length - 1) || pos < 0) return if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" if (activeIndex == pos) return this.pause().cycle() return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) } Carousel.prototype.pause = function (e) { e || (this.paused = true) if (this.$element.find('.next, .prev').length && $.support.transition) { this.$element.trigger($.support.transition.end) this.cycle(true) } this.interval = clearInterval(this.interval) return this } Carousel.prototype.next = function () { if (this.sliding) return return this.slide('next') } Carousel.prototype.prev = function () { if (this.sliding) return return this.slide('prev') } Carousel.prototype.slide = function (type, next) { var $active = this.$element.find('.item.active') var $next = next || $active[type]() var isCycling = this.interval var direction = type == 'next' ? 'left' : 'right' var fallback = type == 'next' ? 'first' : 'last' var that = this if (!$next.length) { if (!this.options.wrap) return $next = this.$element.find('.item')[fallback]() } if ($next.hasClass('active')) return (this.sliding = false) var relatedTarget = $next[0] var slideEvent = $.Event('slide.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) this.$element.trigger(slideEvent) if (slideEvent.isDefaultPrevented()) return this.sliding = true isCycling && this.pause() if (this.$indicators.length) { this.$indicators.find('.active').removeClass('active') var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) $nextIndicator && $nextIndicator.addClass('active') } var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" if ($.support.transition && this.$element.hasClass('slide')) { $next.addClass(type) $next[0].offsetWidth // force reflow $active.addClass(direction) $next.addClass(direction) $active .one('bsTransitionEnd', function () { $next.removeClass([type, direction].join(' ')).addClass('active') $active.removeClass(['active', direction].join(' ')) that.sliding = false setTimeout(function () { that.$element.trigger(slidEvent) }, 0) }) .emulateTransitionEnd($active.css('transition-duration').slice(0, -1) * 1000) } else { $active.removeClass('active') $next.addClass('active') this.sliding = false this.$element.trigger(slidEvent) } isCycling && this.cycle() return this } // CAROUSEL PLUGIN DEFINITION // ========================== function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.carousel') var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) var action = typeof option == 'string' ? option : options.slide if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) if (typeof option == 'number') data.to(option) else if (action) data[action]() else if (options.interval) data.pause().cycle() }) } var old = $.fn.carousel $.fn.carousel = Plugin $.fn.carousel.Constructor = Carousel // CAROUSEL NO CONFLICT // ==================== $.fn.carousel.noConflict = function () { $.fn.carousel = old return this } // CAROUSEL DATA-API // ================= $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { var href var $this = $(this) var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 if (!$target.hasClass('carousel')) return var options = $.extend({}, $target.data(), $this.data()) var slideIndex = $this.attr('data-slide-to') if (slideIndex) options.interval = false Plugin.call($target, options) if (slideIndex) { $target.data('bs.carousel').to(slideIndex) } e.preventDefault() }) $(window).on('load', function () { $('[data-ride="carousel"]').each(function () { var $carousel = $(this) Plugin.call($carousel, $carousel.data()) }) }) }(jQuery); /* ======================================================================== * Bootstrap: collapse.js v3.2.0 * http://getbootstrap.com/javascript/#collapse * ======================================================================== * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // COLLAPSE PUBLIC CLASS DEFINITION // ================================ var Collapse = function (element, options) { this.$element = $(element) this.options = $.extend({}, Collapse.DEFAULTS, options) this.transitioning = null if (this.options.parent) this.$parent = $(this.options.parent) if (this.options.toggle) this.toggle() } Collapse.VERSION = '3.2.0' Collapse.DEFAULTS = { toggle: true } Collapse.prototype.dimension = function () { var hasWidth = this.$element.hasClass('width') return hasWidth ? 'width' : 'height' } Collapse.prototype.show = function () { if (this.transitioning || this.$element.hasClass('in')) return var startEvent = $.Event('show.bs.collapse') this.$element.trigger(startEvent) if (startEvent.isDefaultPrevented()) return var actives = this.$parent && this.$parent.find('> .panel > .in') if (actives && actives.length) { var hasData = actives.data('bs.collapse') if (hasData && hasData.transitioning) return Plugin.call(actives, 'hide') hasData || actives.data('bs.collapse', null) } var dimension = this.dimension() this.$element .removeClass('collapse') .addClass('collapsing')[dimension](0) this.transitioning = 1 var complete = function () { this.$element .removeClass('collapsing') .addClass('collapse in')[dimension]('') this.transitioning = 0 this.$element .trigger('shown.bs.collapse') } if (!$.support.transition) return complete.call(this) var scrollSize = $.camelCase(['scroll', dimension].join('-')) this.$element .one('bsTransitionEnd', $.proxy(complete, this)) .emulateTransitionEnd(350)[dimension](this.$element[0][scrollSize]) } Collapse.prototype.hide = function () { if (this.transitioning || !this.$element.hasClass('in')) return var startEvent = $.Event('hide.bs.collapse') this.$element.trigger(startEvent) if (startEvent.isDefaultPrevented()) return var dimension = this.dimension() this.$element[dimension](this.$element[dimension]())[0].offsetHeight this.$element .addClass('collapsing') .removeClass('collapse') .removeClass('in') this.transitioning = 1 var complete = function () { this.transitioning = 0 this.$element .trigger('hidden.bs.collapse') .removeClass('collapsing') .addClass('collapse') } if (!$.support.transition) return complete.call(this) this.$element [dimension](0) .one('bsTransitionEnd', $.proxy(complete, this)) .emulateTransitionEnd(350) } Collapse.prototype.toggle = function () { this[this.$element.hasClass('in') ? 'hide' : 'show']() } // COLLAPSE PLUGIN DEFINITION // ========================== function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.collapse') var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) if (!data && options.toggle && option == 'show') option = !option if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) if (typeof option == 'string') data[option]() }) } var old = $.fn.collapse $.fn.collapse = Plugin $.fn.collapse.Constructor = Collapse // COLLAPSE NO CONFLICT // ==================== $.fn.collapse.noConflict = function () { $.fn.collapse = old return this } // COLLAPSE DATA-API // ================= $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { var href var $this = $(this) var target = $this.attr('data-target') || e.preventDefault() || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 var $target = $(target) var data = $target.data('bs.collapse') var option = data ? 'toggle' : $this.data() var parent = $this.attr('data-parent') var $parent = parent && $(parent) if (!data || !data.transitioning) { if ($parent) $parent.find('[data-toggle="collapse"][data-parent="' + parent + '"]').not($this).addClass('collapsed') $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed') } Plugin.call($target, option) }) }(jQuery); /* ======================================================================== * Bootstrap: dropdown.js v3.2.0 * http://getbootstrap.com/javascript/#dropdowns * ======================================================================== * Copyright 2011-2014 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // DROPDOWN CLASS DEFINITION // ========================= var backdrop = '.dropdown-backdrop' var toggle = '[data-toggle="dropdown"]' var Dropdown = function (element) { $(element).on('click.bs.dropdown', this.toggle) } Dropdown.VERSION = '3.2.0' Dropdown.prototype.toggle = function (e) { var $this = $(this) if ($this.is('.disabled, :disabled')) return var $parent = getParent($this) var isActive = $parent.hasClass('open') clearMenus() if (!isActive) { if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { // if mobile we use a backdrop because click events don't delegate $('