diff --git a/IFComp/lib/IFComp/Controller/Admin/Questions.pm b/IFComp/lib/IFComp/Controller/Admin/Questions.pm new file mode 100644 index 00000000..1ed0ea2f --- /dev/null +++ b/IFComp/lib/IFComp/Controller/Admin/Questions.pm @@ -0,0 +1,98 @@ +package IFComp::Controller::Admin::Questions; +use Moose; +use namespace::autoclean; + +BEGIN { extends 'Catalyst::Controller'; } + +=head1 NAME + +IFComp::Controller::Admin::Questions - Catalyst Controller + +=head1 DESCRIPTION + +Catalyst Controller. + +=head1 METHODS + +=cut + +=head2 index + +=cut + +sub root : Chained('/admin/root') : PathPart('questions') : CaptureArgs(0) { + my ( $self, $c ) = @_; + + unless ( $c->user && $c->check_any_user_role('cheez') ) { + $c->res->redirect('/'); + $c->detach(); + } + +} + +sub index : Chained('root') : PathPart('view') : Args(0) { + my ( $self, $c ) = @_; + + my @questions = $c->model('IFCompDB::Question')->all; + + $c->stash( + template => 'admin/questions/index.tt', + questions => \@questions, + ); +} + +sub save_questions : Chained('root') : PathPart('save') : Args(0) : + Method('POST') { + my ( $self, $c ) = @_; + + my $params = $c->req->body_parameters; + my $guard = $c->model('IFCompDB')->txn_scope_guard; + + foreach my $param_name ( keys %$params ) { + + if ( $param_name =~ /^question_(\d+)$/ ) { + my $question_id = $1; + my $question_text = $params->{$param_name}; + + my $question_row = + $c->model('IFCompDB::Question')->find($question_id); + + if ($question_row) { + if ( $params->{"disable_$question_id"} ) { + $question_row->update( { disabled => 1 } ); + } + else { + $question_row->update( { disabled => 0 } ); + } + $question_row->update( { question_text => $question_text } ); + } + } + } + + if ( $params->{new_question} && $params->{new_question} =~ /\S/ ) { + $c->model('IFCompDB::Question') + ->create( { question_text => $params->{new_question} } ); + } + + $guard->commit; + + $c->flash->{status_msg} = "Questions successfully updated."; + $c->res->redirect( $c->uri_for('/admin/questions/view') ); +} + +=encoding utf8 + +=head1 AUTHOR + +Mark Musante + +=head1 LICENSE + +This library is free software. You can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + +__PACKAGE__->meta->make_immutable; + +1; diff --git a/IFComp/lib/IFComp/Controller/Entry.pm b/IFComp/lib/IFComp/Controller/Entry.pm index ce9c1300..d504e966 100644 --- a/IFComp/lib/IFComp/Controller/Entry.pm +++ b/IFComp/lib/IFComp/Controller/Entry.pm @@ -61,10 +61,12 @@ sub root : Chained('/') : PathPart('entry') : CaptureArgs(0) { my @entries = $c->user->get_object->current_comp_entries; my $current_comp = $c->model('IFCompDB::Comp')->current_comp; + my @questions = $c->model('IFCompDB::Question')->all; $c->stash( - entries => \@entries, - current_comp => $current_comp, + entries => \@entries, + current_comp => $current_comp, + uk_compliance => \@questions, ); } @@ -72,7 +74,8 @@ sub root : Chained('/') : PathPart('entry') : CaptureArgs(0) { sub fetch_entry : Chained('root') : PathPart('') : CaptureArgs(1) { my ( $self, $c, $id ) = @_; - my $entry = $c->model('IFCompDB::Entry')->find($id); + my $entry = $c->model('IFCompDB::Entry') + ->find( $id, { prefetch => { 'entry_answers' => 'question' }, } ); if ( $entry && $entry->author->id eq $c->user->get_object->id ) { $c->stash->{entry} = $entry; $self->entry($entry); @@ -113,7 +116,7 @@ sub create : Chained('root') : PathPart('create') : Args(0) { ); $c->stash( entry => - $c->model('IFCompDB::Entry')->new_result( \%new_result_args ) ); + $c->model('IFCompDB::Entry')->new_result( \%new_result_args ), ); if ( $self->_process_form($c) ) { $c->user->send_author_reminder_email; $c->res->redirect( $c->uri_for_action('/entry/list') ); @@ -257,6 +260,8 @@ sub _process_form { template => 'entry/update.tt', ); + my @questions = $c->model('IFCompDB::Question')->all; + my $params_ref = $c->req->parameters; foreach (qw( main_upload walkthrough_upload cover_upload )) { my $param = "entry.$_"; @@ -328,6 +333,21 @@ sub _process_form { } } + for my $q (@questions) { + my $val = "uk_compliance.q_" . $q->id; + my $ans = "no"; + if ( defined( $params_ref->{$val} ) ) { + $ans = "yes"; + } + + $c->model('IFCompDB::EntryAnswer')->update_or_create( + { entry_id => $entry->id, + question_id => $q->id, + answer => $ans, + } + ); + } + my $genai_data = $params_ref->{"entry.genai"}; my $value = 0; if ( ref($genai_data) eq 'ARRAY' ) { @@ -427,8 +447,12 @@ sub _process_withdrawal_form { my ( $self, $c ) = @_; if ( $self->withdrawal_form->process( params => $c->req->parameters, ) ) { - $c->stash->{entry}->delete; - $c->flash->{entry_withdrawn} = $c->stash->{entry}->title; + my $entry = + $c->model('IFCompDB::Entry')->find( $c->stash->{entry}->id ); + if ($entry) { + $entry->delete; + $c->flash->{entry_withdrawn} = $c->stash->{entry}->title; + } $c->res->redirect( $c->uri_for_action('/entry/list') ); } diff --git a/IFComp/lib/IFComp/Schema/Result/Entry.pm b/IFComp/lib/IFComp/Schema/Result/Entry.pm index e4f7ecab..12722016 100644 --- a/IFComp/lib/IFComp/Schema/Result/Entry.pm +++ b/IFComp/lib/IFComp/Schema/Result/Entry.pm @@ -443,6 +443,21 @@ __PACKAGE__->belongs_to( { is_deferrable => 1, on_delete => "RESTRICT", on_update => "RESTRICT" }, ); +=head2 entry_answers + +Type: has_many + +Related object: L + +=cut + +__PACKAGE__->has_many( + "entry_answers", + "IFComp::Schema::Result::EntryAnswer", + { "foreign.entry_id" => "self.id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + =head2 entry_coauthors Type: has_many @@ -520,8 +535,8 @@ __PACKAGE__->has_many( #>>> -# Created by DBIx::Class::Schema::Loader v0.07052 @ 2024-08-05 22:40:52 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BA11TUZSbhEvXq6M5++njA +# Created by DBIx::Class::Schema::Loader v0.07053 @ 2026-06-16 20:51:39 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:pZ9XCGIkzyTQGGz4JNbWpw __PACKAGE__->add_columns( '+coauthor_code' => { dynamic_default_on_create => '_generate_unique_coauthor_code', }, ); diff --git a/IFComp/lib/IFComp/Schema/Result/EntryAnswer.pm b/IFComp/lib/IFComp/Schema/Result/EntryAnswer.pm new file mode 100644 index 00000000..032efec4 --- /dev/null +++ b/IFComp/lib/IFComp/Schema/Result/EntryAnswer.pm @@ -0,0 +1,123 @@ +#<<< +use utf8; +package IFComp::Schema::Result::EntryAnswer; + +# Created by DBIx::Class::Schema::Loader +# DO NOT MODIFY THE FIRST PART OF THIS FILE + +=head1 NAME + +IFComp::Schema::Result::EntryAnswer + +=cut + +use strict; +use warnings; + + +=head1 BASE CLASS: L + +=cut + +use Moose; +use MooseX::NonMoose; +use MooseX::MarkAsMethods autoclean => 1; +extends 'IFComp::Schema::Result'; + +=head1 TABLE: C + +=cut + +__PACKAGE__->table("entry_answers"); + +=head1 ACCESSORS + +=head2 entry_id + + data_type: 'integer' + extra: {unsigned => 1} + is_foreign_key: 1 + is_nullable: 0 + +=head2 question_id + + data_type: 'integer' + is_foreign_key: 1 + is_nullable: 0 + +=head2 answer + + data_type: 'varchar' + is_nullable: 1 + size: 255 + +=cut + +__PACKAGE__->add_columns( + "entry_id", + { + data_type => "integer", + extra => { unsigned => 1 }, + is_foreign_key => 1, + is_nullable => 0, + }, + "question_id", + { data_type => "integer", is_foreign_key => 1, is_nullable => 0 }, + "answer", + { data_type => "varchar", is_nullable => 1, size => 255 }, +); + +=head1 PRIMARY KEY + +=over 4 + +=item * L + +=item * L + +=back + +=cut + +__PACKAGE__->set_primary_key("entry_id", "question_id"); + +=head1 RELATIONS + +=head2 entry + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "entry", + "IFComp::Schema::Result::Entry", + { id => "entry_id" }, + { is_deferrable => 1, on_delete => "CASCADE", on_update => "RESTRICT" }, +); + +=head2 question + +Type: belongs_to + +Related object: L + +=cut + +__PACKAGE__->belongs_to( + "question", + "IFComp::Schema::Result::Question", + { id => "question_id" }, + { is_deferrable => 1, on_delete => "RESTRICT", on_update => "RESTRICT" }, +); + +#>>> + +# Created by DBIx::Class::Schema::Loader v0.07053 @ 2026-06-16 20:51:39 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:zfm0Hfg8Ac4nMJbK/YwfTg + +# You can replace this text with custom code or comments, and it will be preserved on regeneration +__PACKAGE__->meta->make_immutable; +1; diff --git a/IFComp/lib/IFComp/Schema/Result/Question.pm b/IFComp/lib/IFComp/Schema/Result/Question.pm new file mode 100644 index 00000000..12793b21 --- /dev/null +++ b/IFComp/lib/IFComp/Schema/Result/Question.pm @@ -0,0 +1,100 @@ +#<<< +use utf8; +package IFComp::Schema::Result::Question; + +# Created by DBIx::Class::Schema::Loader +# DO NOT MODIFY THE FIRST PART OF THIS FILE + +=head1 NAME + +IFComp::Schema::Result::Question + +=cut + +use strict; +use warnings; + + +=head1 BASE CLASS: L + +=cut + +use Moose; +use MooseX::NonMoose; +use MooseX::MarkAsMethods autoclean => 1; +extends 'IFComp::Schema::Result'; + +=head1 TABLE: C + +=cut + +__PACKAGE__->table("questions"); + +=head1 ACCESSORS + +=head2 id + + data_type: 'integer' + is_auto_increment: 1 + is_nullable: 0 + +=head2 question_text + + data_type: 'varchar' + is_nullable: 0 + size: 255 + +=head2 disabled + + data_type: 'tinyint' + default_value: 1 + is_nullable: 1 + +=cut + +__PACKAGE__->add_columns( + "id", + { data_type => "integer", is_auto_increment => 1, is_nullable => 0 }, + "question_text", + { data_type => "varchar", is_nullable => 0, size => 255 }, + "disabled", + { data_type => "tinyint", default_value => 1, is_nullable => 1 }, +); + +=head1 PRIMARY KEY + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->set_primary_key("id"); + +=head1 RELATIONS + +=head2 entry_answers + +Type: has_many + +Related object: L + +=cut + +__PACKAGE__->has_many( + "entry_answers", + "IFComp::Schema::Result::EntryAnswer", + { "foreign.question_id" => "self.id" }, + { cascade_copy => 0, cascade_delete => 0 }, +); + +#>>> + +# Created by DBIx::Class::Schema::Loader v0.07053 @ 2026-06-16 19:53:01 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:zRSkaJHmZmFhsF+2NtgpTg + +# You can replace this text with custom code or comments, and it will be preserved on regeneration +__PACKAGE__->meta->make_immutable; +1; diff --git a/IFComp/root/src/admin/index.tt b/IFComp/root/src/admin/index.tt index 741994ad..b210204a 100644 --- a/IFComp/root/src/admin/index.tt +++ b/IFComp/root/src/admin/index.tt @@ -23,6 +23,7 @@
  • User Validation
  • Download CSV of Results
  • Download CSV of Payout Results
  • +
  • Modify UK Compliance Questions
  • [% END %] [% IF c.check_user_roles( 'prizemanager' ) %]
  • Prize Management
  • diff --git a/IFComp/root/src/admin/questions/index.tt b/IFComp/root/src/admin/questions/index.tt new file mode 100644 index 00000000..93d2a646 --- /dev/null +++ b/IFComp/root/src/admin/questions/index.tt @@ -0,0 +1,79 @@ +
    +

    UK Compliance Questions

    + + [% IF c.flash.status_msg %] + +[% END %] + +
    + +
    +
    + + + + + + + + + [% FOR q IN questions %] + + + + + [% END %] + + + + + + +
    Question TextActions
    + +
    Question text cannot be blank.
    +
    +
    + + +
    +
    +
    + + + + +
    +
    +   +
    +
    + + +
    + +
    +
    diff --git a/IFComp/root/src/entry/_form.tt b/IFComp/root/src/entry/_form.tt index df9b82e7..787a12b8 100644 --- a/IFComp/root/src/entry/_form.tt +++ b/IFComp/root/src/entry/_form.tt @@ -230,6 +230,49 @@ You may optionally provide a brief warning that your work contains emotionally i + +
    +

    UK Compliance Information

    +
    +

    +Due to requirements of the UK Online Safety Act (OSA), +some entries may require additional review and could be unavailable to judges in the United +Kingdom. In the 2025 post-competition survey, approximately 11% of responding authors and 8% of +responding judges reported that these restrictions affected their competition experience. +

    + +

    Please select any topics that may be present in your entry:

    + +

    +[% IF entry.id %] + [% FOR q IN entry.entry_answers %] + [% IF NOT q.question.disabled %] + +
    + [% END %] + [% END %] +[% ELSE %] + [% FOR q IN uk_compliance %] + [% IF NOT q.disabled %] + +
    + [% END %] + [% END %] +[% END %] +

    + +

    +Please note: This information helps organizers identify entries that may require +additional review, but all entries will be reviewed regardless of the selections made above. +Selections do not automatically determine whether an entry will be available in the United +Kingdom. If you have questions or would like to discuss the unique aspects of your entry, please +contact us at ifcomp@ifcomp.org. +

    + +
    +
    + +

    Displayed name(s) and contact info

    diff --git a/IFComp/t/controller_Admin-Questions.t b/IFComp/t/controller_Admin-Questions.t new file mode 100644 index 00000000..8ddfd0b6 --- /dev/null +++ b/IFComp/t/controller_Admin-Questions.t @@ -0,0 +1,25 @@ +use strict; +use warnings; +use Test::More; + +unless ( eval q{use Test::WWW::Mechanize::Catalyst 0.55; 1} ) { + plan skip_all => 'Test::WWW::Mechanize::Catalyst >= 0.55 required'; + exit 0; +} + +use FindBin; +use lib ("$FindBin::Bin/lib"); +use IFCompTest; +my $schema = IFCompTest->init_schema(); + +use Catalyst::Test 'IFComp'; +use IFComp::Controller::Admin::Questions; + +ok( my $mech = + Test::WWW::Mechanize::Catalyst->new( catalyst_app => 'IFComp' ), + 'Created mech object' +); + +IFCompTest::log_in_as_cheez($mech); +ok( $mech->get_ok('/admin/questions/view'), 'Request should succeed' ); +done_testing(); diff --git a/IFComp/t/lib/IFCompTestData.pm b/IFComp/t/lib/IFCompTestData.pm index 8dce581e..e51ca1dc 100644 --- a/IFComp/t/lib/IFCompTestData.pm +++ b/IFComp/t/lib/IFCompTestData.pm @@ -25,6 +25,26 @@ sub add_test_data_to_schema { ], ); + $schema->populate( + 'Question', + [ [ 'id', 'question_text', 'disabled' ], + [ 1, "Images depicting sexual content", 0 ], + [ 2, "Suicide or self-harm", 0 ], + [ 3, "Eating disorders", 0 ], + [ 4, + "Hate speech, discriminatory content, or content targeting people based on protected characteristics", + 0 + ], + [ 5, "Graphic or realistic depictions of violence/blood/gore", + 0 + ], + [ 6, "Bullying or harassment", 0 ], + [ 7, "Harmful substance use or abuse", 0 ], + [ 8, "Dangerous challenges, stunts, or risky activities", 0 ], + [ 9, "None of the above", 0 ], + ], + ); + $schema->populate( 'User', [ [ 'id', 'name', diff --git a/db-updates.txt b/db-updates.txt new file mode 100644 index 00000000..17c4441b --- /dev/null +++ b/db-updates.txt @@ -0,0 +1,16 @@ +Entry Questions + +CREATE TABLE questions ( + id INT AUTO_INCREMENT PRIMARY KEY, + question_text VARCHAR(255) NOT NULL, + disabled BOOLEAN DEFAULT TRUE +); + +CREATE TABLE entry_answers ( + entry_id int(10) unsigned, + question_id INT, + answer VARCHAR(255), + PRIMARY KEY (entry_id, question_id), + FOREIGN KEY (entry_id) REFERENCES entry(id) ON DELETE CASCADE, + FOREIGN KEY (question_id) REFERENCES questions(id) +);