diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 1451ef28fb..fb85d43a17 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -44,6 +44,7 @@ jobs: VALIDATE_CHECKOV: false VALIDATE_JSCPD: false VALIDATE_LATEX: false + VALIDATE_CSS: false FIX_YAML_PRETTIER: true VALIDATE_JAVASCRIPT_PRETTIER: false VALIDATE_JAVASCRIPT_STANDARD: false diff --git a/db/00201/AddWikiTable.pm b/db/00201/AddWikiTable.pm new file mode 100644 index 0000000000..69fee5e812 --- /dev/null +++ b/db/00201/AddWikiTable.pm @@ -0,0 +1,80 @@ +#!/usr/bin/env perl + + +=head1 NAME + + AddWikiTable.pm + +=head1 SYNOPSIS + +mx-run AddWikiTable [options] -H hostname -D dbname -u username [-F] + +this is a subclass of L +see the perldoc of parent class for more details. + +=head1 DESCRIPTION + +This is a test dummy patch. +This subclass uses L. The parent class uses L + +=head1 AUTHOR + + Naama Menda + +=head1 COPYRIGHT & LICENSE + +Copyright 2010 Boyce Thompson Institute for Plant Research + +This program is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + + +package AddWikiTable; + +use Moose; +extends 'CXGN::Metadata::Dbpatch'; + + +has '+description' => ( default => <<'' ); +Description of this patch goes here + +has '+prereq' => ( + default => sub { + [], + }, + ); + +sub patch { + my $self=shift; + + print STDOUT "Executing the patch:\n " . $self->name . ".\n\nDescription:\n ". $self->description . ".\n\nExecuted by:\n " . $self->username . " ."; + + print STDOUT "\nChecking if this db_patch was executed before or if previous db_patches have been executed.\n"; + + print STDOUT "\nExecuting the SQL commands.\n"; + + $self->dbh->do(< (isa => 'Ref', is => 'rw'); + +has page_name => ( isa => 'Str', + is => 'rw'); + +has page_content => ( isa => 'Str', + is => 'rw'); + +has page_version => ( isa => 'Maybe[Int]', + is => 'rw'); + +has sp_person_id => ( isa => 'Int', + is => 'rw'); + +has sp_wiki_id => (isa => 'Int', + is => 'rw'); + +has create_date => (isa => 'Str', + is => 'rw'); + + +sub BUILD { + my $self = shift; + + my $row = $self->people_schema()->resultset("SpWiki")->find( { page_name => $self->page_name } ); + + if ($row) { + $self->sp_person_id($row->sp_person_id()); + $self->create_date($row->create_date()); + $self->sp_wiki_id($row->sp_wiki_id()); + $self->page_version($self->get_version()); + } + +} + + +sub new_page { + my $self = shift; + my $page_name = shift || $self->page_name(); + my $sp_person_id = shift || $self->sp_person_id(); + + my $row = $self->people_schema()->resultset("SpWiki")->find( { page_name => $page_name } ); + + if ($row) { + die "Page named $page_name already exists!\n"; + } + elsif (! $sp_person_id) { + die "The sp_person_id parameter is required!"; + } + else { + my $new_data = { + page_name => $page_name, + sp_person_id => $sp_person_id, + + }; + + my $new_row = $self->people_schema()->resultset("SpWiki")->create($new_data); + + return $new_row->sp_wiki_id(); + + } + +} + +sub retrieve_page { + my $self = shift; + my $page_name = shift; + + print STDERR "RETRIEVING WIKI PAGE NAMED $page_name\n"; + + my $row = $self->people_schema()->resultset("SpWiki")->find( { page_name => $page_name }); + + # if (! $row && ($page_name eq "" || $page_name eq "WikiHome" )) { + + # print STDERR "NO PAGE EXISTS... RETURNING GREETING\n"; + # return { + # page_content => "WELCOME TO THE WIKI!", + # page_version => 0, + # }; + + # } + + if (! $row) { + print STDERR "PAGE DOES NOT EXIST!\n"; + die "The page with name $page_name does not exist!"; + } + + else { + my $sp_wiki_id = $row->sp_wiki_id(); + + print STDERR "RETRIEVING PAGE $page_name WITH sp_wiki_id $sp_wiki_id\n"; + my $content_rs = $self->people_schema()->resultset("SpWikiContent")->search( { sp_wiki_id => $sp_wiki_id }, { order_by => { -desc => 'page_version' } } ); + + my $content_row; + if ($content_rs->count() > 0) { + $content_row = $content_rs->next(); + + $self->page_name($page_name); + $self->page_version($content_row->page_version()); + $self->page_content($content_row->page_content()); + $self->sp_person_id($row->sp_person_id()); + + return { + page_content => $content_row->page_content(), + page_version => $content_row->page_version(), + sp_person_id => $row->sp_person_id(), + }; + } + + else { + return; + } + } + +} + + +sub store_page { + my $self = shift; + my $page_name = shift || $self->page_name() || 'WikiHome'; + my $content = shift || $self->page_content(); + my $sp_person_id = shift || $self->sp_person_id(); + + print STDERR "STORE_PAGE: $page_name, $content\n"; + + my $row = $self->people_schema()->resultset("SpWiki")->find( { page_name => $page_name }); + + # if (! $row && $page_name eq 'WikiHome') { + # $row = $self->people_schema()->resultset("SpWiki")->create( + # { + # sp_person_id => $sp_person_id, + # page_name => "WikiHome", + # }); + + # $row->insert(); + + # } + if (! $row) { + print STDERR "THE WIKI PAGE DOES NOT EXIST ($page_name)\n"; + die "The page with page name $page_name does not exist!"; + } + + my $sp_wiki_id = $row->sp_wiki_id(); + + # figure out previous version, if any + # + my $current_version = 0; + + my $previous_content_rs = $self->people_schema()->resultset("SpWikiContent")->search( { sp_wiki_id => $sp_wiki_id }, { order_by => { -desc => 'page_version' } } ); + + my $previous_content_row; + + print STDERR "FINDING CURRENT VERSION...\n"; + + if ($previous_content_rs->count() > 0) { + print STDERR "WE HAVE PREVIOUS DATA...\n"; + $previous_content_row = $previous_content_rs->next(); + if ($previous_content_row->page_content() eq $content) { + return { error => "The new content is identical to the content of the page already exists!" }; + } + + if ($previous_content_row) { + print STDERR "WE HAVE A ROW...\n"; + $current_version = $previous_content_row->page_version(); + } + } + + print STDERR "CURRENT VERSION: $current_version\n"; + + my $new_version = $current_version + 1; + + print STDERR "NEW VERSION : $new_version\n"; + my $wiki_content = { + page_content => $content, + page_version => $new_version, + sp_wiki_id => $sp_wiki_id, + }; + + my $new_row; + eval { + print STDERR "STORING PAGE DATA... $content\n"; + $new_row = $self->people_schema()->resultset("SpWikiContent")->create($wiki_content); + $new_row->insert(); + }; + if ($@) { + print STDERR "An error occurred storing content. $@\n"; + return { error => $@ }; + } + + $self->page_content($content); + $self->page_version($new_version); + + return { + page_version => $new_row->page_version(), + wiki_content_id => $new_row->sp_wiki_content_id() + }; +} + +sub delete { + my $self = shift; + my $page_name = shift || $self->page_name(); + + print STDERR "DELETING PAGE $page_name\n"; + + my $row = $self->people_schema()->resultset("SpWiki")->find( { page_name => $page_name }); + + $row->delete(); +} + + +sub get_version { + my $self =shift; + my $page_name = shift || $self->page_name(); + + my $row = $self->people_schema()->resultset("SpWiki")->find( { page_name => $page_name }); + + my $page_version; + + if (! $row) { + return 0; + } + + my $version_rs = $self->people_schema()->resultset("SpWikiContent")->search( { sp_wiki_id => $row->sp_wiki_id() }, { order_by => { -desc => 'page_version' } } ); + + if ($version_rs->count() > 0) { + my $version_row = $version_rs->next(); + $page_version = $version_row->page_version(); + } + + return $page_version; +} + + +sub all_pages { + my $self = shift; + + my $rs = $self->people_schema()->resultset("SpWiki")->search(); + + my @pages; + while (my $row = $rs->next()) { + push @pages, $row->page_name(); + } + + @pages = sort(@pages); + + print STDERR "PAGES ".Dumper(\@pages); + return @pages; +} + +1; diff --git a/lib/SGN/Controller/AJAX/Wiki.pm b/lib/SGN/Controller/AJAX/Wiki.pm new file mode 100644 index 0000000000..a9d480c7c9 --- /dev/null +++ b/lib/SGN/Controller/AJAX/Wiki.pm @@ -0,0 +1,236 @@ + +package SGN::Controller::AJAX::Wiki; + +use Data::Dumper; +use URI::FromHash 'uri'; +use Text::MultiMarkdown qw | markdown |; +use CXGN::People::Wiki; + +use Moose; + +BEGIN { extends 'Catalyst::Controller::REST'; } + +__PACKAGE__->config( + default => 'application/json', + stash_key => 'rest', + map => { 'application/json' => 'JSON', 'text/html' => 'JSON' }, + ); + + +has people_schema => ( isa => 'Ref', + is => 'rw' + ); + +# has page_title => ( isa => 'Str', +# is => 'rw'); + +# has page_content => ( isa => 'Str', +# is => 'rw'); + +# has page_version => ( isa => 'Int', +# is => 'rw'); + +# has sp_person_id => ( isa => 'Int', +# is => 'rw'); + +has wiki_id => (isa => 'Int', + is => 'rw'); + + +sub ajax_wiki : Chained('/') PathPart('ajax/wiki') CaptureArgs(1) { + my $self = shift; + my $c = shift; + + $c->stash->{page_name} = shift; +} + +sub ajax_wiki_new :Path('/ajax/wiki/new') Args(0) { + my $self = shift; + my $c = shift; + + if (! $c->user()->check_roles("curator")) { + $c->stash->{rest} = { error => "You do not have the privileges to create wiki pages." }; + $c->detach(); + } + + my $user_id = $c->user->get_object->get_sp_person_id(); + + my $page_name = $c->req->param('page_name'); + + my $wiki_page_name = $page_name; + $wiki_page_name =~ s/(_|-|\s+)(\w)/\U$2/g; + $wiki_page_name =~ s/\W//g; + + + my $sp_wiki_id; + + my $wiki = CXGN::People::Wiki->new( { people_schema => $c->dbic_schema("CXGN::People::Schema") } ); + + + eval { + if (length($wiki_page_name) == 0) { + die "The name of the wiki page cannot be the empty string or consist only of special characters."; + } + + $wiki->page_name($wiki_page_name); + $wiki->sp_person_id($user_id); + $sp_wiki_id = $wiki->new_page(); + }; + + if ($@) { + print STDERR "This error occurred trying to generate a page: $@\n"; + $c->stash->{rest} = { error => $@ }; + } + else { + print STDERR "Page creation a success! $sp_wiki_id with $wiki_page_name\n"; + $c->stash->{rest} = { success => 1, sp_wiki_id => $sp_wiki_id, page_name => $wiki_page_name }; + } +} + +# stores the content for a given wiki_id +# +sub store : Chained('ajax_wiki') PathPart('store') Args(0) ActionClass('REST') { } + +sub store_POST { + my $self = shift; + my $c = shift; + + my $sp_person_id = $c->user->get_object->get_sp_person_id(); + + if (! $sp_person_id) { + $c->stash->{rest} = { error => "You need to be logged in to store wiki pages." }; + $c->detach(); + } + + my $page_name = $c->stash->{page_name}; + + if (! $c->user()->check_roles("curator")) { + $c->stash->{rest} = { error => "You do not have the privileges to modify wiki pages." }; + $c->detach(); + } + + print STDERR "WIKI ID FOR STORE: $page_name\n"; + + my $wiki = CXGN::People::Wiki->new( { people_schema => $c->dbic_schema("CXGN::People::Schema"), page_name => $page_name } ); + + print STDERR "PAGE OWNED BY ".$wiki->sp_person_id()."\n"; + if ($wiki->sp_person_id() != $sp_person_id) { + $c->stash->{rest} = { error => "Only the original owner can modify the wiki page." }; + $c->detach(); + } + my $content_data; + + my $content = $c->req->param('content'); + + eval { + print STDERR "CONTROLLER: STORE $page_name, $content\n"; + $content_data = $wiki->store_page($page_name, $content, $sp_person_id); + }; + + if ($@) { + print STDERR "An error occurred in store_POST: $@\n"; + $c->stash->{rest} = { error => $@ }; + $c->detach(); + } + + $c->stash->{rest} = { + success => 1, + wiki_content_id => $content_data->{wiki_content_id}, + version => $content_data->{page_version}, + }; +} + + +sub delete : Chained('ajax_wiki') PathPart('delete') Args(0) { + my $self = shift; + my $c = shift; + + print STDERR "DELETE WIKI PAGE ".$c->stash->{page_name}."\n"; + + if (! $c->user()->check_roles("curator")) { + $c->stash->{rest} = { error => "You do not have the privileges to delete wiki pages." }; + $c->detach(); + } + my $user_id = $c->user()->get_object->get_sp_person_id(); + + my $wiki = CXGN::People::Wiki->new( { people_schema => $c->dbic_schema("CXGN::People::Schema"), page_name => $c->stash->{page_name} } ); + + print STDERR "WIKI PAGE OWNER: ".$wiki->sp_person_id()." (user id is $user_id)\n"; + + if ($user_id != $wiki->sp_person_id()) { + $c->stash->{rest} = { error => "Only the original page creator can delete the page. It is not you." }; + $c->detach(); + } + + eval { + $wiki->page_name($c->stash->{page_name}); + $wiki->delete(); + }; + if ($@) { + $c->stash->{rest} = { error => $@ }; + } + else { + $c->stash->{rest} = { success => 1 }; + } + + +} + +sub view : Chained('ajax_wiki') PathPart('view') Args(0) { + my $self = shift; + my $c = shift; + + print STDERR "VIEW PAGE NAMED ".$c->stash->{page_name}."\n"; + + if (! $c->user()) { + $c->res->redirect( uri( path => '/user/login', query => { goto_url => $c->req->uri->path_query } ) ); + } + + my $wiki = CXGN::People::Wiki->new( { people_schema => $c->dbic_schema("CXGN::People::Schema"), page_name => $c->stash->{page_name} } ); + + my $page_content; + my $page_version; + + eval { + my $page_data = $wiki->retrieve_page($c->stash->{page_name}); + + print STDERR "PAGE NOW: ".Dumper($page_data); + $page_content = $page_data->{page_content}; + + $page_version = $wiki->get_version(); + + }; + if ($@) { + $c->stash->{rest} = { error => "An error occurred retrieving the page. It may not exist $@" }; + $c->detach(); + } + my $wiki_html = markdown($page_content, { use_wikilinks => 1, base_url => '/wiki/' } ); + + $c->stash->{rest} = { + html => $wiki_html, + page_version => $page_version, + }; + +} + + +sub retrieve : Chained('ajax_wiki') PathPart('retrieve') Args(0) { + my $self = shift; + my $c = shift; + + print STDERR "RETRIEVING WIKI PAGE NAMED ".$c->stash->{page_name}."\n"; + + my $wiki = CXGN::People::Wiki->new( { people_schema => $c->dbic_schema("CXGN::People::Schema"), page_name => $c->stash->{page_name} } ); + + my $page_data = $wiki->retrieve_page($c->stash->{page_name}); + my $page_content = $page_data->{page_content}; + my $page_version = $page_data->{page_version}; + + $c->stash->{rest} = { + raw => $page_content, + page_version => $page_version, + }; +} + + +1; diff --git a/lib/SGN/Controller/Wiki.pm b/lib/SGN/Controller/Wiki.pm new file mode 100644 index 0000000000..c13fa3b1d3 --- /dev/null +++ b/lib/SGN/Controller/Wiki.pm @@ -0,0 +1,58 @@ + +package SGN::Controller::Wiki; + +use Moose; +use URI::FromHash 'uri'; + +BEGIN { extends 'Catalyst::Controller'; } + + + + +sub view_page :Path('/wiki/') Args(1) { + my $self = shift; + my $c = shift; + + my $page_name = shift; + + if (! $c->user()) { + $c->res->redirect( uri( path => '/user/login', query => { goto_url => $c->req->uri->path_query } ) ); + } + + $c->stash->{page_name} = $page_name; + $c->stash->{template} = '/wiki/view.mas'; + +} + + +sub view_home :Path('/wiki/') Args(0) { + my $self = shift; + my $c = shift; + + print STDERR "WIKI HOME\n"; + + if (! $c->user()) { + $c->res->redirect( uri( path => '/user/login', query => { goto_url => $c->req->uri->path_query } ) ); + } + + $c->stash->{template} = '/wiki/view.mas'; +} + +sub all_pages :Path('/wiki/all_pages') Args(0) { + my $self = shift; + my $c = shift; + + if (! $c->user()) { + $c->res->redirect( uri( path => '/user/login', query => { goto_url => $c->req->uri->path_query } ) ); + } + + my $wiki = CXGN::People::Wiki->new( { people_schema => $c->dbic_schema("CXGN::People::Schema") } ); + + my @all_pages = $wiki->all_pages(); + + $c->stash->{all_pages} = \@all_pages; + $c->stash->{template} = '/wiki/all_pages.mas'; +} + + +1; diff --git a/mason/wiki/all_pages.mas b/mason/wiki/all_pages.mas new file mode 100644 index 0000000000..30362b8774 --- /dev/null +++ b/mason/wiki/all_pages.mas @@ -0,0 +1,13 @@ + +<%args> +@all_pages + + +

Wiki

+% use Data::Dumper; +% print STDERR "PAGES NOW: ".Dumper(\@all_pages); +

Wiki - all pages

+ +% foreach my $p (@all_pages) { + <% $p %>
+% } diff --git a/mason/wiki/view.mas b/mason/wiki/view.mas new file mode 100644 index 0000000000..7ee14d542c --- /dev/null +++ b/mason/wiki/view.mas @@ -0,0 +1,247 @@ + +<%args> +$page_name => 'WikiHome' + + +<& /util/import_javascript.mas, classes => [ 'CXGN.Login' ] &> + + + +

Wiki    +    All Pages    Upload    Syntax

+
+? (v?) +
+ + + + +
+
+ + diff --git a/t/selenium2/wiki/Wiki.t b/t/selenium2/wiki/Wiki.t new file mode 100644 index 0000000000..27d6acfadf --- /dev/null +++ b/t/selenium2/wiki/Wiki.t @@ -0,0 +1,95 @@ + +use strict; + +use lib 't/lib'; + +use Test::More qw| no_plan |; +use SGN::Test::Fixture; +use SGN::Test::WWW::WebDriver; + +my $d = SGN::Test::WWW::WebDriver->new(); + +my $f = SGN::Test::Fixture->new(); + +$d->while_logged_in_as("curator", sub { + + $d->get_ok("/wiki/WikiHome", "get wiki home page"); + sleep(2); + + $d->driver()->accept_alert(); + + sleep(2); + + my $wiki_page_content = $d->find_element_ok("wiki_page_content", "id", "find wiki_page_content text area"); + $wiki_page_content->send_keys("#Big Title!\n##Smaller Title\nBla bla bla\n"); + + sleep(2); + + my $save_wiki_page_button = $d->find_element_ok("save_wiki_page_button", "id", "find wiki page save button"); + $save_wiki_page_button->click(); + + my $contents = $d->driver()->get_body(); + like($contents, qr/Big Title\!/, "check page contents"); + like($contents, qr/Smaller Title/, "check more page contents"); + like($contents, qr/Bla bla bla/, "check even more page contents"); + + my $edit_wiki_page_button = $d->find_element_ok("edit_wiki_page_button", "id", "find wiki page edit button"); + $edit_wiki_page_button->click(); + + # create a second version of the page + # + my $wiki_page_content = $d->find_element_ok("wiki_page_content", "id", "find wiki_page_content text area"); + $wiki_page_content->send_keys("This is the new content of version 2"); + + $d->find_element_ok("save_wiki_page_button", "id", "find save wiki page button")->click(); + + $contents = $d->driver()->get_body(); + + like($contents, qr/This is the new content of version 2/, "check page contents version 2"); + + sleep(2); + + # create a new unrelated page + # + my $new_wiki_page_button = $d->find_element_ok("new_wiki_page_button", "id", "find new wiki page button"); + $new_wiki_page_button->click(); + + sleep(2); + + my $wiki_page_name_input = $d->find_element_ok("wiki_page_name", "id", "find wiki page name input field again"); + $wiki_page_name_input->send_keys("AnotherTestPage"); + + my $create_wiki_page_button = $d->find_element_ok("create_wiki_page_button","id", "find create wiki page button again"); + $create_wiki_page_button->click(); + + sleep(2); + + $wiki_page_content = $d->find_element_ok("wiki_page_content", "id", "find wiki_page_content text area"); + $wiki_page_content->send_keys("More Stuff"); + + $d->find_element_ok("save_wiki_page_button", "id", "find save wiki page button")->click(); + + sleep(2); + + $contents = $d->driver()->get_body(); + like($contents, qr/More Stuff/, "check another test page contents"); + + sleep(2); + + # check if homepage still exists + # + $d->get_ok("/wiki/WikiHome", "get wiki home page"); + sleep(2); + + $contents = $d->driver()->get_body(); + like($contents, qr/Big Title\!/, "check page contents"); + like($contents, qr/Smaller Title/, "check more page contents"); + like($contents, qr/Bla bla bla/, "check even more page contents"); + + $d->find_element_ok("delete_wiki_page_button", "id", "find delete wiki page button")->click(); + + $d->driver()->accept_alert(); +}); + + +done_testing(); diff --git a/t/unit_fixture/CXGN/Wiki.t b/t/unit_fixture/CXGN/Wiki.t new file mode 100644 index 0000000000..518b38ad73 --- /dev/null +++ b/t/unit_fixture/CXGN/Wiki.t @@ -0,0 +1,49 @@ + +use strict; + +use Data::Dumper; +use Test::More qw | no_plan |; +use lib 't/lib'; +use SGN::Test::Fixture; +use CXGN::People::Wiki; + +my $f = SGN::Test::Fixture->new(); + +my $wiki = CXGN::People::Wiki->new( { people_schema => $f->people_schema(), page_name => 'TestPage' } ); + +$wiki->sp_person_id(41); + +$wiki->new_page('TestPage'); + +$wiki->page_content('BLA BLA BLA'); + + +my $id = $wiki->store_page(); + +ok($id, "page id returned"); + +my $page_data = $wiki->retrieve_page('TestPage'); +print STDERR Dumper($page_data); +is($page_data->{page_content}, "BLA BLA BLA", "page content test"); +is($page_data->{page_version}, 1, "page version test"); +is($page_data->{sp_person_id}, 41, "page owner test"); + +$wiki->page_content('ANOTHER BLA BLA BLA!'); +$id = $wiki->store_page(); + +$page_data = $wiki->retrieve_page('TestPage'); +is($page_data->{page_content}, "ANOTHER BLA BLA BLA!", "page content test after new store"); +is($page_data->{page_version}, 2, "page version test after new store"); +is($page_data->{sp_person_id}, 41, "page owner test after new store"); + +$wiki->delete('TestPage'); + +eval { + $page_data = $wiki->retrieve_page('TestPage'); +}; + +like($@, qr/The page with name TestPage does not exist/, "page deletion test"); + +print STDERR "DELETED PAGE: ".Dumper($page_data); + +done_testing();