Ado

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

  - Fixed bug in Ado::Sessions. It was failing to set default session type.

0.86 2015-03-30
  - Started work in Ado::Build to handle upgrades beside installations
    so the sqlite database is not replaced with the newly installed version
    and thus destroing content of unconscious users - Alpha.
  - Writing Ado::Manual::Intro.
  - Upgraded to Mojolicious 6.05!

0.85 2015-03-22
  - Added hook 'after_login', emited by Ado::Plugin::Auth.
  - Added possibilty to add items to the adobar menu
    via session->{adobar_links}.
  - $CODENAME changed to
    "земля" - U+2C08 GLAGOLITIC CAPITAL LETTER ZEMLJA (Ⰸ)
  - Showing current controller and action only when in development mode
    (templates/layouts/default.html.ep).
  - Upgraded to Mojolicious::Plugin::SemanticUI 0.15!
  - Upgraded to DBIx::Simple::Class 1.009!
  - Upgraded to Mojolicious::Plugin::DSC 1.005!
  - Upgraded to Mojolicious 6.03!

Changes  view on Meta::CPAN

  - Implemented do_sql_file application helper in Ado::Plugin::AdoHelpers.
  - Improved documentation.
    Added the newly created google group Ado-dev to Ado::Manual page.
  - Upgraded to Mojolicious 5.81.
  - Upgraded to Mojolicious::Plugin::DSC 1.002.

0.79 2015-02-15
  - $ENV{MOJO_HOME} is not set explicitly any more.
    Detecting properly app->home and falling back to Mojo::home.
  - Cleanups.
    - Moved Ado::Control::Ado to Ado-Plugin-Admin where it logically belongs.
  - Fixed broken tests with Mojolicious < 5.78 - Renee Baecker (RENEEB). Thanks!
  - Works with Mojolicious 5.79 too.

0.78 2015-02-01
  - Improved documentation.
  - Cleanups.
    - Moved Ado::Control::Ado::Users and
      Ado::Control::Ado::Default to Ado::Plugin::Admin distribution.
    - Moved generic routes to be always defined last.

Changes  view on Meta::CPAN

    $c->reply->exception() instead.
  - Started cleanups:
    Removed templates/добре/ок.html.ep just to reduce
    http://cpants.cpanauthors.org/dist/Ado/errors
  - Noted in etc/ado.conf that arbitrary Perl code can be executed
    when connecting to the database.
  - POD improvements and cleanups.
  - Upgraded to Mojolicious::Plugin::SemanticUI 0.11.

0.76 2014-12-14
  - Cleaned up Login form. It is used for local login only.
  - Not using flags for languages. Language menu is now a dropdown menu.
  - Upgraded to Mojolicious::Plugin::DSC 1.000 to be able to execute
    perl code upon connecting to the database.
  - Implemented unicode aware upper() and lower() for SQLite.
  - The name of the authentication method is now shown beside its icon
    in the "Sign in" dropdown menu.

0.75 2014-11-26
  - $CODENAME changed to
    "Дзело" - U+2C07 GLAGOLITIC CAPITAL LETTER DZELO (Ⰷ)

Changes  view on Meta::CPAN

0.74 2014-11-22
  - Fixed path to reset.min.js from Semantic-UI.
  - Fixed regression with I18n and Mojolicious 5.62.
  - Code optimisations.

0.73 2014-11-17
  - Upgraded to Mojolicious::Plugin::SemanticUI 0.08.
  - Works with Mojolicious 5.62.

0.72 2014-11-15
  - Emphasized the passwordless login feature in Ado::Plugin::Auth.
  - overeall improvement of Ado::Plugin::Auth documentation.
  - Added Mojolicious::Plugin::OAuth2 as a recommended dependency.
    It will be required only if some of the supported providers is enabled.
  - Implemented experimental authentication via Google.
  - Added hook "after_user_add" emited by Ado::Plugin::Auth.
  - Added German language (Joachim Astel). Thanks!
  - Improved Ado::Manual::Plugins a lot.

0.71 2014-11-01
  - Fixed bug in Ado::Plugin::home_dir(). Wrong assumption regarding %INC keys.

Changes  view on Meta::CPAN

    in which some fields are not available.
  - Improved Ado::Control::require_formats(@formats).
  - Improved Ado::Model::Users::by_group_name($group, $limit, $offset).
  - TODO: Improve Test Coverage.


0.69 2014-10-26
  - $CODENAME changed to
    "Живете" - U+2C06 GLAGOLITIC CAPITAL LETTER ZHIVETE (Ⰶ)
  - Switched from favicon.ico to favicon.png for better quality.
  - Fixed popup positioning and appearance of the logo in the main menu.
  - Added the condition 'ingroup' to Ado::Plugin::Auth.
  - Implemented Ado::Model::Users::by_group_name($group, $limit, $offset).

0.68 2014-10-25
  - Upgraded to Mojolicious 5.54.
  - Added templates/not_found.html.ep.
  - Upgraded to DBD::SQLite 1.44.
  - Dramatic performance improvement with 'PRAGMA synchronous = OFF' and
    'PRAGMA journal_mode=WAL'.
  - Improved Ado::Command::adduser documentation.

Changes  view on Meta::CPAN

0.44 2014-05-29
    - Fixed typos in documentation.

0.43 2014-05-25
    - Default page now has a Bulgarian variant.

0.42 2014-05-18
    - Fixed typos and improved POD.
    - Added Ado::Plugin::I18n to the list of the end-user
      features at the bottom of the home page.
    - Created and used templates/partials/logo.html.ep.
    - Created embedded template languages_menu.html.ep in Ado::Plugin::I18n
      and used it on the home page. This is a usage example.
    - Added jQuery Cookie Plugin v1.4.1.
    - Added some flag icons from http://www.famfamfam.com.
    - Upgraded to Mojolicious 4.99.
    - Upgraded to Semantic UI 0.17.0

0.41 2014-05-11
    - Translated intro.md. to Bulgarian.
    - Menu items in adobar.html.ep now are only icons.

Changes  view on Meta::CPAN

    - Честита Баба Марта!
    - Improved documentation.
    - Added default/index.html.ep template and
      default index page content.
    - Upgraded to Mojolicious 4.85.
    - Fixed failing test in t/sessions/database.t

0.34 2014-02-23
    - Optimised /help User Unterface for hand-held devices.
    - Added text in Bulgarian to cover.md, intro.md. admin.md, domains.md,
      groups.md, users.md, cms.md, pages.md, articles.md, blog.md - TODO:
      Translate them to English.
    - Optimized Ado::Control::generator().
    - Added markdown() helper to MarkdownRenderer plugin.

0.33 2014-02-20
    - Really fixed failing test under Windows.
    - Really upgraded to Mojolicious 4.83.

0.32 2014-02-19
    - Fixed failing test under Windows.

Changes  view on Meta::CPAN

    - This is a bugfix release.
    - Have a testing copy of ado.conf and ado.sqlite in t/etc.
    - Checking for Test::Output in t/command/version.t.
    - Creating README only if $ENV{TEST_AUTHOR}.

0.27 2014-01-29
    - Upgraded to Mojolicious 4.71.
    - Experimental CORS-ready Ado::Sessions with file
      and database storage.
    - Fixed bug in Ado::Build::ACTION_dist.
      It was not emptying log files.

0.26 2014-01-11
    - Improved Ado::Command::version output.
    - Improved Ado::Manual.
    - Not depending on Mojo::Util in Ado::Build.
    - Fixed small bug in create_readme() and optimized it.
    - Installing to the more popular "siteprefix" by default.
    - Fixed failing test because of missing Test::Output.

0.25 2014-01-01 20:27:50 CET

Changes  view on Meta::CPAN

    - Added experimental tests for Ado::Build.
    - Fixed ACTION_perltidy - did not tidy Build.PL
    - Dynamically generating  META.json and META.yml
      depending on if $ENV{TEST_AUTHOR} is set
      thus requiring fewer dependencies.
    - Updated Manual.pod
    - Added uninstall and fakeuninstall actions to Ado::Build (Valcho).
    - Fixed wrong install_path for lib directory
      when "--install_base" is not passed as argument.
    - Using Perl's $Config{siteprefixexp} for
      other directories: qw(etc public log templates).

0.22 2013-12-29 03:30:38 CET
    - Refactored creation of README.md, kept README.
    - Wellcome Valcho to the core team :)!
    - Removed $ADO_HOME (by popular demand)
      and symplified installation process a lot.
    - Improved Manual.pod/README.

0.21 2013-12-28 03:37:42 CET
    - Added build action submit (draft).

Changes  view on Meta::CPAN

    - Skipping pod-coverage.t under Perl 5.014

0.19 2013-12-23 00:37:22 CET
    - More strict checks regarding --install_base.
      use 5.014 to enable say().

0.18 2013-12-22 17:34:32 CET
    - Removed release_status=>'unstable' from Build.PL.
      This does not mean much.
    - Started developing an external plugin - Ado::Plugin::Mess.
    - First attempt for a logo.

0.17 2013-12-21 00:11:06 CET
    - Refactored list() in Ado::Control::Ado::Users
      and implemented list_for_json in Ado::Control.
    - Added test users test1 and test2.
    - Added dummy missing actions to Ado::Control::Ado::Users.
    - Added $c->require_formats(['json', 'xml', 'etc']) to Ado::Control.
    - Enhanced _set_env in Ado::Build.
    - Lower required Perl version to 5.014000.
    - Enhanced POD.

Changes  view on Meta::CPAN

    - Set lib and arch install paths to "lib".
    - Enhanced documentation.
    - Implemented custom automatic README
      generation from Ado::Nabual.
    - Improved Build.PL

0.14 2013-12-14 14:11:45 CET
    - Setting explicitly MOJO_MODE in bin/ado
    - Better detection for MOJO_HOME and MOJO_CONFIG
    - Enhanced the Build process,
      added ACTION_build, ACTION_dist process_log_files
      and _empty_log_files to Ado::Build.
    - Enhanced Ado::Plugin::Routes, added tests.
    - Implemented Ado::Plugin::Routes.
    - Again changed the routes structure in config.
      Every route description is simply a HASHREF.
    - Enhanced documentation.
    - Reenabled pod-coverage tests.

0.13 2013-12-10 01:44:30 CET
    - Removed usage of PoweredBy.
    - Lower required Perl version to 5.014002.

LICENSE  view on Meta::CPAN

for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

MANIFEST  view on Meta::CPAN

lib/Ado/Plugin.pm
lib/Ado/Plugin/AdoHelpers.pm
lib/Ado/Plugin/Auth.pm
lib/Ado/Plugin/I18n.pm
lib/Ado/Plugin/MarkdownRenderer.pm
lib/Ado/Plugin/Routes.pm
lib/Ado/Sessions.pm
lib/Ado/Sessions/Database.pm
lib/Ado/Sessions/File.pm
LICENSE
log/development.log
log/production.log
log/README
MANIFEST			This list of files
META.json
META.yml
public/articles/ala/bala.md
public/articles/hello.md
public/css/ado.css
public/css/flags/bg.png
public/css/flags/de.png
public/css/flags/en.png
public/css/flags/es.png
public/css/flags/fr.png
public/css/flags/readme.txt
public/css/flags/ru.png
public/css/ui-bg_diagonal-maze_light_10x10.png
public/doc/bg/accounting.md
public/doc/bg/admin.md
public/doc/bg/apps.md
public/doc/bg/articles.md
public/doc/bg/blog.md
public/doc/bg/cms.md
public/doc/bg/cover.md
public/doc/bg/crm.md
public/doc/bg/domains.md
public/doc/bg/groups.md
public/doc/bg/img/default_admin_screen.epz
public/doc/bg/img/default_admin_screen.png
public/doc/bg/intro.md
public/doc/bg/messages.md
public/doc/bg/no_title.md

MANIFEST  view on Meta::CPAN

templates/articles/show.html.ep
templates/default/index.html.ep
templates/default/index_text.html+bg.ep
templates/default/index_text.html+de.ep
templates/default/index_text.html.ep
templates/doc/show.html.ep
templates/exception.html.ep
templates/layouts/articles.html.ep
templates/layouts/default.html.ep
templates/layouts/doc.html.ep
templates/login.html.ep
templates/not_found.html.ep
templates/partials/adobar.html.ep
templates/partials/apache2htaccess.ep
templates/partials/apache2vhost.ep
templates/partials/authbar.html.ep
templates/partials/head.html.ep
templates/partials/login_form.html.ep
templates/partials/logo.html.ep
templates/test/ado_helpers.html.ep

README  view on Meta::CPAN

  based on Mojolicious, written in the Perl programming language
  <http://www.perl.org/>. Ado is a typical well structured, MVC
  <http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller>
  Mojolicious application. It aims to allow teams to build on it a CMS, ERP,
  CRM or all of them integrated together.

  Please note that the project is still a work-in-progress. Parts of it
  work, others are simply missing.

GETTING STARTED
  *   Start a personal blog with Ado::Manual::FiveMinutes if you are
      impatient.

  *   Read the Ado::Manual::Intro for a general overview.

  *   To see different installation methods, go to
      Ado::Manual::Installation.

  *   To learn about plugins and how to write your own plugin, see
      Ado::Manual::Plugins.

README.md  view on Meta::CPAN

language](http://www.perl.org/). Ado is a typical well structured,
[MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)
[Mojolicious](https://metacpan.org/pod/Mojolicious) application. It aims to allow teams to build on it a CMS, ERP,
CRM or all of them integrated together.

Please note that the project is still a work-in-progress. Parts of it work,
others are simply missing.

# GETTING STARTED

- Start a personal blog with [Ado::Manual::FiveMinutes](https://metacpan.org/pod/Ado::Manual::FiveMinutes) if you are impatient.
- Read the [Ado::Manual::Intro](https://metacpan.org/pod/Ado::Manual::Intro) for a general overview.
- To see different installation methods, go to
[Ado::Manual::Installation](https://metacpan.org/pod/Ado::Manual::Installation).
- To learn about plugins and how to write your own plugin, see [Ado::Manual::Plugins](https://metacpan.org/pod/Ado::Manual::Plugins).

# SUPPORT AND DOCUMENTATION

After installing, you can find documentation with the perldoc command.

        perldoc Ado

etc/ado-sqlite-schema.sql  view on Meta::CPAN

  changed_by INTEGER REFERENCES users(id), 
  disabled INT(1) NOT NULL DEFAULT 1
);

-- 'This table stores the users'
DROP TABLE IF EXISTS users;
CREATE TABLE IF NOT EXISTS users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
--  'Primary group for this user'
  group_id INTEGER REFERENCES groups(id),
  login_name varchar(100) UNIQUE,
--  'Mojo::Util::sha1_hex($login_name.$login_password)'
  login_password varchar(40) NOT NULL,
  first_name varchar(100) NOT NULL DEFAULT '',
  last_name varchar(100) NOT NULL DEFAULT '',
  email varchar(255) NOT NULL UNIQUE,
  description varchar(255) DEFAULT NULL,
--  'id of who created this user.'
  created_by INTEGER REFERENCES users(id),
--  'Who modified this user the last time?'
  changed_by INTEGER REFERENCES users(id), 
--  'last modification time'
--  'All dates are stored as seconds since the epoch(1970) in GMT. In Perl we use gmtime as object from Time::Piece'

etc/ado.conf  view on Meta::CPAN

        'SemanticUI',

        #These are Ado plugins, loading their own config files.
        'ado_helpers',
        'markdown_renderer',
        'auth',
        'i18n',

        #'admin',
        #'vest',
        #'blog',
        #put your own routes for your private code in
        # etc/plugins/routes.$mode.conf
        'routes',
    ],

    #Configuration for commands
    #Every Ado::Command:: ISA Mojolicious::Command
    commands => {},

    #Routes order is important, so this configuration value is an ARRAYREF

etc/plugins/auth.conf  view on Meta::CPAN

        #     key =>'123456654321abcd.apps.googleusercontent.com',
        #     secret =>'Y0uRS3cretHEre',
        #     scope=>'profile email',
        #     info_url => 'https://www.googleapis.com/userinfo/v2/me',
        #     },
    },

    #Add your routes definitions here.
    routes => [

        #the login form and authentication
        {   route => '/login/:auth_method',
            via   => ['GET', 'POST'],
            to    => {
                auth_method => 'ado',
                cb          => \&Ado::Plugin::Auth::login
            },
            description => 'GET: Renders a login form when :auth_method is "ado".'
              . ' or authenticates a user when :auth_method is an OAuth2 provider.'
              . $/
              . 'POST: Authenticates a user.',
        },

        {   route => '/authorize/:auth_method',
            via   => ['GET', 'POST'],
            to    => {
                auth_method => 'google',
                cb          => \&Ado::Plugin::Auth::authorize
            },
        },

        {   route => '/logout',
            to    => {cb => \&Ado::Plugin::Auth::logout}
        },
        {   route => '/test/authenticateduser',
            over  => {authenticated => 1},
            to    => 'test#authenticateduser'
        },

    ],
};

lib/Ado.pm  view on Meta::CPAN

      : $app->ado_home->rel_file('etc/' . lc($CLASS) . '.conf');
    $app->plugin('Config');
    return $app;
}

sub load_plugins {
    my ($app) = @_;

    my $plugins = $app->config('plugins') || [];
    foreach my $plugin (@$plugins) {
        $app->log->debug('Loading Plugin ' . (ref $plugin ? $plugin->{name} : $plugin));
        if (ref $plugin eq 'HASH') {
            $app->plugin($plugin->{name} => $plugin->{config});
        }
        elsif ($plugin) {
            $app->plugin($plugin);
        }
    }

    return $app;
}

lib/Ado/Build.pm  view on Meta::CPAN

sub PERL_DIRS {
    state $dirs = [map { catdir($_[0]->base_dir, $_) } qw(bin lib etc t)];
    return @$dirs;
}

sub create_build_script {
    my $self = shift;

    #Deciding where to install
    my $prefix = $self->install_base || $self->config('siteprefix');
    for my $be (qw(etc public log templates)) {

        #in case of installing a plugin, check if folder exists
        next unless -d $be;
        $self->add_build_element($be);
        $self->install_path($be => catdir($prefix, $be));
    }
    return $self->SUPER::create_build_script();
}

sub process_public_files {

lib/Ado/Build.pm  view on Meta::CPAN

    for my $asset (@{$self->rscan_dir('etc')}) {
        if (-d $asset) {
            make_path(catdir('blib', $asset));
            next;
        }

        # skip SQLite automatically created files
        next if ($asset =~ m/ado\-(shm|wal)/);
        my $asset_path = catfile($prefix, $asset);
        if ($asset =~ m/ado\.sqlite/ && -s $asset_path) {
            $self->log_info("Skipping $asset because it already exists at $asset_path! $/");
            next;
        }
        copy($asset, catfile('blib', $asset));
    }
    return;
}

sub process_log_files {
    my $self = shift;
    for my $asset (@{$self->rscan_dir('log')}) {
        if (-d $asset) {
            make_path(catdir('blib', $asset));
            next;
        }
        copy($asset, catfile('blib', $asset));
    }
    return;
}

sub process_templates_files {

lib/Ado/Build.pm  view on Meta::CPAN

}

sub ACTION_fakeuninstall {
    my $self = shift;
    return $self->_uninstall('dry-run');
}

sub ACTION_build {
    my $self = shift;

    #Make sure *log files are empty before moving them to blib
    _empty_log_files('log');

    #Do other interventions before the real build...
    $self->SUPER::ACTION_build;
    return;
}

sub ACTION_test {
    my $self = shift;

    #Custom functionality before test
    $self->_process_custom_files(catdir('blib', 'etc'), catdir('blib', 'log'))
      if -d 'blib';
    $self->_process_custom_files('etc', 'log');
    $self->SUPER::ACTION_test;
    return;
}

sub ACTION_dist {
    my $self = shift;

    #Make sure *log files are empty before including them into the distro
    _empty_log_files('log');

    #Do other interventions before the real dist..
    $self->SUPER::ACTION_dist;
    return;
}

sub ACTION_install {
    my $self = shift;

    #Custom functionality before installation
    #here...
    #TODO: Think about what to do with *.conf and *.sqlite files in case of upgrade!!!
    #TODO: (upgrade)rotate logs - archive existing log files before emptying.
    $self->SUPER::ACTION_install;

    #Custom functionality after installation
    $self->_process_custom_files($self->install_path('etc'), $self->install_path('log'));
    return;
}

sub _process_custom_files {
    my ($self, $etc_dir, $log_dir) = @_;

    #make some files writable and/or readable only by the user that runs the application
    my $ro = oct('0400');
    my $rw = oct('0600');
    for my $asset (
        qw(ado.conf plugins/routes.conf plugins/auth.conf plugins/markdown_renderer.conf))
    {
        _chmod($ro, catfile($etc_dir, $asset));
    }
    _chmod($rw, catfile($etc_dir, 'ado.sqlite'));

    #Make sure *log files are existing and empty
    _empty_log_files($log_dir);
    for my $asset (qw(development production)) {
        _chmod($rw, catfile($log_dir, "$asset.log"));
    }
    return;
}

sub _chmod {
    my ($mode, $file) = @_;
    return chmod($mode, $file)
      || Carp::carp("Could not change mode for $file: $!");
}

sub ACTION_perltidy {
    my $self = shift;
    eval { require Perl::Tidy } || do {
        $self->log_warn(
            "Perl::Tidy is not installed$/" . "Please install it and rerun ./Build perltidy$/");
        return;
    };
    my @files;
    for my $dir ($self->PERL_DIRS) {
        my $dir_files = $self->rscan_dir($dir);
        for my $file (@$dir_files) {
            push @files, $file
              if -f $file && $file =~ m{(\.pl|/ado|\.pm|ado.*?\.conf|\.t)$}x;
        }

lib/Ado/Build.pm  view on Meta::CPAN


sub ACTION_submit {
    my $self = shift;

    #$self->depends_on("perltidy");
    say "TODO: commit and push after tidying and testing and who knows what";
    return;
}


#Empties log files in a given directory.
sub _empty_log_files {
    (my ($log_dir) = @_) || Carp::croak('Please provide $log_dir');
    open my $logd, ">", "$log_dir/development.log" || Carp::croak $!;
    close $logd;
    open my $logp, ">", "$log_dir/production.log" || Carp::croak $!;
    close $logp;
    return;
}

sub do_create_readme {
    my $self = shift;
    if ($self->dist_version_from =~ /Ado\.pm$/) {

        #Create README from Ado::Manual.pod
        require Pod::Text;
        my $readme_from = catfile('lib', 'Ado', 'Manual.pod');
        my $parser = Pod::Text->new(sentence => 0, indent => 2, width => 76);
        $parser->parse_from_file($readme_from, 'README');
        $self->log_info("Created README$/");

        #add README.md just to be cool..
        eval { require Pod::Markdown }
          || return $self->log_warn('Pod::Markdown required for creating README.md' . $/);
        $parser = Pod::Markdown->new;
        $parser->parse_from_file($readme_from);
        my $readme_md = 'README.md';
        if (open(my $out, '>', $readme_md)) {
            my $markdown = $parser->as_markdown;
            my $ci_badge =
                '[![Build Status](https://travis-ci.org/kberov/Ado.svg?'
              . 'branch=master)](https://travis-ci.org/kberov/Ado)';
            $markdown =~ s/(\n.+Travis-CI.+\n)/$1\n$ci_badge\n\n/xgm;
            $out->say($markdown);
            $out->close;
            $self->log_info("Created $readme_md$/");
        }
        else { Carp::croak("Could not create $readme_md... $!"); }
    }
    else {
        $self->SUPER::do_create_readme();
    }
    return;
}

1;

lib/Ado/Build.pm  view on Meta::CPAN


=head1 METHODS

Ado::Build inherits all methods from L<Module::Build> and implements
the following ones.

=head2 create_build_script

This method is called in C<Build.PL>.
In this method we also call C<add_build_element> for C<etc> C<public>,
C<templates> and C<log> folders.
Finally we set all the C<install_path>s for the distro
and we call C<$self-E<gt>SUPER::create_build_script>.

=head2 process_etc_files

Moves files found in C<Ado/etc> to C<Ado/blib/etc>.
See L<Module::Build::API/METHODS>
Returns void.

=head2 process_log_files

Moves files found in C<Ado/log> to C<Ado/blib/log>.
Returns void.

=head2 process_public_files

Moves files found in C<Ado/public> to C<Ado/blib/public>.
Returns void.

=head2 process_templates_files

Moves files found in C<Ado/templates> to C<Ado/blib/templates>.

lib/Ado/Command.pm  view on Meta::CPAN

    #first try (global config) !!!autovivification
    my $config = $app->config->{commands} && $app->config->{commands}->{$name};
    $config && return $config;

    #second try (command specific configuration file)
    my $conf_file = $app->home->rel_file('/etc/commands/' . decamelize($name) . '.conf');
    if ($config = eval { Mojolicious::Plugin::Config->new->load($conf_file, {}, $app) }) {
        return $config;
    }
    else {
        $app->log->warn(
            "Could not load configuration from file $conf_file! " . decode('UTF-8', $@));
        return {};
    }
}

sub config {
    my ($self, $key) = @_;
    state $config = $self->_get_command_config();
    return $key
      ? $config->{$key}

lib/Ado/Command/adduser.pm  view on Meta::CPAN

has args => sub {
    my $t = time;
    {   changed_by => 1,
        created_by => 1,
        disabled   => 1,

        #TODO: add funcionality for notifying users on account expiration
        #TODO: document this
        stop_date      => $t + ONE_YEAR,             #account expires after one year
        start_date     => $t,
        login_password => rand($t) . $$ . {} . $t,
    };
};

sub init {
    my ($self, @args) = @_;
    $self->SUPER::init();
    unless (@args) { Carp::croak($self->usage); }
    my $args = $self->args;
    my $ret  = GetOptionsFromArray(
        \@args,
        'u|login_name=s'     => \$args->{login_name},
        'p|login_password=s' => \$args->{login_password},
        'e|email=s'          => \$args->{email},
        'g|ingroup=s'        => \$args->{ingroup},
        'd|disabled:i'       => \$args->{disabled},
        'f|first_name=s'     => \$args->{first_name},
        'l|last_name=s'      => \$args->{last_name},
        'start_date=s'       => sub {
            $args->{start_date} =
              $_[1] ? Time::Piece->strptime('%Y-%m-%d', $_[1])->epoch : time;
        },
    );

    # Assume an UTF-8 terminal. TODO: make this more clever
    utf8::decode($args->{login_name})
      if ($args->{login_name} && !utf8::is_utf8($args->{login_name}));
    utf8::decode($args->{first_name})
      if ($args->{first_name} && !utf8::is_utf8($args->{first_name}));
    utf8::decode($args->{last_name})
      if ($args->{last_name} && !utf8::is_utf8($args->{last_name}));
    $args->{login_password} = Mojo::Util::sha1_hex($args->{login_name} . $args->{login_password});
    unless ($args->{ingroup}) {
        say($self->usage)
          unless ($args->{first_name}
            and $args->{last_name}
            and $args->{login_name}
            and $args->{email});
    }
    $self->app->log->debug('$self->args: ' . $self->app->dumper($self->args));
    return $ret;
}


#default action
sub adduser {
    my $self = shift;
    my $args = $self->args;
    my ($group, $user, $ingroup);
    if (($group = Ado::Model::Groups->by_name($args->{login_name}))->id) {
        $self->app->log->debug('$group:', $self->app->dumper($group));

        #if we have such group, we have the user or we do not want to give a user
        #the privileges of a shared group
        say "'$args->{login_name}' is already taken!";
    }
    else {
        $user = Ado::Model::Users->add($args) unless $group->id;
        return unless $user;
    }
    if ($user) {
        say "User '$args->{login_name}' was created with primary group '$args->{login_name}'.";
    }
    else {
        $user = Ado::Model::Users->by_login_name($args->{login_name});
    }

    return unless $args->{ingroup};
    if (not $user->ingroup($args->{ingroup})) {
        if ($ingroup = $user->add_to_group($args)) {
            say "User '$args->{login_name}' was added to group '$args->{ingroup}'.";
        }
    }
    else {
        say "User '$args->{login_name}' is already in group '$args->{ingroup}'.";
    }
    return 1;
}


1;

=pod

=encoding utf8

=head1 NAME

Ado::Command::adduser - adduser command

=head1 SYNOPSIS

  USAGE
  # On the command line
  # Minimal required options to add a user
  ado adduser --login_name USERNAME --email user\@example.com \
    --first_name John --last_name Smith

  # Add a user to an additional group
  ado adduser --login_name USERNAME --ingroup GROUPNAME

  # Change password / disable a user
  ado adduser --login_name USERNAME  --login_password N3W$36RE7P1$5W

  # Disable a user
  ado adduser --login_name USERNAME --disabled

  # Programatically
  use Ado::Command::adduser;
  Ado::Command::adduser->run('--login_name'=>'test1',...);


=head1 DESCRIPTION

L<Ado::Command::adduser> adds a user to an L<Ado> application.
It is a facade for L<Ado::Model::Users>.
This is a core L<Ado> command, that means it is always available and its code
a good example for learning to build new L<Ado> commands, you're welcome to fork it.

=head1 ATTRIBUTES

L<Ado::Command::adduser> inherits all attributes from
L<Ado::Command> and implements the following new ones.

=head2 args

  $self->args(login_name=>'peter','ingroup'=>'facebook');
  my $args = $self->args;

Default arguments for creating a user.


=head2 description

  my $description = $a->description;
  $a              = $a->description('Foo!');

lib/Ado/Command/adduser.pm  view on Meta::CPAN


  my $usage = $a->usage;
  $a        = $a->usage('Foo!');

Usage information for this command, used for the help screen.

=head1 OPTIONS

On the commandline C<ado adduser> accepts the following options:

    'u|login_name=s'     #username (mandatory)
    'p|login_password=s' #the user password (optional, random is generated)
    'e|email=s'          #user email (mandatory)
    'g|ingroup=s'        #existing users can be added to other groups too
    'd|disabled:i'       #is user disabled? (1 by default)
    'f|first_name=s'     #user's first name (mandatory)
    'l|last_name=s'      #user's last name (mandatory)
    'start_date=s'       #format: %Y-%m-%d (optional, today by default)

=head1 METHODS

L<Ado::Command::adduser> inherits all methods from

lib/Ado/Command/adduser.pm  view on Meta::CPAN

=head2 init

Calls the default parent L<Ado::Command/init> and parses the arguments
passed on the command-line. Returns true on success.
Croaks with L</usage> message on failure.


=head2 adduser

The default and only action this command implements.
Makes logical checks for existing user and group and calls
L<Ado::Model::Users/adduser> and L<Ado::Model::Users/add_to_group>
depending on parsed arguments.
See L<Ado::Command/run>.

=head1 SEE ALSO

L<Ado::Model::Users>,
L<Ado::Command> L<Ado::Manual>, L<Mojolicious::Command>,
L<Mojolicious>, L<Mojolicious::Guides>.

lib/Ado/Command/generate/adoplugin.pm  view on Meta::CPAN

=head1 NAME

Ado::Command::generate::adoplugin - Generates an Ado::Plugin

=head1 SYNOPSIS

On the command-line:

  $ cd ~/opt/public_dev
  # Ado is "globally" installed for the current perlbrew Perl
  $ ado generate adoplugin --name MyBlog
  $ ado generate adoplugin --name MyBlog --crud -t 'articles,news'

Programmatically:

  use Ado::Command::generate::adoplugin;
  my $vhost = Ado::Command::generate::adoplugin->new;
  $vhost->run(-n => 'MyBlog', -c => 1, -t => 'articles,news');

=head1 DESCRIPTION

L<Ado::Command::generate::adoplugin> generates directory structures for
fully functional L<Ado>-specific plugins with optional
L<MVC set of files|Ado::Command::generate::crud> in the newly created plugin directory.
The new plugin is generated in the current directory.

This is a core command, that means it is always enabled and its code a
more complex example for learning to build new commands. You're welcome to fork it.

lib/Ado/Command/generate/apache2vhost.pm  view on Meta::CPAN

      'v|verbose'        => \$args->{verbose},
      'u|user=s'         => \$args->{user},
      'g|group=s'        => \$args->{group},
      's|with_suexec'    => \$args->{with_suexec};

    Carp::croak $self->usage unless $args->{ServerName};

    $args->{ServerAlias} //=
      $$args{ServerName} =~ /^www\./ ? $$args{ServerName} : 'www.' . $$args{ServerName};
    $args->{ServerAdmin} //= 'webmaster@' . $args->{ServerName};
    $args->{user}        //= ($ENV{USER} || getlogin || 'nobody');
    $args->{group}       //= $args->{user};
    $args->{DocumentRoot} =~ s|\\|/|g if $IS_DOS;

    say STDERR 'Using arguments:' . $app->dumper($args) if $args->{verbose};
    state $rel_file      = 'templates/partials/apache2vhost.ep';
    state $template_file = (
        -s $home->rel_file($rel_file)
        ? $home->rel_file($rel_file)
        : $ado_home->rel_file($rel_file)
    );

lib/Ado/Control.pm  view on Meta::CPAN

    state $app = $_[0]->app;
    return $app->config(ref $_[0])->{$_[1]} if $_[1];    #if key
    return $app->config(ref $_[0]);
}

sub debug;
if ($DEV_MODE) {

    sub debug {
        my ($package, $filename, $line, $subroutine) = caller(0);
        state $log = $_[0]->app->log;
        return $log->debug(
            @_[1 .. $#_]                                 #, "    at $filename:$line"
        );
    }
}

#Require a  list of formats or render "415 - Unsupported Media Type"
#and return false.
sub require_formats {
    my ($c, @formats) = @_;
    unless ($c->accepts(@formats)) {

lib/Ado/Control.pm  view on Meta::CPAN

                    data    => 'validate_input'
                }
              )
            : (output => $v->output)
        )
    };
}

sub user {
    my ($c, $user) = @_;
    state $delete_fields = [qw(login_password created_by changed_by disabled start_date email)];
    if ($user) {

        # Remove as much as possible user data.
        delete @{$user->data}{@$delete_fields};
        $c->{user} = $user;
        return $c;
    }
    elsif ($c->{user}) {
        return $c->{user};
    }

    # Called for the first time without a $user object.
    # Defaults to current user or Guest.
    $c->{user} = Ado::Model::Users->by_login_name($c->session->{login_name} //= 'guest');
    delete @{$c->{user}->data}{@$delete_fields};
    return $c->{user};
}

1;

=pod

=encoding utf8

lib/Ado/Control.pm  view on Meta::CPAN

  my $myvalue = $app->config(__PACKAGE__)->{mykey}
  ...

To access the application-wide configuration use
C<$c-E<gt>app-E<gt>config('key')>.

=head2 debug

A shortcut to:

  $c->app->log->debug(@_);

=head2 list_for_json

Prepares a structure suitable for rendering as JSON for  listing  an ARRAYref
of HASHES or L<Ado::Model>* objects, returned by L<Ado::Model/select_range>
and returns it. Accepts two C<ARRAY> references  and one arbitrary C<HASH>
reference as parameters:

  my $res = $c->list_for_json([$limit, $offset], \@list_of_AMobjects_or_hashes, $meta);

lib/Ado/Control.pm  view on Meta::CPAN

    #400 Bad Request
    return $c->render(
        status => 400,
        json   => $result->{json}
    ) if $result->{errors};

=head2 user

Returns the current user. This is the user C<guest> for not authenticated
users. Note that this instance is not meant for manipulation and some fields
are not available for security reasons. The fields are: C<login_password
created_by changed_by disabled start_date>. TODO: move as much as possible
checks and fields retrieval in SQL, not in Perl.


  $c->user(Ado::Model::Users->by_login_name($login_name));
  my $names = $c->user->name;


=head1 SEE ALSO

L<Mojolicious::Controller>, L<Ado::Manual::Controllers>,
L<Ado::Manual::RESTAPI>, L<DBIx::Simple>

=head1 AUTHOR

lib/Ado/Control/Articles.pm  view on Meta::CPAN


  #in your browser go to
  http://your-host/articles


=head1 DESCRIPTION

Ado::Control::Articles is a controller that generates full static HTML
documents for markdown files found in the folder specified in
C<md_articles_root> in C<etc/plugins/markdown_renderer.$mode.conf>. It allows
you to have a simple static blog on Ado in no time, i.e. install Ado and you
have a personal blog.

=head1 METHODS/ACTIONS

L<Ado::Control::Articles> inherits all the methods from
L<Ado::Control> and defines the following.

=head2 show

Renders the file found in C<$c-E<gt>stash('html_file')> but with extension
C<.md>. If C<$config-E<gt>{md_reuse_produced_html}> is set, the produced html

lib/Ado/Control/Test.pm  view on Meta::CPAN

    $c->debug('$$stash{language_from}:' . $$stash{language_from});
    $$stash{language_from} ||= $c->param('from');
    $c->debug('$$stash{language_from}:' . $$stash{language_from});
    return $c->render();
}

# Test Ado::Model::Users->by_group_name
sub ingroup {
    my $c = shift;

    #get users from group with the same name as the user login_name
    my @users = Ado::Model::Users->by_group_name($c->user->login_name, $c->param('offset'),
        $c->param('limit'));
    return $c->render(json => [@users]);

}

sub guest {
    my $c = shift;

    #get users from group with the same name as the user login_name
    state $guest = Ado::Model::Users->by_login_name('guest')->data;
    return $c->render(json => $guest);
}

1;

=pod

=encoding utf8

=head1 NAME

lib/Ado/I18n/bg.pm  view on Meta::CPAN

package Ado::I18n::bg;
use Mojo::Base 'Ado::I18n';
use I18N::LangTags::List;
our %Lexicon = (    ##no critic(ProhibitPackageVars)
    hello          => 'Здрасти, [_1]!',
    Login          => 'Вписване',
    Logout         => 'Изход',
    Help           => 'Помощ',
    login_name     => 'Потребител',
    login_password => 'Парола',
    login_field_error =>
      'Моля въведете валидна стойност за полето "[_1]"!',
    first_name   => 'Име',
    last_name    => 'Фамилия',
    email        => 'Е-поща',
    title        => 'Заглавие/Име',
    tags         => 'Етикети',
    time_created => 'Created on',
    sorting      => 'Подредба',
    data_type    => 'Тип',
    data_format  => 'Формат',

lib/Ado/I18n/de.pm  view on Meta::CPAN

package Ado::I18n::de;
use Mojo::Base 'Ado::I18n';
use I18N::LangTags::List;
our %Lexicon = (    ##no critic(ProhibitPackageVars)
    hello             => 'Hallo [_1],',
    Logout            => 'Ausloggen',
    login_name        => 'Benutzer',
    login_password    => 'Passwort',
    login_field_error => 'Bitte gültigen Wert in Feld "[_1]" eintragen!',
    first_name        => 'Vorname',
    last_name         => 'Nachname',
    title             => 'Titel/Name',
    tags              => 'Tags',
    time_created      => 'Angelegt am',
    tstamp            => 'Verändert am',
    body              => 'Inhalt (body)',
    invisible         => 'Versteckt',
    language          => 'Sprache',
    group_id          => 'Gruppe',

lib/Ado/I18n/en.pm  view on Meta::CPAN

package Ado::I18n::en;
use Mojo::Base 'Ado::I18n';
use I18N::LangTags::List;
our %Lexicon = (    ##no critic(ProhibitPackageVars)
    hello             => 'Hello [_1],',
    Logout            => 'Sign out',
    login_name        => 'User',
    login_password    => 'Password',
    login_field_error => 'Please enter a valid value for the field "[_1]"!',
    first_name        => 'First Name',
    last_name         => 'Last Name',
    title             => 'Title/Name',
    tags              => 'Tags',
    time_created      => 'Created on',
    tstamp            => 'Changed on',
    body              => 'Content (body)',
    invisible         => 'Invisible',
    language          => 'Language',
    group_id          => 'Group',

lib/Ado/I18n/en.pm  view on Meta::CPAN

    DESC              => 'Descending',
    created_by        => 'Created by',
    created_by_help   => 'Who created this user?',
    changed_by_help   => 'Who modified this user the last time?',
    changed_by        => 'Changed by',
    reg_date          => 'Date of registration',
    disabled          => 'Locked',
    description       => 'Description',
    'Sign in'         => 'Sign in',
    Translate         => 'Translate',
    LoginThanks       => 'Thanks for logging in! Welcome!',

);

1;

=pod

=encoding utf8

=head1 NAME

lib/Ado/Manual.pod  view on Meta::CPAN

L<Mojolicious> application. It aims to allow teams to build on it a CMS, ERP,
CRM or all of them integrated together.

Please note that the project is still a work-in-progress. Parts of it work,
others are simply missing.

=head1 GETTING STARTED

=over

=item * Start a personal blog with L<Ado::Manual::FiveMinutes> if you are impatient.

=item * Read the L<Ado::Manual::Intro> for a general overview.

=item * To see different installation methods, go to
L<Ado::Manual::Installation>.

=item * To learn about plugins and how to write your own plugin, see L<Ado::Manual::Plugins>.

=back

lib/Ado/Manual/Contributing.pod  view on Meta::CPAN

"RESTful Service Best Practices" at L<www.RestApiTutorial.com>.
You must have a reference implementation of a browser-based user-agent(yourpluginroute.html) residing in
C<Ado/public/>. It would be best if your code is CORS-ready this
should be easy if your code depends on Ado::Plugin::CORS(TODO).
See L<Ado::Manual::RESTAPI>(TODO). Feel free to propose additional best
practices. Be ready to explain well your proposal.

=head2 TEMPLATES

We have a minimal set of templates residing in the C<templates> directory.
Separation of concerns must be to its maximum. Strive for minimal logic in
your templates if you have such.

=head2 MODEL

Put your database related code under the L<Model> namespace.
Use L<DBIx::Simple::Class>.
Generate your classes from tables using C<dsc_dump_schema.pl> utility
that comes with DBIx::Simple::Class.
Feel free to contribute to L<DBIx::Simple::Class>.
Minimize SQL complexity. Write ANSI SQL if you need to write SQL.

lib/Ado/Manual/FiveMinutes.pod  view on Meta::CPAN

=encoding utf8

=head1 NAME

Ado::Manual::FiveMinutes - Start a personal blog in five minutes.

=head1 FIVE MINUTES

Within five minutes we will use L<Ado::Plugin::MarkdownRenderer> and
L<Ado::Control::Articles> to create a personal blog.

Create a directory for L<Ado> and install it in it.

  mkdir ~/ado
  curl -L https://cpanmin.us | perl - -M https://cpan.metacpan.org -l ~/ado -n Ado

Start Ado in development mode.

  cd ~/ado
  morbo -w ./lib -w ./site_templates -w templates  bin/ado

lib/Ado/Manual/Intro.pod  view on Meta::CPAN

  │       ├── Command       # Commands namespace for commands such as Ado::Command::adduser
  │       │   └── generate  # Generators such as Ado::Command::generate::apache2vhost are here
  │       ├── Control       # Controller namespace where controller classes
  │       │                 # like Ado::Control::Users reside
  │       ├── I18n          # Namespace for lexicon packages such as Ado::I18n::bg
  │       ├── Manual        # Namespace for developer manuals like this very file
  │       ├── Model         # Controller namespace where controller classes
  │       │                 # like Ado::Model::Users reside
  │       ├── Plugin        # Ado plugins namespace, e.g. Ado::Plugin::Auth
  │       └── Sessions      # Server side sessions - e.g. Ado::Sessions::Database
  ├── log                   # Log directory
  ├── public                # Static file directory (served automatically)
  │   │                     # Good for generated static pages served by e.g. Apache or Nginx
  │   ├── css
  │   │   └── flags
  │   ├── doc
  │   │   ├── bg
  │   │   │   └── img
  │   │   └── en
  │   ├── fonts
  │   ├── img

lib/Ado/Manual/Plugins.pod  view on Meta::CPAN

C<app-E<gt>home-E<gt>rel_dir('etc/plugins/hello.conf')>. The file must return
a HASHREF. See the code of the listed plugins below for examples of how and
what can be done in a plugin.

To create your own plugin do the following (Example):

=over

=item * Create one or more tables to be used by your plugin in C<etc/ado.sqlite>

  CREATE TABLE blog (
    id INTEGER PRIMARY KEY,
    title VARCHAR NOT NULL UNIQUE,
    body TEXT NOT NULL,
    published BOOL DEFAULT '0',
    deleted BOOL NOT NULL DEFAULT '0',
    user_id INTEGER REFERENCES users(id),
    group_id INTEGER REFERENCES groups(id),
    permissions VARCHAR(10) DEFAULT '-rwxr-xr-xr'
  );
  CREATE INDEX blog_published ON blog(published);
  CREATE INDEX blog_deleted ON blog(deleted);

=item * Add some dummy records.

  INSERT INTO blog(title,body,user_id,group_id)
  VALUES('Hey','Hello world',3,3);
  INSERT INTO blog(title,body,user_id,group_id)
  VALUES('Hey You','Hello Universe',3,3);

=item * Generate the files for the plugin. These are the files which you will edit :).

  $ cd ~/opt/public_dev
  $ ado generate adoplugin -n Blog --crud -t blog

The above command will generate the needed files for an ado plugin which can
even be uploaded to and subsequently downloaded from
L<CPAN|http://www.cpan.org/>. L<CPAN> is the best open source dependency
management system. You can also use L<Stratopan|https://stratopan.com/> if you
wish.

=back

Ado uses L<Ado::Build> and L<Ado::BuildPlugin> which extend L<Module::Build>.
They were created to add some custom actions and handle the additional
C<templates>,C<log> and C<public> directories in Ado root folder.
The file tree looks like the following:

  ~/opt/public_dev/Ado-Plugin-Blog$ tree
  .
  ├── Build.PL
  ├── etc
  │   └── plugins
  │       └── blog.conf
  ├── lib
  │   └── Ado
  │       ├── Control
  │       │   └── Blog.pm
  │       └── Plugin
  │           └── Blog.pm
  ├── templates
  │   └── blog
  │       ├── create.html.ep
  │       ├── delete.html.ep
  │       ├── list.html.ep
  │       └── read.html.ep
  └── t
      └── plugin
          └── blog-00.t


No worries, your plugin has everything needed to be installed from CPAN.
L<Ado::Plugin::Vest> was started using this command.

L<Ado> can be stripped down to a bare Mojolicious application by not loading
any plugins. And L<Ado> can be extended I<infinitely> just by adding helpers,
conditions, routes, templates and injecting code into hooks from plugins.
I<This is true for any Mojolicious application.>

lib/Ado/Model/Users.pm  view on Meta::CPAN

use parent qw(Ado::Model);
use Carp;
use Email::Address;
sub is_base_class { return 0 }
my $CLASS      = __PACKAGE__;
my $TABLE_NAME = 'users';

sub TABLE       { return $TABLE_NAME }
sub PRIMARY_KEY { return 'id' }
my $COLUMNS = [
    'id',         'group_id',   'login_name', 'login_password',
    'first_name', 'last_name',  'email',      'description',
    'created_by', 'changed_by', 'tstamp',     'reg_date',
    'disabled',   'start_date', 'stop_date'
];

sub COLUMNS { return $COLUMNS }
my $ALIASES = {};

sub ALIASES { return $ALIASES }
my $CHECKS = {

lib/Ado/Model/Users.pm  view on Meta::CPAN

        'required' => 1,
        'defined'  => 1,
        'allow'    => qr/(?^x:^\d{1,1}$)/,
        'default'  => '1'
    },
    'tstamp' => {
        'required' => 1,
        'defined'  => 1,
        'allow'    => qr/(?^x:^\d{1,10}$)/
    },
    'login_password' => {
        'required' => 1,
        'defined'  => 1,

        #result of Mojo::Util::sha1_hex($login_name.$login_password)
        'allow' => qr/^[A-Fa-f0-9]{40}$/x
    },
    'stop_date'   => {'allow' => qr/(?^x:^-?\d{1,}$)/},
    'description' => {
        'allow'   => qr/(?^x:^.{0,255}$)/,
        'default' => ''
    },
    'last_name' => {'allow' => qr/(?^x:^.{1,100}$)/},
    'email'     => {
        'required' => 1,

lib/Ado/Model/Users.pm  view on Meta::CPAN

        'required' => 1,
        'defined'  => 1,
        'allow'    => qr/(?^x:^-?\d{1,}$)/
    },
    'start_date' => {
        'required' => 1,
        'defined'  => 1,
        'allow'    => qr/(?^x:^-?\d{1,}$)/
    },
    'id'         => {'allow' => qr/(?^x:\d{1,}$)/},
    'login_name' => {
        'required' => 1,
        'defined'  => 1,
        'allow'    => qr/(?^x:^.{1,100}$)/
    },
    'created_by' => {'allow' => qr/(?^x:^-?\d{1,}$)/},
    'first_name' => {'allow' => qr/(?^x:^.{1,100}$)/}
};

sub CHECKS { return $CHECKS }

__PACKAGE__->QUOTE_IDENTIFIERS(0);

#__PACKAGE__->BUILD;#build accessors during load

#find and instantiate a user object by email
sub by_email {
    state $sql = $_[0]->SQL('SELECT') . ' WHERE email=?';
    return $_[0]->query($sql, $_[1]);
}

#find and instantiate a user object by login_name
sub by_login_name {
    state $sql = $_[0]->SQL('SELECT') . ' WHERE login_name=?';
    return $_[0]->query($sql, $_[1]);
}

sub name {
    ref($_[0]) || Carp::croak("The method $_[0]::name must be called only on instances!");
    return $_[0]->{name} ||= do {
        Mojo::Util::trim(
            ($_[0]->{data}{first_name} || '') . ' ' . ($_[0]->{data}{last_name} || ''))
          || $_[0]->{data}{login_name};
    };
}

sub add {
    my $class = shift;
    my $args  = $class->_get_obj_args(@_);
    state $dbix = $class->dbix;

    state $GR = 'Ado::Model::Groups';    #shorten class name
    my ($group, $user);
    my $try = eval {
        $dbix->begin_work;

        #First we need a primary group for the user.
        $group = $GR->create(
            name        => $args->{login_name},
            disabled    => 0,
            description => 'Primary group for user ' . $args->{login_name},
            created_by => $args->{created_by} || 1,
        );

        #Let us create the user now...
        $user = $class->create(
            first_name     => $args->{first_name},
            last_name      => $args->{last_name},
            login_name     => $args->{login_name},
            login_password => $args->{login_password},
            email          => $args->{email},
            disabled       => $args->{disabled},
            tstamp         => time,
            reg_date       => time,
            created_by     => $args->{created_by},
            changed_by     => $args->{changed_by},
            stop_date      => $args->{stop_date},
            start_date     => $args->{start_date},
            description    => $args->{description},
            group_id       => $group->id,

lib/Ado/Model/Users.pm  view on Meta::CPAN

    state $GR   = 'Ado::Model::Groups';    #shorten class name
    my $ingroup;
    my $try = eval {
        $dbix->begin_work;

        #Create the group if it does not exist yet
        if (!(($ingroup = $GR->by_name($args->{ingroup}))->id)) {
            $ingroup = $GR->create(
                name        => $args->{ingroup},
                disabled    => 0,
                description => 'Additional group initially created for user ' . $self->login_name,
                created_by => $args->{created_by} || 1,
            );
        }

        #Link them
        Ado::Model::UserGroup->create(
            user_id  => $self->id,
            group_id => $ingroup->id
        );
        $dbix->commit;

lib/Ado/Model/Users.pm  view on Meta::CPAN

    }
    return @groups;
}

$CLASS->SQL('user_id_by_group_name' => <<"UG");
    SELECT user_id FROM user_group WHERE group_id =
        (SELECT id FROM groups  WHERE name = ?)
UG

$CLASS->SQL('by_group_name' => <<"SQL");
    SELECT id, login_name, first_name, last_name, email
    FROM ${\ $CLASS->TABLE }
    WHERE id IN(${\ $CLASS->SQL('user_id_by_group_name') })
        AND (disabled=0 AND (stop_date>? OR stop_date=0) AND start_date<?)
    ORDER BY first_name, last_name ASC

SQL

#Selects users belonging to a group only.
#returns a list of hashes
sub by_group_name {

lib/Ado/Model/Users.pm  view on Meta::CPAN

=encoding utf8

=head1 NAME

A class for TABLE users in schema main

=head1 SYNOPSIS


    #In a controller use the helper.
    #Find a user by login_name and change the current user
    my $user       = Ado::Model::Users->by_login_name($login_name);
    $c->user($user);

    #in a template
    <h1>Hello, <%=user->name%>!</h1>

    #Create a new user.
    my $user = Ado::Model::Users->add(login_name=>'petko'...);
    #Add the user to a group
    $user->add_to_group('cool');

=head1 DESCRIPTION

This class maps to rows in table C<users>.

=head1 ATTRIBUTES

Ado::Model::Users inherits all attributes from Ado::Model

lib/Ado/Model/Users.pm  view on Meta::CPAN

    <h1>Hello, <%=user->name%>!</h1>

=head1 COLUMNS

Each column from table C<users> has an accessor method in this class.

=head2 id

=head2 group_id

=head2 login_name

=head2 login_password

=head2 first_name

=head2 last_name

=head2 email

=head2 description

=head2 created_by

lib/Ado/Model/Users.pm  view on Meta::CPAN

following additional methods:

=head2 add

Given enough parameters creates a new user object and inserts it  into the
table C<users>. Creates a primary group for the user with the same group
C<name>. Throws an exception if any of the above fails. Returns (the
eventually newly created) user object.

    my $user = Ado::Model::Users->add(
        login_name     => $login_name,
        login_password => Mojo::Util::sha1_hex($login_name.$login_password)
    );

=head2 add_to_group

Adds a user with C<login_name> to a group.
Creates the group if it does not already exists.
Returns the group.

    $ingroup = $user->add_to_group(ingroup=>'admin');

=head2 by_email

Selects a user by email column.

    my $user = Ado::Model::Users->by_email('user@example.com');
    say $user->email if $user->id;

=head2 by_group_name

Selects active users (C<WHERE (disabled=0 AND (stop_date>$now OR stop_date=0)
AND start_date<$now )>) belonging to a given group only and within a given
range, ordered by C<first_name, last_name> alphabetically. C<$limit> defaults
to 500 and C<$offset> to 0. Only the following fields are retrieved: C<id,
login_name, first_name, last_name, email>.

Returns an array of hashes. The L</name> method is executed for each  row in
the resultset and the evaluation is available via key 'name'.

  #get contacts of the user 'berov'
  my @users = Ado::Model::Users->by_group_name('vest_contacts_for_berov', $limit, $offset);

=head2 by_login_name

Selects a user by login_name column.

    my $user = Ado::Model::Users->by_login_name('guest');
    say $user->login_name if $user->id;

=head2 ingroup

Given a group name returns true if a user is member of the group.
Returns false otherwise.
Returns a list of all group names a user belongs to if no group name passed.

    say $user->name . ' is admin!' if $user->ingroup('admin');
    say $user->name .' is member of the following groups:'
    . join(', ', $user->ingroup);

lib/Ado/Plugin/AdoHelpers.pm  view on Meta::CPAN

use Mojo::Base 'Ado::Plugin';
use Mojo::Util qw(decode);
use List::Util qw(first);
use Mojo::File 'path';

# allow plugins to process SQL scripts while loading
sub do_sql_file {
    my ($app, $sql_file) = @_;
    my $dbh = $app->dbix->dbh;

    #$app->log->debug('do_sql_file:' . $sql_file) if $Ado::Control::DEV_MODE;

    my $SQL = decode('UTF-8', path($sql_file)->slurp());

    #Remove multi-line comments
    $SQL =~ s|/\*+.+?\*/\s+?||gsmx;

    #$app->log->debug('$SQL:' . $SQL) if $Ado::Control::DEV_MODE;
    local $dbh->{RaiseError} = 1;
    my $last_statement = '';
    return eval {
        $dbh->begin_work;
        for my $st (split /;/smx, $SQL) {
            $last_statement = $st;
            $dbh->do($st) if ($st =~ /\S+/smx);
        }
        $dbh->commit;
    } || do {
        my $e = "\nError in statement:$last_statement\n$@";
        my $e2;
        eval { $dbh->rollback } || ($e2 = $/ . 'Additionally we have a rollback error:' . $@);
        $app->log->error($e . ($e2 ? $e2 : ''));
        Carp::croak($e . ($e2 ? $e2 : ''));
    };
}

sub register {
    my ($self, $app, $conf) = shift->initialise(@_);

    # Add helpers
    $app->helper(user => sub { shift->user(@_) });

    # http://irclog.perlgeek.de/mojo/2014-10-03#i_9453021
    $app->helper(to_json => sub { Mojo::JSON::to_json($_[1]) });
    Mojo::Util::monkey_patch(ref($app), do_sql_file => \&Ado::Plugin::AdoHelpers::do_sql_file);

    $app->helper('head_css'        => \&_head_css);
    $app->helper('head_javascript' => \&_head_javascript);

    return $self;
}    #end of register

my $file_re = qr/\w+\.\w+(\?.*)?$/;

lib/Ado/Plugin/AdoHelpers.pm  view on Meta::CPAN

  $c->stash(user_as_js => $chars);
  # in a javascript chunk of a template
  var user = <%== $user_as_js %>;
  var user_group_names = <%== to_json([user->ingroup]) %>;

=head2 user

Returns the current user. This is the user C<guest> for not authenticated users.
This helper is a wrapper for L<Ado::Control/user>.

  $c->user(Ado::Model::Users->query("SELECT * from users WHERE login_name='guest'"));
  #in a controller action:
  my $current_user = $c->user;
  #in a template:
  <h1>Hello, <%=user->name%>!</h1>

=head1 METHODS

L<Ado::Plugin::AdoHelpers> inherits all methods from
L<Ado::Plugin> and implements the following new ones.

lib/Ado/Plugin/Auth.pm  view on Meta::CPAN

package Ado::Plugin::Auth;
use Mojo::Base 'Ado::Plugin';
use Mojo::Util qw(class_to_path);

sub register {
    my ($self, $app, $conf) = shift->initialise(@_);

    # Make sure we have all we need from config files.
    $conf->{auth_methods} ||= ['ado'];
    $app->helper(login_ado => \&_login_ado);

    $app->config(auth_methods => $conf->{auth_methods});
    $app->config(ref($self)   => $conf);

    #OAuth2 providers
    my @auth_methods = @{$conf->{auth_methods}};

    if (@auth_methods > 1) {
        for my $m (@auth_methods) {
            next if $m eq 'ado';
            Carp::croak("Configuration options for authentication method \"$m\" "
                  . "are not enough!. Please add them.")
              if (keys %{$conf->{providers}{$m}} < 2);
        }
        $app->plugin('OAuth2', {%{$conf->{providers}}, fix_get_token => 1});
    }

    # Add helpers
    #oauth2 links - helpers after 'ado'
    $app->helper(login_google => \&_login_google)
      if (List::Util::first { $_ eq 'google' } @auth_methods);
    $app->helper(login_facebook => \&_login_facebook)
      if (List::Util::first { $_ eq 'google' } @auth_methods);

    # Add conditions
    $app->routes->add_condition(authenticated => \&authenticated);
    $app->routes->add_condition(
        ingroup => sub {
            $_[1]->debug("is user " . $_[1]->user->name . " in  group $_[-1]?")
              if $Ado::Control::DEV_MODE;
            return $_[1]->user->ingroup($_[-1]);
        }
    );
    $app->hook(
        after_user_add => sub {
            my ($c, $user, $raw_data) = @_;
            $app->log->info($user->description . ' $user->id ' . $user->id . ' added!');
            $c->debug('new user created with arguments:' . $c->dumper($user->data, $raw_data))
              if $Ado::Control::DEV_MODE;
        }
    );
    $app->hook(
        after_login => sub {
            my ($c) = @_;
            $c->session(adobar_links => []);

            # Store a friendly message for the next page in flash
            $c->flash(login_message => $c->l('LoginThanks'));
        }
    );
    return $self;
}


# general condition for authenticating users - redirects to /login
sub authenticated {
    my ($route, $c) = @_;
    $c->debug('in condition "authenticated"') if $Ado::Control::DEV_MODE;
    if ($c->user->login_name eq 'guest') {
        $c->session(over_route => $c->req->url);
        $c->debug('session(over_route => $c->req->url):' . $c->session('over_route'))
          if $Ado::Control::DEV_MODE;
        $c->redirect_to('/login');
        return;
    }
    return 1;
}


#expires the session.
sub logout {
    my ($c) = @_;
    $c->session(expires => 1);
    $c->redirect_to('/');
    return;
}

#authenticate a user /login route implementation is here
sub login {
    my ($c) = @_;

#TODO: add json format

    #prepare redirect url for after login
    unless ($c->session('over_route')) {
        my $base_url = $c->url_for('/')->base;
        my $referrer = $c->req->headers->referrer // $base_url;
        $referrer = $base_url unless $referrer =~ m|^$base_url|;
        $c->session('over_route' => $referrer);
        $c->debug('over_route is ' . $referrer) if $Ado::Control::DEV_MODE;
    }
    my $auth_method = Mojo::Util::trim($c->param('auth_method'));

    return $c->render(status => 200, template => 'login')
      if $c->req->method ne 'POST' && $auth_method eq 'ado';

    #derive a helper name for login the user
    my $login_helper = 'login_' . $auth_method;
    $c->debug('Chosen $login_helper: ' . $login_helper) if $Ado::Control::DEV_MODE;

    my $authnticated = 0;
    if (eval { $authnticated = $c->$login_helper(); 1 }) {
        if ($authnticated) {
            $c->app->plugins->emit_hook(after_login => $c);

            # Redirect to referrer page with a 302 response
            $c->debug('redirecting to ' . $c->session('over_route'))
              if $Ado::Control::DEV_MODE;
            $c->redirect_to($c->session('over_route'));
            return;
        }
        else {
            unless ($c->res->code // '' eq '403') {
                $c->stash(error_login => 'Wrong credentials! Please try again!');
                $c->render(status => 401, template => 'login');
                return;
            }
        }
    }
    else {
        $c->app->log->error("Error calling \$login_helper:[$login_helper][$@]");
        $c->stash(error_login => 'Please choose one of the supported login methods.');
        $c->render(status => 401, template => 'login');
        return;
    }
    return;
}

#used as helper 'login_ado' returns 1 on success, '' otherwise
sub _login_ado {
    my ($c) = @_;

    #1. do basic validation first
    my $val = $c->validation;
    return '' unless $val->has_data;
    if ($val->csrf_protect->has_error('csrf_token')) {
        delete $c->session->{csrf_token};
        $c->render(error_login => 'Bad CSRF token!', status => 403, template => 'login');
        return '';
    }
    my $_checks = Ado::Model::Users->CHECKS;
    $val->required('login_name')->like($_checks->{login_name}{allow});
    $val->required('digest')->like(qr/^[0-9a-f]{40}$/);
    if ($val->has_error) {
        delete $c->session->{csrf_token};
        return '';
    }

    #2. find the user and do logical checks
    my $login_name = $val->param('login_name');
    my $user       = Ado::Model::Users->by_login_name($login_name);
    if ((not $user->id) or $user->disabled) {
        delete $c->session->{csrf_token};
        $c->stash(error_login_name => "No such user '$login_name'!");
        return '';
    }

    #3. really authnticate the user
    my $checksum = Mojo::Util::sha1_hex($c->session->{csrf_token} . $user->login_password);
    if ($checksum eq $val->param('digest')) {
        $c->session(login_name => $user->login_name);
        $c->user($user);
        $c->app->log->info('$user ' . $user->login_name . ' logged in!');
        delete $c->session->{csrf_token};
        return 1;
    }

    $c->debug('We should not be here! - wrong password') if $Ado::Control::DEV_MODE;
    delete $c->session->{csrf_token};
    return '';
}

#used as helper within login()
# this method is called as return_url after the user
# agrees or denies access for the application
sub _login_google {
    my ($c) = @_;
    state $app = $c->app;
    my $provider  = $c->param('auth_method');
    my $providers = $app->config('Ado::Plugin::Auth')->{providers};

    #second call should get the token it self
    my $response = $c->oauth2->get_token($provider, $providers->{$provider});
    do {
        $c->debug("in _login_google \$response: " . $c->dumper($response));
        $c->debug("in _login_google error from provider: " . ($c->param('error') || 'no error'));
    } if $Ado::Control::DEV_MODE;
    if ($response->{access_token}) {    #Athenticate, create and login the user.
        return _create_or_authenticate_google_user(
            $c,
            $response->{access_token},
            $providers->{$provider}
        );
    }
    else {
        #Redirect to front-page and say sorry
        # We are very sorry but we need to know you are a reasonable human being.
        $c->flash(error_login => $c->l('oauth2_sorry[_1]', ucfirst($provider))
              . ($c->param('error') || ''));
        $c->app->log->error('error_response:' . $c->dumper($response));
        $c->res->code(307);    #307 Temporary Redirect
        $c->redirect_to('/');
    }
    return;
}

#used as helper within login()
# this method is called as return_url after the user
# agrees or denies access for the application
sub _login_facebook {
    my ($c) = @_;
    state $app = $c->app;
    my $provider  = $c->param('auth_method');
    my $providers = $app->config('Ado::Plugin::Auth')->{providers};

    #second call should get the token it self
    my $response = $c->oauth2->get_token($provider, $providers->{$provider});
    do {
        $c->debug("in _login_facebook \$response: " . $c->dumper($response));
        $c->debug(
            "in _login_facebook error from provider: " . ($c->param('error') || 'no error'));
    } if $Ado::Control::DEV_MODE;
    if ($response->{access_token}) {    #Athenticate, create and login the user.
        return _create_or_authenticate_facebook_user(
            $c,
            $response->{access_token},
            $providers->{$provider}
        );
    }
    else {
        #Redirect to front-page and say sorry
        # We are very sorry but we need to know you are a reasonable human being.
        $c->flash(error_login => $c->l('oauth2_sorry[_1]', ucfirst($provider))
              . ($c->param('error') || ''));
        $c->app->log->error('error_response:' . $c->dumper($response));
        $c->res->code(307);    #307 Temporary Redirect
        $c->redirect_to('/');
    }
    return;

}

sub _authenticate_oauth2_user {
    my ($c, $user, $time) = @_;
    if (   $user->disabled
        || ($user->stop_date != 0 && $user->stop_date < $time)
        || $user->start_date > $time)
    {
        $c->flash(login_message => $c->l('oauth2_disabled'));
        $c->redirect_to('/');
        return;
    }
    $c->session(login_name => $user->login_name);
    $c->user($user);
    $c->app->log->info('$user ' . $user->login_name . ' logged in!');
    return 1;
}

#Creates a user using given info from provider
sub _create_oauth2_user {
    my ($c, $user_info, $provider) = @_;
    state $app = $c->app;
    if (my $user = Ado::Model::Users->add(_user_info_to_args($user_info, $provider))) {
        $app->plugins->emit_hook(after_user_add => $c, $user, $user_info);
        $c->user($user);
        $c->session(login_name => $user->login_name);
        $app->log->info($user->description . ' New $user ' . $user->login_name . ' logged in!');
        $c->flash(login_message => $c->l('oauth2_wellcome[_1]', $user->name));
        $c->redirect_to('/');
        return 1;
    }
    $app->log->error($@);
    return;
}

#next two methods
#(_create_or_authenticate_facebook_user and _create_or_authenticate_google_user)
# exist only because we pass different parameters in the form
# which are specific to the provider.
# TODO: think of a way to map the generation of the form arguments to the
# specific provider so we can dramatically reduce the number of provider
# specific subroutines

lib/Ado/Plugin/Auth.pm  view on Meta::CPAN


    #else create the user
    return _create_oauth2_user($c, $user_info, $provider);
}

# Redirects to Consent screen
sub authorize {
    my ($c)    = @_;
    my $m      = $c->param('auth_method');
    my $params = $c->app->config('Ado::Plugin::Auth')->{providers}{$m};
    $params->{redirect_uri} = '' . $c->url_for("/login/$m")->to_abs;

    #This call will redirect the user to the provider Consent screen.
    $c->redirect_to($c->oauth2->auth_url($m, %$params));
    return;
}

# Maps user info given from provider to arguments for
# Ado::Model::Users->new
sub _user_info_to_args {
    my ($ui, $provider) = @_;

lib/Ado/Plugin/Auth.pm  view on Meta::CPAN

    elsif (index($provider->{info_url}, 'facebook') > -1) {
        $args{first_name} = $ui->{first_name};
        $args{last_name}  = $ui->{last_name};
    }

    #Add another elsif to map different %args to $ui from a new provider
    else {
        Carp::croak('Unknown provider info_url:' . $provider->{info_url});
    }
    $args{email}      = $ui->{email};
    $args{login_name} = $ui->{email};
    $args{login_name} =~ s/[\@\.]+//g;
    $args{login_password} =
      Mojo::Util::sha1_hex($args{login_name} . Ado::Sessions->generate_id());
    $args{description} = "Registered via $provider->{info_url}!";
    $args{created_by}  = $args{changed_by} = 1;
    $args{start_date}  = $args{disabled} = $args{stop_date} = 0;

    return %args;
}
1;


=pod

lib/Ado/Plugin/Auth.pm  view on Meta::CPAN

To find more about conditions read L<Mojolicious::Guides::Routing/Conditions>.

=head2 authenticated

Condition for routes used to check if a user is authenticated.

=cut

#TODO:?
#Additional parameters can be passed to specify the preferred
#authentication method to be preselected in the login form
#if condition redirects to C</login/:auth_method>.

=pod

  # add the condition programatically
  $app->routes->route('/ado-users/:action', over => {authenticated=>1});
  $app->routes->route('/ado-users/:action',
    over => [authenticated => 1, ingroup => 'admin']
  );

  #in etc/ado.$mode.conf or etc/plugins/foo.$mode.conf

lib/Ado/Plugin/Auth.pm  view on Meta::CPAN

    over => [authenticated => 1, ingroup => 'foo'],
  }
  # programatically
  $app->routes->route('/ado-users/:action', over => {ingroup => 'foo'});

=head1 HELPERS

L<Ado::Plugin::Auth> provides the following helpers for use in
L<Ado::Control> methods and templates.

=head2 login_ado

Finds and logs in a user locally. Returns true on success, false otherwise.

=head2 login_google

Called via C</login/google>. Finds an existing user and logs it in via Google.
Creates a new user if it does not exist and logs it in via Google. The new
user can login via any supported OAuth2 provider as long as it has the same
email. The user can not login using Ado local authentication because he does
not know his password, which is randomly generated. Returns true on success,
false otherwise.

=head2 login_facebook

Called via C</login/facebook>. Finds an existing user and logs it in via
Facebook. Creates a new user if it does not exist and logs it in via Facebook.
The new user can login via any supported Oauth2 provider as long as it has the
same email. The user can not login using Ado local authentication because he
does not know his password, which is randomly generated. Returns true on
success, false otherwise.

=head1 HOOKS

Ado::Plugin::Auth emits the following hooks.

=head2 after_login

In your plugin you can define some functionality to be executed right after a
user has logged in. For example add some links to the adobar template,
available only to logged-in users. Only the controller C<$c> is passed to this
hook.

    #example from Ado::Plugin::Admin
    $app->hook(
        after_login => sub {
            push @{shift->session->{adobar_links} //= []},
              {icon => 'dashboard', href => '/ado', text => 'Dashboard'};
        }
    );


=head2 after_user_add

  $app->hook(after_user_add => sub {
    my ($c, $user, $raw_data) = @_;

lib/Ado/Plugin/Auth.pm  view on Meta::CPAN

=head1 ROUTES

L<Ado::Plugin::Auth> provides the following routes (actions):

=head2 /authorize/:auth_method

Redirects to an OAuth2 provider consent screen where the user can authorize
L<Ado> to use his information or not. Currently L<Ado> supports Facebook and
Google.

=head2 /login

  /login/ado

If accessed using a C<GET> request displays a login form. If accessed via
C<POST> performs authentication using C<ado> system database, and emits the
hook L</after_login>.

  /login/facebook

Facebook consent screen redirects to this action. This action is handled by
L</login_facebook>.


  /login/google

Google consent screen redirects to this action. This action is handled by
L</login_google>.


=head2 /logout

Expires the session and redirects to the base URL.

  $c->logout();

=head1 TEMPLATES

L<Ado::Plugin::Auth> uses the following templates. The paths are in the
C</templates> folder. Feel free to move them to the site_templates folder and
modify them for your needs.

=head2 partials/authbar.html.ep

Renders a menu dropdown for choosing methods for signing in.

=head2 partials/login_form.html.ep

Renders a Login form to authenticate locally.

=head2 login.html.ep

Renders a page containing the login form above.

=head1 METHODS

L<Ado::Plugin::Auth> inherits all methods from L<Ado::Plugin> and implements
the following new ones.


=head2 register

This method is called by C<$app-E<gt>plugin>. Registers the plugin in L<Ado>

lib/Ado/Plugin/I18n.pm  view on Meta::CPAN

    $config->{language_from_host}    //= 1;
    $config->{language_from_param}   //= 1;
    $config->{language_from_cookie}  //= 1;
    $config->{language_from_headers} //= 1;
    $config->{language_param}        //= 'language';

    #Allow other namespaces too
    $config->{namespace} ||= 'Ado::I18n';

    my $e = Mojo::Loader::load_class($config->{namespace});
    $app->log->error(qq{Loading "$config->{namespace}" failed: $e}) if $e;

    # Defaults for $c->stash so templates without controllers can be used
    $app->defaults(language => '', language_from => '');

    #Add helpers
    $app->helper(language => \&language);
    $app->helper(l        => \&_maketext);

    #default routes including language placeholder.
    $app->load_routes($self->routes);



( run in 1.034 second using v1.01-cache-2.11-cpan-49f99fa48dc )