NAME

Slovo - Искони бѣ Слово

SYNOPSIS

Install Slovo locally with all dependencies in less than two minutes

    time curl -L https://cpanmin.us | perl - -M https://cpan.metacpan.org \
    -q -n -l ~/opt/slovo Slovo

Run slovo for the first time in debug mode

   morbo ~/opt/slovo/bin/slovo

Visit http://127.0.0.1:3000. For help visit http://127.0.0.1:3000/perldoc.

DESCRIPTION

Slovo is a simple to install and extensible Mojolicious CMS with nice core features, listed below.

This is a usable release, yet full of creeping bugs and half-implemented pieces! The project is in active development, so expect often breaking changes.

  • On the fly generation of static pages under Apache/CGI – perfect for cheap shared hosting and blogging – BETA;

  • Multi-domain support - WIP;

  • Multi-language pages - WIP;

  • Cached published pages and content - DONE;

  • Multi-user support - DONE;

  • User onboarding - WIP;

  • User sign in - DONE;

  • Managing pages, content, domains, users - WIP;

  • Managing groups - BASIC;

  • Multiple groups per user - DONE;

  • Ownership and permissions management per page and it's content - DONE;

  • Automatic 301 and 308 (Moved Permanently) redirects for renamed pages and content - DONE;

  • Embedded fonts for displaying all Azbuka and Glagolitsa characters - DONE;

  • OpenAPI 2/3.0 (Swagger) REST API - BASIC;

  • Embedded Trumbowyg - A lightweight WYSIWYG editor;

  • Embedded Editor.md - The open source embeddable online markdown editor (component), based on CodeMirror & jQuery & Marked;

  • Example startup scripts for slovo and slovo_minion services for systemd, Apache 2.4 and NGINX vhost configuration files.

  • Inflatable embedded themes support - BETA;

  • and more to come…

By default Slovo comes with SQLite database, but support for PostgreSQL or MySQL is about to be added when needed. It is just a question of making compatible and/or translating some limited number of SQL queries to the corresponding SQL dialects. Contributors are welcome.

The word "slovo" (слово) has one unchanged meaning during the last millennium among all slavic languages. It is actually one language that started splitting apart less than one thousand years ago. The meaning is "word" – the God's word (when used with capital letter). Hence the self-naming of this group of people qr/sl(o|a)v(e|a|i)n(i|y|e)/ - people who have been given the God's word or people who can speak. All others were considered "mute", hence the naming (немци)...

INSTALL

All you need is a one-liner, it takes less than a minute.

    $ curl -L https://cpanmin.us | perl - -M https://cpan.metacpan.org -n -l ~/opt/slovo Slovo

We recommend the use of a Perlbrew environment.

If you already downloaded it and you have cpanm.

    $ cpanm -l ~/opt/slovo Slovo-XXXX.XX.XX.tar.gz

Or even if you don't have cpanm. Note that you need to install dependencies first. Set INSTALL_BASE, remove old Slovo installation, make, test, install, create data directory for sqlite database and run slovo to see available commands.

    tar zxf Slovo-XXXX.XX.XX.tar.gz
    cd  Slovo-XXXX.XX.XX
    INSTALL_BASE=~/opt/slovo && rm -rf $INSTALL_BASE && make distclean; \
    perl Makefile.PL INSTALL_BASE=$INSTALL_BASE && make && make test && make install \
    && $INSTALL_BASE/bin/slovo eval 'app->home->child("data")->make_path({mode => 0700});' \
    && $INSTALL_BASE/bin/slovo

Use cpanm to install or update into a custom location as self contained application and run slovo to see how it's going.

    # From metacpan. org
    export PREFIX=~/opt/slovo;
    cpanm -M https://cpan.metacpan.org -n --self-contained -l $PREFIX Slovo \
    $PREFIX/bin/slovo eval 'app->home->child("data")->make_path({mode => 0700});' \
    $PREFIX/bin/slovo

    # From the directory where you unpacked Slovo
    export PREFIX=~/opt/slovo;
    cpanm . -n --self-contained -l $PREFIX Slovo
    $PREFIX/bin/slovo eval 'app->home->child("data")->make_path({mode => 0700});'
    $PREFIX/bin/slovo

Start the development server and open a browser

    morbo ./script/slovo -l http://*:3000 & sleep 1 exo-open http://localhost:3000

USAGE

    cd /path/to/installed/slovo
    # ...and see various options
    ./bin/slovo

CONFIGURATION, PATHS and UPGRADING

Slovo is a Mojolicious application which means that everything applying to Mojolicious applies to it too. Slovo main configuration file is in lib/Slovo/resourses/etc/slovo.conf. You can use your own by setting $ENV{MOJO_CONFIG} or by just copying slovo.conf to $ENV{MOJO_HOME} and modify it as you wish. Routes can be added or removed in routes.conf. See Mojolicious::Plugin::RoutesConfig for details and examples. New plugins can be added per deployment in plugins section in slovo.conf.

$ENV{MOJO_HOME} (where you installed Slovo) is automatically detected and used. All paths, used in the application, are expected to be its children. You can add your own templates in $ENV{MOJO_HOME}/templates and they will be loaded and used with priority. You can theme your own instance of Slovo by just copying $ENV{MOJO_HOME}/lib/Slovo/resources/templates to $ENV{MOJO_HOME}/templates and modify them. You can add your own static files to $ENV{MOJO_HOME}/public. You can create custom themes by forking Slovo::Themes::Malka and using it as a starting point.

You can have separate static files and templates per domain under $ENV{MOJO_HOME}/domove/your.domain/public, $ENV{MOJO_HOME}/domove/your.other.domain/templates, etc. See $ENV{MOJO_HOME}/domove/localhost for example.

You can switch between different themes by just selecting the theme in the form for editing domains.

Last but not least, you can add your own classes into $ENV{MOJO_HOME}/site/lib and (why not) replace entirely some Slovo classes or just extend them. $ENV{MOJO_HOME}/bin/slovo will load them with priority.

With all the above, you can upgrade Slovo by just installing new versions over it and your files will not be touched. And of course, we know that you are using versioning just in case anything goes wrong. See "home".

ATTRIBUTES

Slovo inherits all attributes from Mojolicious and implements the following new ones.

home

Slovo detects where home is not like Mojo::Home by where lib/Mojolicous.pm is but by where the script/ or bin/ folder resides starting from where lib/Slovo.pm is and going up the tree. If in one of these folders there is a slovo executable, then the upper folder is the home.

Examples:

    berov@Skylake:Slovo$ pwd
    /home/berov/opt/dev/Slovo
    berov@Skylake:Slovo$ perl script/slovo eval 'say app->home'
    /home/berov/opt/dev/Slovo

    berov@Skylake:Slovo$ cpanm . -n -l ~/opt/t.com/slovo
    --> Working on .
    Configuring /home/berov/opt/dev/Slovo ... OK
    Building Slovo-v2019.06.09 ... OK
    Successfully installed Slovo-v2019.06.09
    1 distribution installed

    berov@Skylake:t.com$ pwd
    /home/berov/opt/t.com
    berov@Skylake:t.com$ slovo/bin/slovo eval 'say app->home'
    /home/berov/opt/t.com/slovo

    berov@Skylake:t.com$ pwd
    /home/berov/opt/t.com
    berov@Skylake:t.com$ perl -Islovo/lib/perl5 -MSlovo -E 'say Slovo->new->home'
    /home/berov/opt/t.com/slovo

log

Overrides "log" in Mojolicious. Logs to self->home->child('log/slovo.log') if $self->home->child('log') exists and is writable. Oderwise writes to STDERR. The log-level will default to either the MOJO_LOG_LEVEL environment variable, debug if the "mode" is development, or info otherwise.

resources

  push @{$app->static->paths}, $app->resources->child('public');

Returns a Mojo::File instance for path "resources" in Slovo next to where Slovo.pm is installed.

validator

  my $validator = $app->validator;
  $app          = $app->validator(Slovo::Validator->new);

Validate values, defaults to a Slovo::Validator object.

  # Add validation check
  $app->validator->add_check(foo => sub {
    my ($v, $name, $value) = @_;
    return $value ne 'foo';
  });

  # Add validation filter
  $app->validator->add_filter(quotemeta => sub {
    my ($v, $name, $value) = @_;
    return quotemeta $value;
  });

METHODS

Slovo inherits all methods from Mojolicious and implements the following new ones.

load_class

A convenient wrapper with check for "load_class" in Mojo::Loader. Loads a class and croaks if something is wrong. This could be a helper.

  for my $class (@{$config->{load_classes} // []}) {
    $app->load_class($class);
  }

startup

Starts the application. Adds hooks, prepares $app->routes for use, loads configuration files and applies settings from them, loads plugins, sets default paths, and returns the application instance. See also "startup" in Mojolicious.

HOOKS

Slovo adds custom code to the following hooks.

around_action

On each request we set the following variables in the stash so they are available in the respective templates. Here they are:

    $stash->{l}         //= $c->language;     # current language
    $stash->{user}      //= $c->user;         # current user

around_dispatch

On each request we determine the current host and modify the static and renderer paths accordingly. This is how each domain has its own templates and static files.

Also if the templates field for the current domain is not empty, we determine from it the templates root for the theme to be used for this domain during this request. This is how the themes support for multiple domains in one Slovo instance work.

It is also important to note that in a long running application (not CGI) the templates are cached in memory and the relative path from the current templates root to each template is used as the key in Mojo::Cache cache. We had to implement "key_prefix" in Slovo::Cache to be able to differentiate between templates having the same names, but found in different paths. All this is possible thanks to Mojolicious's well decoupled components.

Example: Let's suppose that the domain https://слово.бг has the field 'templates' value set to themes/malka (малка==small f. in Bulgarian).

Renderer paths before the check is performed:

  [
    "/home/berov/opt/dev/Slovo/templates",
    "/home/berov/perl5/perlbrew/perls/perl-5.28.2/lib/site_perl/5.28.2/Mojolicious/Plugin/Minion/resources/templates",
    "/home/berov/opt/dev/Slovo/lib/Slovo/resources/templates"
  ]

Static paths before the check:

  [
    "/home/berov/opt/dev/Slovo/public",
    "/home/berov/perl5/perlbrew/perls/perl-5.28.2/lib/site_perl/5.28.2/Mojolicious/Plugin/Minion/resources/public",
    "/home/berov/opt/dev/Slovo/lib/Slovo/resources/public"
  ]

Renderer paths after the check is performed. The first path in the list will be used with priority:

  [
    "/home/berov/opt/dev/Slovo/lib/Slovo/resources/templates/themes/malka", # if exists!
    "/home/berov/opt/dev/Slovo/domove/xn--b1arjbl.xn--90ae/templates", # if exists!
    "/home/berov/opt/dev/Slovo/templates",
    "/home/berov/perl5/perlbrew/perls/perl-5.28.2/lib/site_perl/5.28.2/Mojolicious/Plugin/Minion/resources/templates",
    "/home/berov/opt/dev/Slovo/lib/Slovo/resources/templates"
  ]

Static paths after the check:

  [
    "/home/berov/opt/dev/Slovo/domove/xn--b1arjbl.xn--90ae/public", # if exists!
    "/home/berov/opt/dev/Slovo/public",
    "/home/berov/perl5/perlbrew/perls/perl-5.28.2/lib/site_perl/5.28.2/Mojolicious/Plugin/Minion/resources/public",
    "/home/berov/opt/dev/Slovo/lib/Slovo/resources/public"
  ]

In addition the current domain row from table domove becomes available in the stash as $domain.

  # In a controller or model
  $c->stash('domain')->{id};
  $m->c->stash('domain')->{aliases};

  # In a template like
  # lib/Slovo/resources/templates/stranici/_form.html.ep
  <%=
  select_box
    dom_id   => $domove,
    required => 1,
    label    => 'Дом',
    title    => 'В кой сайт се намира страницата.',
    readonly => '',
    value    => $domain->{id} #default value
  %>

before_dispatch

On each request we check if we have a logged in user and set the current user to guest if we don't. This way every part of the application (including newly developed plugins) can count on having a current user. The user is needed to determine the permissions for any table that has column permissions. The current user is available as $c->user.

HELPERS

Slovo implements the following helpers.

openapi_spec

We need to have our OpenAPI API specification always at hand as a unified source of truth so here it is.

    # anywhere via $app or $c, even not via a REST call
    state $columns =
        $c->openapi_spec('/paths/~1stranici/get/parameters/3/default');
    [
      "id",
      "pid",
      "alias",
      "title",
      "is_dir"
    ]

BUGS, SUPPORT, CONTRIBUTING, DISCUSS

To report a bug, please create issues at GitHub, fork the project and make pull requests.

AUTHOR

    Красимир Беров
    CPAN ID: BEROV
    berov на cpan точка org
    http://i-can.eu

CONTRIBUTORS

Ordered by time of first commit.

  • MANWAR (Mohammad S Anwar)

  • KABANOID (Mikhail Katasonov)

  • 0xAF (Stanislav Lechev)

COPYRIGHT

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)

The full text of the license can be found in the LICENSE file included with this module.

This distribution contains other free software which belongs to their respective authors.

TODO

  • Stop adding features. Stabilize what we have.

  • Gradually replace MUI CSS with Chota CSS - site part is done.

  • Considerably improve the Adminiastration UI - now it is quite simplistic. Use ES6 directly as per browsers compatibility table

  • Consider using Mithril or Vue.js or something light as frontend framework for building UI. We already use jQuery distributed with the Mojolicious distro.

SEE ALSO

Slovo::Plugin::TagHelpers, Slovo::Plugin::DefaultHelpers, Slovo::Validator, Mojolicious, Mojolicious::Guides