├── phpstan-baseline.neon
├── .prettierignore
├── www
├── admin
│ └── styles
│ │ ├── inquisition-details.css
│ │ └── inquisition-question-edit.css
└── javascript
│ ├── inquisition-checkbox-entry-list.js
│ └── inquisition-radio-entry-list.js
├── sql
├── fixtures
│ ├── InquisitionQuestionImageSet.sql
│ ├── InquisitionQuestionOptionImageSet.sql
│ ├── InquisitionQuestionImageDimension.sql
│ └── InquisitionQuestionOptionImageDimension.sql
├── tables
│ ├── Inquisition.sql
│ ├── InquisitionQuestionHint.sql
│ ├── InquisitionQuestionGroup.sql
│ ├── InquisitionQuestion.sql
│ ├── InquisitionQuestionOption.sql
│ ├── InquisitionResponse.sql
│ ├── InquisitionQuestionImageBinding.sql
│ ├── InquisitionQuestionDependency.sql
│ ├── InquisitionQuestionOptionImageBinding.sql
│ ├── InquisitionInquisitionQuestionBinding.sql
│ ├── InquisitionResponseUsedHintBinding.sql
│ └── InquisitionResponseValue.sql
└── views
│ ├── InquisitionResponseTotalQuestionsView.sql
│ ├── VisibleInquisitionQuestionView.sql
│ └── InquisitionResponseGradeView.sql
├── phpstan.dist.neon
├── prettier.config.js
├── .gitignore
├── Inquisition
├── dataobjects
│ ├── InquisitionQuestionImage.php
│ ├── InquisitionQuestionOptionImage.php
│ ├── InquisitionQuestionImageWrapper.php
│ ├── InquisitionQuestionOptionWrapper.php
│ ├── InquisitionQuestionWrapper.php
│ ├── InquisitionResponseWrapper.php
│ ├── InquisitionInquisitionWrapper.php
│ ├── InquisitionQuestionOptionImageWrapper.php
│ ├── InquisitionQuestionHintWrapper.php
│ ├── InquisitionResponseValueWrapper.php
│ ├── InquisitionQuestionGroup.php
│ ├── InquisitionResponseUsedHintBindingWrapper.php
│ ├── InquisitionInquisitionQuestionBindingWrapper.php
│ ├── InquisitionQuestionHint.php
│ ├── InquisitionResponseUsedHintBinding.php
│ ├── InquisitionResponseValue.php
│ ├── InquisitionQuestionOption.php
│ ├── InquisitionInquisitionQuestionBinding.php
│ ├── InquisitionQuestion.php
│ ├── InquisitionResponse.php
│ └── InquisitionInquisition.php
├── exceptions
│ └── InquisitionImportException.php
├── InquisitionQuestionOptionCellRenderer.php
├── admin
│ ├── components
│ │ ├── Question
│ │ │ ├── correct-option.xml
│ │ │ ├── import.xml
│ │ │ ├── hint-edit.xml
│ │ │ ├── edit.xml
│ │ │ ├── add.xml
│ │ │ ├── index.xml
│ │ │ ├── ImageDelete.php
│ │ │ ├── Import.php
│ │ │ ├── Index.php
│ │ │ ├── Order.php
│ │ │ ├── ImageUpload.php
│ │ │ ├── Edit.php
│ │ │ ├── HintOrder.php
│ │ │ ├── CorrectOption.php
│ │ │ └── ImageOrder.php
│ │ ├── Option
│ │ │ ├── edit.xml
│ │ │ ├── details.xml
│ │ │ ├── ImageDelete.php
│ │ │ ├── ImageUpload.php
│ │ │ ├── Order.php
│ │ │ ├── ImageOrder.php
│ │ │ └── Delete.php
│ │ └── Inquisition
│ │ │ ├── image-upload.xml
│ │ │ ├── image-delete.xml
│ │ │ ├── index.xml
│ │ │ ├── edit.xml
│ │ │ ├── Edit.php
│ │ │ ├── Index.php
│ │ │ ├── ImageUpload.php
│ │ │ ├── details.xml
│ │ │ ├── Delete.php
│ │ │ └── ImageDelete.php
│ └── InquisitionCorrectOptionRadioButton.php
├── views
│ ├── InquisitionQuestionView.php
│ ├── InquisitionTextQuestionView.php
│ ├── InquisitionFlydownQuestionView.php
│ ├── InquisitionRadioListQuestionView.php
│ ├── InquisitionCheckboxListQuestionView.php
│ ├── InquisitionRadioEntryQuestionView.php
│ └── InquisitionCheckboxEntryQuestionView.php
├── InquisitionImporter.php
├── InquisitionFileParser.php
├── InquisitionRadioEntryList.php
├── Inquisition.php
├── InquisitionCheckboxEntryList.php
└── InquisitionQuestionImporter.php
├── .editorconfig
├── package.json
├── pnpm-lock.yaml
├── README.md
├── .github
├── pull_request_template.md
└── workflows
│ └── pull-requests.yml
├── Jenkinsfile
├── dependencies
└── inquisition.yaml
├── composer.json
└── .php-cs-fixer.php
/phpstan-baseline.neon:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /composer.lock
2 | /pnpm-lock.yaml
3 | /.pnpm-store
4 |
--------------------------------------------------------------------------------
/www/admin/styles/inquisition-details.css:
--------------------------------------------------------------------------------
1 | #question_view li.correct {
2 | color: #558b2f;
3 | }
4 |
--------------------------------------------------------------------------------
/sql/fixtures/InquisitionQuestionImageSet.sql:
--------------------------------------------------------------------------------
1 | insert into ImageSet (
2 | shortname,
3 | use_cdn,
4 | obfuscate_filename
5 | ) values (
6 | 'inquisition-question',
7 | true,
8 | false
9 | );
10 |
--------------------------------------------------------------------------------
/sql/tables/Inquisition.sql:
--------------------------------------------------------------------------------
1 | create table Inquisition (
2 | id serial,
3 | title varchar(255),
4 | createdate timestamp not null,
5 | enabled boolean not null default true,
6 | primary key (id)
7 | );
8 |
9 |
--------------------------------------------------------------------------------
/sql/fixtures/InquisitionQuestionOptionImageSet.sql:
--------------------------------------------------------------------------------
1 | insert into ImageSet (
2 | shortname,
3 | use_cdn,
4 | obfuscate_filename
5 | ) values (
6 | 'inquisition-question-option',
7 | true,
8 | false
9 | );
10 |
--------------------------------------------------------------------------------
/phpstan.dist.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - phpstan-baseline.neon
3 |
4 | parameters:
5 | phpVersion: 80200
6 | level: 1
7 | paths:
8 | - Inquisition
9 | editorUrl: '%%file%%:%%line%%'
10 | editorUrlTitle: '%%file%%:%%line%%'
11 |
--------------------------------------------------------------------------------
/sql/tables/InquisitionQuestionHint.sql:
--------------------------------------------------------------------------------
1 | create table InquisitionQuestionHint (
2 | id serial,
3 | question integer not null references InquisitionQuestion(id) on delete cascade,
4 | bodytext text,
5 | displayorder integer not null default 0,
6 | primary key(id)
7 | );
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @see https://prettier.io/docs/en/configuration.html
3 | * @type {import("prettier").Config}
4 | */
5 | const config = {
6 | singleQuote: true,
7 | tabWidth: 2,
8 | trailingComma: 'none'
9 | };
10 |
11 | export default config;
12 |
--------------------------------------------------------------------------------
/sql/tables/InquisitionQuestionGroup.sql:
--------------------------------------------------------------------------------
1 | create table InquisitionQuestionGroup (
2 | id serial,
3 | title varchar(255),
4 | bodytext text,
5 | primary key (id)
6 | );
7 |
8 | alter table InquisitionQuestion add question_group integer references InquisitionQuestionGroup(id) on delete set null;
9 |
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | vendor/
5 | node_modules/
6 |
7 | # misc
8 | .DS_Store
9 | .env
10 | .idea/
11 | .php-cs-fixer.cache
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 | *.swp
16 |
17 | # dependency lock file
18 | composer.lock
19 | yarn.lock
20 |
21 | # overrides for local tooling
22 | /phpstan.neon
23 |
--------------------------------------------------------------------------------
/sql/tables/InquisitionQuestion.sql:
--------------------------------------------------------------------------------
1 | create table InquisitionQuestion (
2 | id serial,
3 | bodytext text,
4 | question_type integer not null,
5 | displayorder integer not null default 0,
6 | required boolean not null default true,
7 | enabled boolean not null default true,
8 | primary key (id)
9 | );
10 |
11 | alter table InquisitionQuestion add correct_option integer references InquisitionQuestionOption(id) on delete set null;
12 |
13 |
--------------------------------------------------------------------------------
/sql/tables/InquisitionQuestionOption.sql:
--------------------------------------------------------------------------------
1 | create table InquisitionQuestionOption (
2 | id serial,
3 | question integer not null references InquisitionQuestion(id) on delete cascade,
4 | title varchar(255),
5 | displayorder integer not null default 0,
6 | include_text boolean not null default false,
7 | primary key(id)
8 | );
9 |
10 | create index InquisitionQuestionOption_question_index on
11 | InquisitionQuestionOption(question);
12 |
--------------------------------------------------------------------------------
/sql/tables/InquisitionResponse.sql:
--------------------------------------------------------------------------------
1 | create table InquisitionResponse (
2 | id serial,
3 | inquisition integer not null references Inquisition(id) on delete cascade,
4 | createdate timestamp not null,
5 | complete_date timestamp,
6 | grade decimal(5, 2),
7 | primary key (id)
8 | );
9 |
10 | create index InquisitionResponse_inquisition_index on InquisitionResponse(inquisition);
11 | create index InquisitionResponse_account_index on InquisitionResponse(account);
12 |
--------------------------------------------------------------------------------
/www/admin/styles/inquisition-question-edit.css:
--------------------------------------------------------------------------------
1 | #question_option_table_view .title .swat-textarea {
2 | width: 97%;
3 | padding: 1px 2px;
4 | resize: vertical;
5 | }
6 |
7 | #question_option_table_view .title {
8 | width: 100%;
9 | }
10 |
11 | #question_option_table_view thead th,
12 | #question_option_table_view td.correct-option,
13 | #question_option_table_view td.checkbox-column {
14 | text-align: center;
15 | }
16 |
17 | #bodytext {
18 | width: 97%;
19 | }
20 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionQuestionImage.php:
--------------------------------------------------------------------------------
1 | image_set_shortname = 'inquisition-question';
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/sql/views/InquisitionResponseTotalQuestionsView.sql:
--------------------------------------------------------------------------------
1 | create or replace view InquisitionResponseTotalQuestionsView as
2 | select InquisitionResponse.id as response, count(1) as total_questions
3 | from InquisitionResponse
4 | inner join Inquisition
5 | on InquisitionResponse.inquisition = Inquisition.id
6 | left outer join InquisitionInquisitionQuestionBinding
7 | on InquisitionInquisitionQuestionBinding.inquisition = Inquisition.id
8 | group by InquisitionResponse.id;
9 |
--------------------------------------------------------------------------------
/sql/views/VisibleInquisitionQuestionView.sql:
--------------------------------------------------------------------------------
1 | create or replace view VisibleInquisitionQuestionView as
2 | select id as question from InquisitionQuestion
3 | where InquisitionQuestion.enabled = true and (
4 | -- Question is always visible if it InquisitionQuestion::TYPE_TEXT (4),
5 | -- or if it has related options
6 | InquisitionQuestion.question_type = 4 or InquisitionQuestion.id in (
7 | select InquisitionQuestionOption.question from InquisitionQuestionOption
8 | )
9 | );
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.php]
16 | indent_size = 4
17 |
18 | [*.{diff,md}]
19 | trim_trailing_whitespace = false
20 |
21 | [Jenkinsfile]
22 | indent_size = 4
23 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionQuestionOptionImage.php:
--------------------------------------------------------------------------------
1 | image_set_shortname = 'inquisition-question-option';
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@silverorange/inquisition",
3 | "private": true,
4 | "engines": {
5 | "node": "^22.14"
6 | },
7 | "type": "module",
8 | "scripts": {
9 | "prettier": "prettier --check .",
10 | "prettier:write": "prettier --write ."
11 | },
12 | "devDependencies": {
13 | "prettier": "^3.5.3"
14 | },
15 | "packageManager": "pnpm@9.12.2+sha512.22721b3a11f81661ae1ec68ce1a7b879425a1ca5b991c975b074ac220b187ce56c708fe5db69f4c962c989452eee76c82877f4ee80f474cebd61ee13461b6228"
16 | }
17 |
--------------------------------------------------------------------------------
/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '9.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | importers:
8 |
9 | .:
10 | devDependencies:
11 | prettier:
12 | specifier: ^3.5.3
13 | version: 3.6.2
14 |
15 | packages:
16 |
17 | prettier@3.6.2:
18 | resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
19 | engines: {node: '>=14'}
20 | hasBin: true
21 |
22 | snapshots:
23 |
24 | prettier@3.6.2: {}
25 |
--------------------------------------------------------------------------------
/sql/tables/InquisitionQuestionImageBinding.sql:
--------------------------------------------------------------------------------
1 | create table InquisitionQuestionImageBinding (
2 | question integer not null references InquisitionQuestion(id) on delete cascade,
3 | image integer not null references Image(id) on delete cascade,
4 | displayorder integer not null default 0,
5 | primary key (question, image)
6 | );
7 |
8 | CREATE INDEX InquisitionQuestionImageBinding_question_index ON InquisitionQuestionImageBinding(question);
9 | CREATE INDEX InquisitionQuestionImageBinding_image_index ON InquisitionQuestionImageBinding(image);
10 |
--------------------------------------------------------------------------------
/sql/tables/InquisitionQuestionDependency.sql:
--------------------------------------------------------------------------------
1 | create table InquisitionQuestionDependency
2 | (
3 | question_binding integer not null references InquisitionInquisitionQuestionBinding(id) on delete cascade,
4 | dependent_question_binding integer not null references InquisitionInquisitionQuestionBinding(id) on delete cascade,
5 |
6 | option integer not null references InquisitionQuestionOption(id) on delete cascade
7 | );
8 |
9 | create index InquisitionQuestionDependency_dependent_question_binding_index on
10 | InquisitionQuestionDependency(dependent_question_binding);
11 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionQuestionImageWrapper.php:
--------------------------------------------------------------------------------
1 | row_wrapper_class =
18 | SwatDBClassMap::get(InquisitionQuestionImage::class);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionQuestionOptionWrapper.php:
--------------------------------------------------------------------------------
1 | row_wrapper_class =
16 | SwatDBClassMap::get(InquisitionQuestionOption::class);
17 |
18 | $this->index_field = 'id';
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionQuestionWrapper.php:
--------------------------------------------------------------------------------
1 | row_wrapper_class = SwatDBClassMap::get(InquisitionQuestion::class);
18 | $this->index_field = 'id';
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionResponseWrapper.php:
--------------------------------------------------------------------------------
1 | row_wrapper_class = SwatDBClassMap::get(InquisitionResponse::class);
18 | $this->index_field = 'id';
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/sql/tables/InquisitionQuestionOptionImageBinding.sql:
--------------------------------------------------------------------------------
1 | create table InquisitionQuestionOptionImageBinding (
2 | question_option integer not null references InquisitionQuestionOption(id) on delete cascade,
3 | image integer not null references Image(id) on delete cascade,
4 | displayorder integer not null default 0,
5 | primary key (question_option, image)
6 | );
7 |
8 | CREATE INDEX InquisitionQuestionOptionImageBinding_question_option_index ON InquisitionQuestionOptionImageBinding(question_option);
9 | CREATE INDEX InquisitionQuestionOptionImageBinding_image_index ON InquisitionQuestionOptionImageBinding(image);
10 |
--------------------------------------------------------------------------------
/sql/tables/InquisitionInquisitionQuestionBinding.sql:
--------------------------------------------------------------------------------
1 | create table InquisitionInquisitionQuestionBinding (
2 | id serial,
3 | inquisition integer not null references Inquisition(id) on delete cascade,
4 | question integer not null references InquisitionQuestion(id) on delete cascade,
5 | displayorder integer not null default 0,
6 |
7 | primary key (id)
8 | );
9 |
10 | create index InquisitionInquisitionQuestionBinding_inquisition_index on
11 | InquisitionInquisitionQuestionBinding(inquisition);
12 |
13 | create index InquisitionInquisitionQuestionBinding_question_index on
14 | InquisitionInquisitionQuestionBinding(question);
15 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionInquisitionWrapper.php:
--------------------------------------------------------------------------------
1 | row_wrapper_class =
18 | SwatDBClassMap::get(InquisitionInquisition::class);
19 |
20 | $this->index_field = 'id';
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionQuestionOptionImageWrapper.php:
--------------------------------------------------------------------------------
1 | row_wrapper_class =
18 | SwatDBClassMap::get(InquisitionQuestionOptionImage::class);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionQuestionHintWrapper.php:
--------------------------------------------------------------------------------
1 | row_wrapper_class =
18 | SwatDBClassMap::get(InquisitionQuestionHint::class);
19 |
20 | $this->index_field = 'id';
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionResponseValueWrapper.php:
--------------------------------------------------------------------------------
1 | row_wrapper_class =
18 | SwatDBClassMap::get(InquisitionResponseValue::class);
19 |
20 | $this->index_field = 'id';
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionQuestionGroup.php:
--------------------------------------------------------------------------------
1 | table = 'InquisitionQuestionGroup';
29 | $this->id_field = 'integer:id';
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionResponseUsedHintBindingWrapper.php:
--------------------------------------------------------------------------------
1 | index_field = 'question_hint';
18 | $this->row_wrapper_class =
19 | SwatDBClassMap::get(InquisitionResponseUsedHintBinding::class);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionInquisitionQuestionBindingWrapper.php:
--------------------------------------------------------------------------------
1 | row_wrapper_class =
18 | SwatDBClassMap::get(InquisitionInquisitionQuestionBinding::class);
19 |
20 | $this->index_field = 'id';
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Inquisition/exceptions/InquisitionImportException.php:
--------------------------------------------------------------------------------
1 | file_parser = $file_parser;
21 | }
22 |
23 | public function getFileParser()
24 | {
25 | return $this->file_parser;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Inquisition/InquisitionQuestionOptionCellRenderer.php:
--------------------------------------------------------------------------------
1 | visible) {
17 | return;
18 | }
19 |
20 | $span = new SwatHtmlTag('span');
21 | $span->class = 'inquisition-question-option';
22 |
23 | if ($this->correct) {
24 | $span->class .= ' inquisition-question-option-correct';
25 | }
26 |
27 | $span->open();
28 | parent::render();
29 | $span->close();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/correct-option.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Correct Option
7 |
8 |
9 | Options
10 |
11 | true
12 |
13 |
14 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Option/edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Option
7 |
8 |
9 | Text
10 |
11 | 255
12 | true
13 |
14 |
15 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/sql/tables/InquisitionResponseUsedHintBinding.sql:
--------------------------------------------------------------------------------
1 | create table InquisitionResponseUsedHintBinding (
2 | response integer not null references InquisitionResponse(id) on delete cascade,
3 | question_hint integer not null references InquisitionQuestionHint(id) on delete cascade,
4 | question_binding integer not null references InquisitionInquisitionQuestionBinding(id) on delete cascade,
5 | createdate timestamp not null default LOCALTIMESTAMP,
6 | primary key (response, question_hint, question_binding)
7 | );
8 |
9 | CREATE INDEX InquisitionResponseUsedHintBinding_response_index ON InquisitionResponseUsedHintBinding(response);
10 | CREATE INDEX InquisitionResponseUsedHintBinding_question_hint_index ON InquisitionResponseUsedHintBinding(question_hint);
11 | CREATE INDEX InquisitionResponseUsedHintBinding_response_question_binding_index
12 | ON InquisitionResponseUsedHintBinding(response, question_binding);
13 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionQuestionHint.php:
--------------------------------------------------------------------------------
1 | table = 'InquisitionQuestionHint';
31 | $this->id_field = 'integer:id';
32 |
33 | $this->registerInternalProperty(
34 | 'question',
35 | SwatDBClassMap::get(InquisitionQuestion::class)
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/sql/tables/InquisitionResponseValue.sql:
--------------------------------------------------------------------------------
1 | create table InquisitionResponseValue (
2 | id serial,
3 | response integer not null references InquisitionResponse(id) on delete cascade,
4 | question_option integer null references InquisitionQuestionOption(id) on delete cascade,
5 | question_binding integer not null references InquisitionInquisitionQuestionBinding(id) on delete cascade,
6 | numeric_value integer,
7 | text_value text,
8 | primary key (id)
9 | );
10 |
11 | create index InquisitionResponseValue_response_index on
12 | InquisitionResponseValue(response);
13 |
14 | create index InquisitionResponseValue_question_option_index on
15 | InquisitionResponseValue(question_option);
16 |
17 | create index InquisitionResponseValue_question_binding_index on
18 | InquisitionResponseValue(question_binding);
19 |
20 | create index InquisitionResponseValue_response_question_binding_index on
21 | InquisitionResponseValue(response, question_binding);
22 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/import.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Import Questions
7 |
8 |
9 | Questions File
10 |
11 | true
12 | text/csv
13 | text/plain
14 |
15 |
16 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Inquisition
2 |
3 | A package for creating, managing, and running online quizzes.
4 |
5 | Inquisition is responsible for the following basic object types and related
6 | tables:
7 |
8 | - Inquisition (quizzes)
9 | - InquisitionQuestion
10 | - InquisitionInquisitionQuestionBinding
11 | - InquisitionQuestionOption
12 | - InquisitionResponse
13 | - InquisitionResponseValue
14 |
15 | Additional objects are provided for extended features:
16 |
17 | - InquisitionQuestionImage
18 | - InquisitionQuestionOptionImage
19 | - InquisitionQuestionGroup
20 | - InquisitionQuestionHint
21 | - InquisitionResponseUsedHintBinding
22 |
23 | It provides pages for displaying these objects and admin tools for managing
24 | them.
25 |
26 | There is also a CSV importer and exporter for question management.
27 |
28 | ## Installation
29 |
30 | Make sure the silverorange composer repository is added to the `composer.json`
31 | for the project and then run:
32 |
33 | ```sh
34 | composer require silverorange/inquisition
35 | ```
36 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Inquisition/image-upload.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Image
9 |
10 | true
11 | image/jpeg
12 | image/png
13 | image/tiff
14 |
15 |
16 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/sql/views/InquisitionResponseGradeView.sql:
--------------------------------------------------------------------------------
1 | create or replace view InquisitionResponseGradeView as
2 | select InquisitionResponse.inquisition,
3 | InquisitionResponse.account,
4 | sum(
5 | case when InquisitionQuestion.correct_option =
6 | InquisitionResponseValue.question_option then 1
7 | else 0
8 | end
9 | )::float /
10 | count(InquisitionResponseValue.id)::float as grade
11 | from InquisitionResponse
12 | inner join InquisitionResponseValue on
13 | InquisitionResponseValue.response = InquisitionResponse.id
14 | inner join InquisitionInquisitionQuestionBinding on
15 | InquisitionInquisitionQuestionBinding.id =
16 | InquisitionResponseValue.question_binding
17 | inner join InquisitionQuestion on
18 | InquisitionQuestion.id =
19 | InquisitionInquisitionQuestionBinding.question
20 | where InquisitionResponse.complete_date is not null
21 | and InquisitionResponse.reset_date is null
22 | and InquisitionQuestion.correct_option is not null
23 | group by InquisitionResponse.inquisition, InquisitionResponse.account;
24 |
25 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | Add a description of new changes, the reason for new changes, and how the new
4 | changes work here.
5 |
6 | # Testing Instructions (optional)
7 |
8 | Add step-by-step instructions for testing the PR, if necessary.
9 |
10 | 1. Check out this PR
11 | 2. …
12 |
13 | # Developer Checklist
14 |
15 | Before requesting review for this PR, make sure the following tasks are
16 | complete:
17 |
18 | - [ ] I added a link to the relevant Shortcut story, if applicable
19 | - [ ] I added testing instructions, if any
20 | - [ ] I made sure existing CI checks pass
21 | - [ ] I checked that all requirements of the ticket are fulfilled
22 |
23 | # Reviewer Checklist
24 |
25 | Before merging this PR, make sure the following tasks are complete:
26 |
27 | - [ ] I made sure there are no active labels that block merge
28 | - [ ] I followed the testing instructions
29 | - [ ] I made sure the CI checks pass
30 | - [ ] I reviewed the file changes on GitHub
31 | - [ ] I checked that all requirements of the ticket (if any) are fulfilled
32 |
--------------------------------------------------------------------------------
/Inquisition/views/InquisitionQuestionView.php:
--------------------------------------------------------------------------------
1 | question_binding = $question_binding;
26 | $this->db = $db;
27 | }
28 |
29 | abstract public function getWidget(?InquisitionResponseValue $value = null);
30 |
31 | public function getResponseValue()
32 | {
33 | $value = SwatDBClassMap::new(InquisitionResponseValue::class);
34 | $value->question_binding = $this->question_binding->id;
35 |
36 | return $value;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/hint-edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Hint
7 |
8 |
9 | Bodytext
10 |
11 | true
12 | 5
13 |
14 |
15 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | pipeline {
2 | agent any
3 | stages {
4 | stage('Install Composer Dependencies') {
5 | steps {
6 | sh 'rm -rf composer.lock vendor/'
7 | sh 'composer install'
8 | }
9 | }
10 |
11 | stage('Install NPM Dependencies') {
12 | environment {
13 | PNPM_CACHE_FOLDER = "${env.WORKSPACE}/pnpm-cache/${env.BUILD_NUMBER}"
14 | }
15 | steps {
16 | sh 'n -d exec engine corepack enable pnpm'
17 | sh 'n -d exec engine pnpm install'
18 | }
19 | }
20 |
21 |
22 | stage('Check PHP Coding Style') {
23 | steps {
24 | sh 'composer run phpcs:ci'
25 | }
26 | }
27 |
28 | stage('Check PHP Static Analysis') {
29 | steps {
30 | sh 'composer run phpstan:ci'
31 | }
32 | }
33 |
34 | stage('Check Formating') {
35 | steps {
36 | sh 'n -d exec engine pnpm prettier'
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Inquisition/image-delete.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
17 |
18 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Inquisition/views/InquisitionTextQuestionView.php:
--------------------------------------------------------------------------------
1 | question_binding;
16 | $question = $this->question_binding->question;
17 |
18 | if ($this->textarea === null) {
19 | $id = sprintf('question%s_%s', $binding->id, $question->id);
20 |
21 | $this->textarea = new SwatTextarea($id);
22 | $this->textarea->required = $question->required;
23 | }
24 |
25 | if ($value !== null) {
26 | $this->textarea->value = intval(
27 | $value->getInternalValue('question_option')
28 | );
29 | }
30 |
31 | return $this->textarea;
32 | }
33 |
34 | public function getResponseValue()
35 | {
36 | $value = parent::getResponseValue();
37 | $value->text_value = $this->textarea->value;
38 |
39 | return $value;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionResponseUsedHintBinding.php:
--------------------------------------------------------------------------------
1 | table = 'InquisitionResponseUsedHintBinding';
23 |
24 | $this->registerDateProperty('createdate');
25 |
26 | $this->registerInternalProperty(
27 | 'response',
28 | SwatDBClassMap::get(InquisitionResponse::class)
29 | );
30 |
31 | $this->registerInternalProperty(
32 | 'question_hint',
33 | SwatDBClassMap::get(InquisitionQuestionHint::class)
34 | );
35 |
36 | $this->registerInternalProperty(
37 | 'question_binding',
38 | SwatDBClassMap::get(InquisitionInquisitionQuestionBinding::class)
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Inquisition/index.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Inquisitions
7 |
8 |
9 | New Inquisition
10 | Inquisition/Edit
11 | add
12 |
13 |
14 |
15 |
16 | Title
17 |
18 | title
19 | Inquisition/Details?id=%s
20 | id
21 |
22 |
23 |
24 |
25 | question_count
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Inquisition/edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Inquisition
7 |
8 |
9 | Title
10 |
11 | 255
12 |
13 |
14 |
15 | Show on Site?
16 |
21 | false
22 |
23 | true
24 |
25 |
26 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/dependencies/inquisition.yaml:
--------------------------------------------------------------------------------
1 | ##
2 | ## Static Web-resource dependencies for the Inquisition package
3 | ##
4 | ## Copyright (c) 2010 silverorange
5 | ##
6 | ## This library is free software; you can redistribute it and/or modify
7 | ## it under the terms of the GNU Lesser General Public License as
8 | ## published by the Free Software Foundation; either version 2.1 of the
9 | ## License, or (at your option) any later version.
10 | ##
11 | ## This library is distributed in the hope that it will be useful,
12 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | ## Lesser General Public License for more details.
15 | ##
16 | ## You should have received a copy of the GNU Lesser General Public
17 | ## License along with this library; if not, write to the Free Software
18 | ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 | ##
20 | Inquisition:
21 | Depends:
22 | # Package Dependencies
23 | - Swat
24 |
25 | Provides:
26 | # JavaScript resources
27 | packages/inquisition/javascript/inquisition-radio-list-with-text-question-view.js:
28 |
29 | # Style-sheet resources
30 | packages/inquisition/admin/styles/inquisition-details.css:
31 | packages/inquisition/admin/styles/inquisition-question-edit.css:
32 | # vim: set expandtab tabstop=2 shiftwidth=2 softtabstop=2:
33 |
--------------------------------------------------------------------------------
/Inquisition/views/InquisitionFlydownQuestionView.php:
--------------------------------------------------------------------------------
1 | question_binding;
16 | $question = $this->question_binding->question;
17 |
18 | if ($this->flydown === null) {
19 | $id = sprintf('question%s_%s', $binding->id, $question->id);
20 |
21 | $this->flydown = new SwatFlydown($id);
22 | $this->flydown->required = $question->required;
23 |
24 | foreach ($question->options as $option) {
25 | $this->flydown->addOption($option->id, $option->title);
26 | }
27 | }
28 |
29 | if ($value !== null) {
30 | $this->flydown->value = intval(
31 | $value->getInternalValue('question_option')
32 | );
33 | }
34 |
35 | return $this->flydown;
36 | }
37 |
38 | public function getResponseValue()
39 | {
40 | $value = parent::getResponseValue();
41 | $value->question_option = $this->flydown->value;
42 |
43 | return $value;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Question
7 |
8 |
9 | Question
10 |
11 | true
12 | 5
13 |
14 |
15 |
16 | Show on Site?
17 | Hide a question from the site when there are responses to the question that should remain available for reporting.
18 | false
19 |
20 | true
21 |
22 |
23 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Inquisition/admin/InquisitionCorrectOptionRadioButton.php:
--------------------------------------------------------------------------------
1 | getForm()->getHiddenField($this->id . '_submitted') === null) {
16 | return;
17 | }
18 |
19 | $data = &$this->getForm()->getFormData();
20 | $this->value = (array_key_exists('correct_option', $data)
21 | && $data['correct_option'] == $this->id);
22 | }
23 |
24 | public function display()
25 | {
26 | SwatInputControl::display();
27 |
28 | $this->getForm()->addHiddenField($this->id . '_submitted', 1);
29 |
30 | $input_tag = new SwatHtmlTag('input');
31 | $input_tag->type = 'radio';
32 | $input_tag->class = $this->getCSSClassString();
33 | $input_tag->name = 'correct_option';
34 | $input_tag->id = $this->id;
35 | $input_tag->value = $this->id;
36 | $input_tag->accesskey = $this->access_key;
37 |
38 | if ($this->value) {
39 | $input_tag->checked = 'checked';
40 | }
41 |
42 | $input_tag->display();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionResponseValue.php:
--------------------------------------------------------------------------------
1 | table = 'InquisitionResponseValue';
33 | $this->id_field = 'integer:id';
34 |
35 | $this->registerInternalProperty(
36 | 'response',
37 | SwatDBClassMap::get(InquisitionResponse::class)
38 | );
39 |
40 | $this->registerInternalProperty(
41 | 'question_option',
42 | SwatDBClassMap::get(InquisitionQuestionOption::class)
43 | );
44 |
45 | $this->registerInternalProperty(
46 | 'question_binding',
47 | SwatDBClassMap::get(InquisitionInquisitionQuestionBinding::class)
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Inquisition/views/InquisitionRadioListQuestionView.php:
--------------------------------------------------------------------------------
1 | question_binding;
19 | $question = $this->question_binding->question;
20 |
21 | if ($this->radio_list === null) {
22 | $id = sprintf('question%s_%s', $binding->id, $question->id);
23 |
24 | $this->radio_list = new SwatRadioList($id);
25 | $this->radio_list->required = $question->required;
26 |
27 | foreach ($question->options as $option) {
28 | $this->radio_list->addOption($option->id, $option->title);
29 | }
30 | }
31 |
32 | if ($value !== null) {
33 | $this->radio_list->value = intval(
34 | $value->getInternalValue('question_option')
35 | );
36 | }
37 |
38 | return $this->radio_list;
39 | }
40 |
41 | public function getResponseValue()
42 | {
43 | $value = parent::getResponseValue();
44 | $value->question_option = $this->radio_list->value;
45 |
46 | return $value;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/www/javascript/inquisition-checkbox-entry-list.js:
--------------------------------------------------------------------------------
1 | function InquisitionCheckboxEntryList(id) {
2 | this.id = id;
3 | this.entries = [];
4 | this.checkboxs_by_entry = {};
5 | YAHOO.util.Event.onDOMReady(this.init, this, true);
6 | }
7 |
8 | InquisitionCheckboxEntryList.prototype.init = function () {
9 | this.list = document.getElementById(this.id);
10 | this.checkboxes = YAHOO.util.Dom.getElementsBy(
11 | function (el) {
12 | return el.type == 'checkbox';
13 | },
14 | 'input',
15 | this.list
16 | );
17 |
18 | var id_parts, entry_id, entry;
19 | for (var i = 0; i < this.checkboxes.length; i++) {
20 | id_parts = this.checkboxes[i].id.split('_');
21 | entry_id = id_parts[0] + '_' + id_parts[1] + '_entry_' + id_parts[2];
22 | entry = document.getElementById(entry_id);
23 | if (entry) {
24 | this.entries.push(entry);
25 | this.checkboxs_by_entry[entry.id] = this.checkboxes[i];
26 | }
27 | }
28 |
29 | YAHOO.util.Event.on(this.checkboxes, 'click', this.updateEntries, this, true);
30 |
31 | this.updateEntries();
32 | };
33 |
34 | InquisitionCheckboxEntryList.prototype.updateEntries = function () {
35 | var checkbox, entry;
36 | for (var i = 0; i < this.entries.length; i++) {
37 | entry = this.entries[i];
38 | checkbox = this.checkboxs_by_entry[entry.id];
39 | if (checkbox.checked) {
40 | YAHOO.util.Dom.removeClass(entry, 'swat-insensitive');
41 | entry.disabled = false;
42 | } else {
43 | YAHOO.util.Dom.addClass(entry, 'swat-insensitive');
44 | entry.disabled = true;
45 | }
46 | }
47 | };
48 |
--------------------------------------------------------------------------------
/www/javascript/inquisition-radio-entry-list.js:
--------------------------------------------------------------------------------
1 | function InquisitionRadioEntryList(id) {
2 | this.id = id;
3 | this.entries = [];
4 | this.radios_by_entry = {};
5 | YAHOO.util.Event.onDOMReady(this.init, this, true);
6 | }
7 |
8 | InquisitionRadioEntryList.prototype.init = function () {
9 | this.list = document.getElementById(this.id);
10 | this.radio_buttons = YAHOO.util.Dom.getElementsBy(
11 | function (el) {
12 | return el.type == 'radio';
13 | },
14 | 'input',
15 | this.list
16 | );
17 |
18 | var id_parts, entry_id, entry;
19 | for (var i = 0; i < this.radio_buttons.length; i++) {
20 | id_parts = this.radio_buttons[i].id.split('_');
21 | entry_id = id_parts[0] + '_' + id_parts[1] + '_entry_' + id_parts[2];
22 | entry = document.getElementById(entry_id);
23 | if (entry) {
24 | this.entries.push(entry);
25 | this.radios_by_entry[entry.id] = this.radio_buttons[i];
26 | }
27 | }
28 |
29 | YAHOO.util.Event.on(
30 | this.radio_buttons,
31 | 'click',
32 | this.updateEntries,
33 | this,
34 | true
35 | );
36 |
37 | this.updateEntries();
38 | };
39 |
40 | InquisitionRadioEntryList.prototype.updateEntries = function () {
41 | var radio, entry;
42 | for (var i = 0; i < this.entries.length; i++) {
43 | entry = this.entries[i];
44 | radio = this.radios_by_entry[entry.id];
45 | if (radio.checked) {
46 | YAHOO.util.Dom.removeClass(entry, 'swat-insensitive');
47 | entry.disabled = false;
48 | } else {
49 | YAHOO.util.Dom.addClass(entry, 'swat-insensitive');
50 | entry.disabled = true;
51 | }
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/Inquisition/views/InquisitionCheckboxListQuestionView.php:
--------------------------------------------------------------------------------
1 | question_binding;
16 | $question = $this->question_binding->question;
17 |
18 | if ($this->checkbox_list === null) {
19 | $id = sprintf('question%s_%s', $binding->id, $question->id);
20 |
21 | $this->checkbox_list = new InquisitionCheckboxEntryList($id);
22 | $this->checkbox_list->required = $question->required;
23 | $this->checkbox_list->show_check_all = false;
24 |
25 | foreach ($question->options as $option) {
26 | $this->checkbox_list->addOption($option->id, $option->title);
27 | }
28 | }
29 |
30 | if ($value !== null) {
31 | $this->checkbox_list->value = intval(
32 | $value->getInternalValue('question_option')
33 | );
34 | }
35 |
36 | return $this->checkbox_list;
37 | }
38 |
39 | public function getResponseValue()
40 | {
41 | $base_value = parent::getResponseValue();
42 | $value = [];
43 |
44 | foreach ($this->checkbox_list->values as $list_value) {
45 | $response_value = clone $base_value;
46 | $response_value->question_option = $list_value;
47 | $value[] = $response_value;
48 | }
49 |
50 | return $value;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Inquisition/views/InquisitionRadioEntryQuestionView.php:
--------------------------------------------------------------------------------
1 | question_binding;
16 | $question = $this->question_binding->question;
17 |
18 | if (!$this->radio_table instanceof InquisitionRadioEntryList) {
19 | $id = sprintf('question%s_%s', $binding->id, $question->id);
20 |
21 | $this->radio_table = new InquisitionRadioEntryList($id);
22 | $this->radio_table->required = $question->required;
23 |
24 | foreach ($question->options as $option) {
25 | $this->radio_table->addOption($option->id, $option->title);
26 | if ($option->include_text) {
27 | $this->radio_table->setEntryOption($option->id);
28 | }
29 | }
30 | }
31 |
32 | if ($value instanceof InquisitionResponseValue) {
33 | $this->radio_table->value = intval(
34 | $value->getInternalValue('question_option')
35 | );
36 | }
37 |
38 | return $this->radio_table;
39 | }
40 |
41 | public function getResponseValue()
42 | {
43 | $value = parent::getResponseValue();
44 | $value->question_option = $this->radio_table->value;
45 | $value->text_value = $this->radio_table->getEntryValue(
46 | $this->radio_table->value
47 | );
48 |
49 | return $value;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "silverorange/inquisition",
3 | "description": "A quiz and/or survey framework.",
4 | "type": "library",
5 | "keywords": [
6 | "questionnaire",
7 | "survey",
8 | "quiz"
9 | ],
10 | "homepage": "https://github.com/silverorange/inquisition",
11 | "license": "LGPL-2.1",
12 | "authors": [
13 | {
14 | "name": "Charles Waddell",
15 | "email": "charles@silverorange.com"
16 | },
17 | {
18 | "name": "Isaac Grant",
19 | "email": "isaac@silverorange.com"
20 | },
21 | {
22 | "name": "Michael Gauthier",
23 | "email": "mike@silverorange.com"
24 | },
25 | {
26 | "name": "Nick Burka",
27 | "email": "nick@silverorange.com"
28 | }
29 | ],
30 | "repositories": [
31 | {
32 | "type": "composer",
33 | "url": "https://composer.silverorange.com",
34 | "only": [
35 | "silverorange/*"
36 | ]
37 | }
38 | ],
39 | "require": {
40 | "php": ">=8.2",
41 | "ext-mbstring": "*",
42 | "silverorange/admin": "^7.0.3",
43 | "silverorange/mdb2": "^3.1.2",
44 | "silverorange/site": "^15.3.2",
45 | "silverorange/swat": "^7.9.2"
46 | },
47 | "require-dev": {
48 | "friendsofphp/php-cs-fixer": "3.64.0",
49 | "phpstan/phpstan": "^1.12"
50 | },
51 | "scripts": {
52 | "phpcs": "./vendor/bin/php-cs-fixer check -v",
53 | "phpcs:ci": "./vendor/bin/php-cs-fixer check --config=.php-cs-fixer.php --no-interaction --show-progress=none --diff --using-cache=no -vvv",
54 | "phpcs:write": "./vendor/bin/php-cs-fixer fix -v",
55 | "phpstan": "./vendor/bin/phpstan analyze",
56 | "phpstan:ci": "./vendor/bin/phpstan analyze -vvv --no-progress --memory-limit 2G",
57 | "phpstan:baseline": "./vendor/bin/phpstan analyze --generate-baseline"
58 | },
59 | "autoload": {
60 | "classmap": [
61 | "Inquisition/"
62 | ]
63 | },
64 | "config": {
65 | "sort-packages": true
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/.github/workflows/pull-requests.yml:
--------------------------------------------------------------------------------
1 | name: Pull Requests
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | runner:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Setup Tailscale
14 | uses: tailscale/github-action@v3
15 | with:
16 | oauth-client-id: ${{ secrets.SILVERORANGE_TAILSCALE_OAUTH_CLIENT_ID }}
17 | oauth-secret: ${{ secrets.SILVERORANGE_TAILSCALE_OAUTH_SECRET }}
18 | tags: tag:silverorange-gh-actions
19 | version: latest
20 |
21 | - name: Check out repository code
22 | uses: actions/checkout@v4
23 |
24 | # Can maybe be replaced with pnpm/action-setup@v4 in the future
25 | - name: Setup pnpm/corepack
26 | uses: actions/setup-node@v4
27 | with:
28 | node-version-file: 'package.json'
29 | - run: npm i -g --force corepack && corepack enable
30 |
31 | - name: Setup Node
32 | uses: actions/setup-node@v4
33 | with:
34 | node-version-file: 'package.json'
35 | cache: 'pnpm'
36 |
37 | - name: Install dependencies
38 | run: pnpm install --frozen-lockfile
39 |
40 | - name: Setup PHP with tools
41 | uses: silverorange/actions-setup-php@v2
42 | with:
43 | php-version: '8.2'
44 | extensions: gd, imagick
45 |
46 | - name: Get composer cache directory
47 | id: composer-cache
48 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
49 |
50 | - name: Cache dependencies
51 | uses: actions/cache@v4
52 | with:
53 | path: ${{ steps.composer-cache.outputs.dir }}
54 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
55 | restore-keys: ${{ runner.os }}-composer-
56 |
57 | - name: Install PHP dependencies
58 | run: 'composer install'
59 |
60 | - name: Run tests
61 | timeout-minutes: 5
62 | run: |
63 | pnpm prettier
64 | composer run phpcs:ci
65 | composer run phpstan:ci
66 |
--------------------------------------------------------------------------------
/Inquisition/views/InquisitionCheckboxEntryQuestionView.php:
--------------------------------------------------------------------------------
1 | question_binding;
16 | $question = $this->question_binding->question;
17 |
18 | if (!$this->checkbox_list instanceof InquisitionCheckboxEntryList) {
19 | $id = sprintf('question%s_%s', $binding->id, $question->id);
20 |
21 | $this->checkbox_list = new InquisitionCheckboxEntryList($id);
22 | $this->checkbox_list->required = $question->required;
23 | $this->checkbox_list->show_check_all = false;
24 |
25 | foreach ($question->options as $option) {
26 | $this->checkbox_list->addOption($option->id, $option->title);
27 | if ($option->include_text) {
28 | $this->checkbox_list->setEntryOption($option->id);
29 | }
30 | }
31 | }
32 |
33 | if ($value instanceof InquisitionResponseValue) {
34 | $this->checkbox_list->value = intval(
35 | $value->getInternalValue('question_option')
36 | );
37 | }
38 |
39 | return $this->checkbox_list;
40 | }
41 |
42 | public function getResponseValue()
43 | {
44 | $base_value = parent::getResponseValue();
45 | $value = [];
46 |
47 | foreach ($this->checkbox_list->values as $list_value) {
48 | $response_value = clone $base_value;
49 | $response_value->question_option = $list_value;
50 | $response_value->text_value = $this->checkbox_list->getEntryValue(
51 | $list_value
52 | );
53 | $value[] = $response_value;
54 | }
55 |
56 | return $value;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Inquisition/InquisitionImporter.php:
--------------------------------------------------------------------------------
1 | app = $app;
17 | }
18 |
19 | // inquisition
20 |
21 | public function importInquisition(
22 | InquisitionInquisition $inquisition,
23 | InquisitionFileParser $file
24 | ) {
25 | $this->importInquisitionProperties($inquisition, $file);
26 | $this->importQuestions($inquisition, $file);
27 | }
28 |
29 | protected function importInquisitionProperties(
30 | InquisitionInquisition $inquisition,
31 | InquisitionFileParser $file
32 | ) {}
33 |
34 | // questions
35 |
36 | protected function importQuestions(
37 | InquisitionInquisition $inquisition,
38 | InquisitionFileParser $file
39 | ) {
40 | $importer = $this->getQuestionImporter();
41 | $questions = $importer->importQuestions($file);
42 |
43 | $binding_class = SwatDBClassMap::get(
44 | InquisitionInquisitionQuestionBinding::class
45 | );
46 |
47 | foreach ($questions as $question) {
48 | $binding = new $binding_class();
49 | $binding->setDatabase($this->app->db);
50 |
51 | $binding->question = $question;
52 | $binding->inquisition = $inquisition;
53 |
54 | $previous_binding = $inquisition->question_bindings->getLast();
55 |
56 | if ($previous_binding instanceof $binding_class) {
57 | $binding->displayorder = $previous_binding->displayorder + 1;
58 | } else {
59 | $binding->displayorder = 1;
60 | }
61 |
62 | $inquisition->question_bindings->add($binding);
63 | }
64 | }
65 |
66 | protected function getQuestionImporter()
67 | {
68 | return new InquisitionQuestionImporter($this->app);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/.php-cs-fixer.php:
--------------------------------------------------------------------------------
1 | in(__DIR__);
9 |
10 | return (new Config())
11 | ->setParallelConfig(ParallelConfigFactory::detect(null, null, 2**18-1))
12 | ->setRules([
13 | '@PhpCsFixer' => true,
14 | '@PHP82Migration' => true,
15 | 'indentation_type' => true,
16 |
17 | // Overrides for (opinionated) @PhpCsFixer and @Symfony rules:
18 |
19 | // Align "=>" in multi-line array definitions, unless a blank line exists between elements
20 | 'binary_operator_spaces' => ['operators' => ['=>' => 'align_single_space_minimal']],
21 |
22 | // Subset of statements that should be proceeded with blank line
23 | 'blank_line_before_statement' => ['statements' => ['case', 'continue', 'declare', 'default', 'return', 'throw', 'try', 'yield', 'yield_from']],
24 |
25 | // Enforce space around concatenation operator
26 | 'concat_space' => ['spacing' => 'one'],
27 |
28 | // Use {} for empty loop bodies
29 | 'empty_loop_body' => ['style' => 'braces'],
30 |
31 | // Don't change any increment/decrement styles
32 | 'increment_style' => false,
33 |
34 | // Forbid multi-line whitespace before the closing semicolon
35 | 'multiline_whitespace_before_semicolons' => ['strategy' => 'no_multi_line'],
36 |
37 | // Clean up PHPDocs, but leave @inheritDoc entries alone
38 | 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'remove_inheritdoc' => false],
39 |
40 | // Ensure that traits are listed first in classes
41 | // (it would be nice to enforce more, but we'll start simple)
42 | 'ordered_class_elements' => ['order' => ['use_trait']],
43 |
44 | // Ensure that param and return types are sorted consistently, with null at end
45 | 'phpdoc_types_order' => ['sort_algorithm' => 'alpha', 'null_adjustment' => 'always_last'],
46 |
47 | // Yoda style is too weird
48 | 'yoda_style' => false,
49 | ])
50 | ->setIndent(' ')
51 | ->setLineEnding("\n")
52 | ->setFinder($finder);
53 |
--------------------------------------------------------------------------------
/sql/fixtures/InquisitionQuestionImageDimension.sql:
--------------------------------------------------------------------------------
1 | insert into ImageDimension (
2 | image_set,
3 | default_type,
4 | shortname,
5 | title,
6 | max_width,
7 | max_height,
8 | crop,
9 | dpi,
10 | quality,
11 | strip,
12 | interlace,
13 | resize_filter,
14 | upscale
15 | ) values (
16 | (select id from ImageSet where shortname = 'inquisition-question'),
17 | (select id from ImageType where mime_type = 'image/jpeg'),
18 | 'original',
19 | 'Original',
20 | null,
21 | null,
22 | false,
23 | 72,
24 | 80,
25 | true,
26 | false,
27 | null,
28 | false
29 | );
30 |
31 | insert into ImageDimension (
32 | image_set,
33 | default_type,
34 | shortname,
35 | title,
36 | max_width,
37 | max_height,
38 | crop,
39 | dpi,
40 | quality,
41 | strip,
42 | interlace,
43 | resize_filter,
44 | upscale
45 | ) values (
46 | (select id from ImageSet where shortname = 'inquisition-question'),
47 | (select id from ImageType where mime_type = 'image/jpeg'),
48 | 'thumb',
49 | 'Thumbnail',
50 | 100,
51 | 100,
52 | true,
53 | 72,
54 | 80,
55 | true,
56 | false,
57 | null,
58 | false
59 | );
60 |
61 | insert into ImageDimension (
62 | image_set,
63 | default_type,
64 | shortname,
65 | title,
66 | max_width,
67 | max_height,
68 | crop,
69 | dpi,
70 | quality,
71 | strip,
72 | interlace,
73 | resize_filter,
74 | upscale
75 | ) values (
76 | (select id from ImageSet where shortname = 'inquisition-question'),
77 | (select id from ImageType where mime_type = 'image/jpeg'),
78 | 'small',
79 | 'Small',
80 | 500,
81 | 500,
82 | true,
83 | 72,
84 | 80,
85 | true,
86 | false,
87 | null,
88 | false
89 | );
90 |
91 | insert into ImageDimension (
92 | image_set,
93 | default_type,
94 | shortname,
95 | title,
96 | max_width,
97 | max_height,
98 | crop,
99 | dpi,
100 | quality,
101 | strip,
102 | interlace,
103 | resize_filter,
104 | upscale
105 | ) values (
106 | (select id from ImageSet where shortname = 'inquisition-question'),
107 | (select id from ImageType where mime_type = 'image/jpeg'),
108 | 'large',
109 | 'Large',
110 | 1000,
111 | 1000,
112 | true,
113 | 72,
114 | 80,
115 | true,
116 | false,
117 | null,
118 | false
119 | );
120 |
--------------------------------------------------------------------------------
/sql/fixtures/InquisitionQuestionOptionImageDimension.sql:
--------------------------------------------------------------------------------
1 | insert into ImageDimension (
2 | image_set,
3 | default_type,
4 | shortname,
5 | title,
6 | max_width,
7 | max_height,
8 | crop,
9 | dpi,
10 | quality,
11 | strip,
12 | interlace,
13 | resize_filter,
14 | upscale
15 | ) values (
16 | (select id from ImageSet where shortname = 'inquisition-question-option'),
17 | (select id from ImageType where mime_type = 'image/jpeg'),
18 | 'original',
19 | 'Original',
20 | null,
21 | null,
22 | false,
23 | 72,
24 | 80,
25 | true,
26 | false,
27 | null,
28 | false
29 | );
30 |
31 | insert into ImageDimension (
32 | image_set,
33 | default_type,
34 | shortname,
35 | title,
36 | max_width,
37 | max_height,
38 | crop,
39 | dpi,
40 | quality,
41 | strip,
42 | interlace,
43 | resize_filter,
44 | upscale
45 | ) values (
46 | (select id from ImageSet where shortname = 'inquisition-question-option'),
47 | (select id from ImageType where mime_type = 'image/jpeg'),
48 | 'thumb',
49 | 'Thumbnail',
50 | 100,
51 | 100,
52 | true,
53 | 72,
54 | 80,
55 | true,
56 | false,
57 | null,
58 | false
59 | );
60 |
61 | insert into ImageDimension (
62 | image_set,
63 | default_type,
64 | shortname,
65 | title,
66 | max_width,
67 | max_height,
68 | crop,
69 | dpi,
70 | quality,
71 | strip,
72 | interlace,
73 | resize_filter,
74 | upscale
75 | ) values (
76 | (select id from ImageSet where shortname = 'inquisition-question-option'),
77 | (select id from ImageType where mime_type = 'image/jpeg'),
78 | 'small',
79 | 'Small',
80 | 500,
81 | 500,
82 | true,
83 | 72,
84 | 80,
85 | true,
86 | false,
87 | null,
88 | false
89 | );
90 |
91 | insert into ImageDimension (
92 | image_set,
93 | default_type,
94 | shortname,
95 | title,
96 | max_width,
97 | max_height,
98 | crop,
99 | dpi,
100 | quality,
101 | strip,
102 | interlace,
103 | resize_filter,
104 | upscale
105 | ) values (
106 | (select id from ImageSet where shortname = 'inquisition-question-option'),
107 | (select id from ImageType where mime_type = 'image/jpeg'),
108 | 'large',
109 | 'Large',
110 | 1000,
111 | 1000,
112 | true,
113 | 72,
114 | 80,
115 | true,
116 | false,
117 | null,
118 | false
119 | );
120 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/add.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Question
7 |
8 |
9 | Question
10 |
11 | true
12 | 5
13 |
14 |
15 |
16 | Options
17 |
18 |
19 |
20 |
21 | 2
22 | false
23 | 255
24 |
25 |
26 |
27 |
28 | Correct Option
29 |
30 |
31 |
32 |
33 |
34 | Remove
35 |
36 |
37 |
38 | 4
39 | add another option
40 |
41 |
42 |
43 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/index.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Search Questions
7 |
8 |
9 | New Queston
10 | Question/Edit
11 | add
12 |
13 |
14 |
15 |
16 | Keywords
17 |
18 |
19 |
24 |
25 |
26 |
27 | Questions
28 | false
29 |
30 |
31 |
32 |
33 |
34 | id
35 |
36 |
37 |
38 | Question
39 |
40 | bodytext
41 | Question/Details?id=%s
42 | id
43 |
44 |
45 |
46 |
47 |
48 | delete…
49 |
50 |
51 |
52 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/ImageDelete.php:
--------------------------------------------------------------------------------
1 | question = SwatDBClassMap::new(InquisitionQuestion::class);
21 | $this->question->setDatabase($this->app->db);
22 |
23 | if ($id == '') {
24 | throw new AdminNotFoundException(
25 | 'Question id not provided.'
26 | );
27 | }
28 |
29 | if (!$this->question->load($id)) {
30 | throw new AdminNotFoundException(
31 | sprintf(
32 | 'Question with id ‘%s’ not found.',
33 | $id
34 | )
35 | );
36 | }
37 |
38 | parent::setId($id);
39 | }
40 |
41 | protected function getImageWrapper()
42 | {
43 | return SwatDBClassMap::get(InquisitionQuestionImageWrapper::class);
44 | }
45 |
46 | // build phase
47 |
48 | protected function buildNavBar()
49 | {
50 | AdminDBDelete::buildNavBar();
51 |
52 | $this->navbar->popEntry();
53 |
54 | if ($this->inquisition instanceof InquisitionInquisition) {
55 | $this->navbar->createEntry(
56 | $this->inquisition->title,
57 | sprintf(
58 | 'Inquisition/Details?id=%s',
59 | $this->inquisition->id
60 | )
61 | );
62 | }
63 |
64 | $this->navbar->createEntry(
65 | $this->getQuestionTitle(),
66 | sprintf(
67 | 'Question/Details?id=%s%s',
68 | $this->question->id,
69 | $this->getLinkSuffix()
70 | )
71 | );
72 |
73 | $this->navbar->createEntry(
74 | ngettext(
75 | 'Delete Image',
76 | 'Delete Images',
77 | count($this->images)
78 | )
79 | );
80 | }
81 |
82 | protected function getQuestionTitle()
83 | {
84 | // TODO: Update this with some version of getPosition().
85 | return Inquisition::_('Question');
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionQuestionOption.php:
--------------------------------------------------------------------------------
1 | table = 'InquisitionQuestionOption';
39 | $this->id_field = 'integer:id';
40 |
41 | $this->registerInternalProperty(
42 | 'question',
43 | SwatDBClassMap::get(InquisitionQuestion::class)
44 | );
45 | }
46 |
47 | // loader methods
48 |
49 | protected function loadValues()
50 | {
51 | $sql = sprintf(
52 | 'select * from InquisitionResponseValue
53 | where question_option = %s order by id',
54 | $this->db->quote($this->id, 'integer')
55 | );
56 |
57 | return SwatDB::query(
58 | $this->db,
59 | $sql,
60 | SwatDBClassMap::get(InquisitionResponseValueWrapper::class)
61 | );
62 | }
63 |
64 | protected function loadImages()
65 | {
66 | $sql = sprintf(
67 | 'select * from Image
68 | inner join InquisitionQuestionOptionImageBinding
69 | on InquisitionQuestionOptionImageBinding.image = Image.id
70 | where InquisitionQuestionOptionImageBinding.question_option = %s
71 | order by InquisitionQuestionOptionImageBinding.displayorder,
72 | InquisitionQuestionOptionImageBinding.image',
73 | $this->db->quote($this->id, 'integer')
74 | );
75 |
76 | return SwatDB::query(
77 | $this->db,
78 | $sql,
79 | SwatDBClassMap::get(InquisitionQuestionOptionImageWrapper::class)
80 | );
81 | }
82 |
83 | protected function loadPosition()
84 | {
85 | $sql = sprintf(
86 | 'select position from (
87 | select id, rank() over (
88 | partition by question order by displayorder, id
89 | ) as position from InquisitionQuestionOption
90 | ) as temp where id = %s',
91 | $this->id
92 | );
93 |
94 | return SwatDB::queryOne($this->db, $sql);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Option/details.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Edit
9 | Option/Edit?id=%s
10 | edit
11 |
12 |
13 |
14 |
15 |
16 |
17 | Bodytext
18 |
19 | title
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | Images
29 |
30 |
31 |
32 | Add Image
33 | Option/ImageUpload?id=%s
34 | add
35 |
36 |
37 | Change Image Order
38 | Option/ImageOrder?id=%s
39 | change-order
40 |
41 |
42 |
43 |
44 |
45 |
46 | image
47 | width
48 | height
49 | preview_image
50 | preview_width
51 | preview_height
52 | false
53 |
54 |
55 |
56 | id
57 |
58 |
59 |
60 |
61 |
62 | delete…
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/Inquisition/InquisitionFileParser.php:
--------------------------------------------------------------------------------
1 | file = $filename;
23 | } else {
24 | $this->file = new SplFileObject($filename, 'r');
25 | }
26 |
27 | $this->file->setFlags(SplFileObject::READ_CSV);
28 |
29 | $this->defuseBOM();
30 | }
31 |
32 | public function key(): mixed
33 | {
34 | return $this->file->key();
35 | }
36 |
37 | public function current(): mixed
38 | {
39 | return $this->file->current();
40 | }
41 |
42 | public function next(): void
43 | {
44 | if (!$this->file->eof()) {
45 | // count newlines in csv columns
46 | $this->line += mb_substr_count(implode('', $this->current()), "\n");
47 |
48 | // count next line
49 | $this->line++;
50 |
51 | $this->file->next();
52 |
53 | // Need to call current to parse next line, otherwise the eof()
54 | // call will not be valid.
55 | $current = $this->current();
56 |
57 | // skip blank lines
58 | while (!$this->file->eof()) {
59 | if (array_pop($current) === null) {
60 | $this->line++;
61 | $this->file->next();
62 | $current = $this->current();
63 | } else {
64 | break;
65 | }
66 | }
67 | }
68 | }
69 |
70 | public function rewind(): void
71 | {
72 | $this->file->rewind();
73 | $this->line = 1;
74 | }
75 |
76 | public function valid(): bool
77 | {
78 | return $this->file->valid();
79 | }
80 |
81 | public function eof()
82 | {
83 | return $this->file->eof();
84 | }
85 |
86 | public function line()
87 | {
88 | return $this->line;
89 | }
90 |
91 | public function row()
92 | {
93 | return $this->file->key() + 1;
94 | }
95 |
96 | /**
97 | * Seeks ahead of the byte-order-mark if it exists in the file.
98 | *
99 | * If there is a BOM at the start of the current line, then move the file
100 | * pointer past the BOM. This will cause subsequent calls to
101 | * $file->current() to skip the BOM.
102 | */
103 | protected function defuseBOM()
104 | {
105 | $bom = "\xef\xbb\xbf";
106 | $data = $this->file->current();
107 | $encoding = '8bit';
108 |
109 | if (mb_strpos($data[0], $bom, 0, $encoding) === 0) {
110 | $this->file->fseek(mb_strlen($bom, $encoding));
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/Import.php:
--------------------------------------------------------------------------------
1 | ui->getWidget('questions_file');
33 | if ($questions_file->isUploaded()) {
34 | $this->importInquisition($questions_file->getTempFileName());
35 | }
36 | }
37 |
38 | protected function importInquisition($filename)
39 | {
40 | $inquisition = $this->getObject();
41 | $initial_questions_count = count($inquisition->question_bindings);
42 |
43 | try {
44 | $importer = $this->getImporter();
45 | $importer->importInquisition(
46 | $inquisition,
47 | $this->getFileParser($filename)
48 | );
49 | } catch (InquisitionImportException $e) {
50 | $this->ui->getWidget('questions_file')->addMessage(
51 | new SwatMessage($e->getMessage())
52 | );
53 | }
54 |
55 | $final_question_count = count($inquisition->question_bindings);
56 | $this->imported_question_count = $final_question_count -
57 | $initial_questions_count;
58 | }
59 |
60 | protected function getSavedMessage()
61 | {
62 | $locale = SwatI18NLocale::get();
63 |
64 | return new SwatMessage(
65 | sprintf(
66 | Inquisition::ngettext(
67 | 'One question has been imported.',
68 | '%s questions have been imported.',
69 | $this->imported_question_count
70 | ),
71 | $locale->formatNumber($this->imported_question_count)
72 | )
73 | );
74 | }
75 |
76 | protected function getFileParser($filename)
77 | {
78 | return new InquisitionFileParser($filename);
79 | }
80 |
81 | protected function getImporter()
82 | {
83 | return new InquisitionImporter($this->app);
84 | }
85 |
86 | // build phase
87 |
88 | protected function buildFrame()
89 | {
90 | parent::buildFrame();
91 |
92 | $this->ui->getWidget('edit_frame')->title =
93 | Inquisition::_('Import Questions');
94 | }
95 |
96 | protected function buildNavBar()
97 | {
98 | parent::buildNavBar();
99 |
100 | $this->navbar->createEntry(Inquisition::_('Import Questions'));
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Inquisition/Edit.php:
--------------------------------------------------------------------------------
1 | ui->loadFromXML($this->getUiXml());
22 | $this->initInquisition();
23 | }
24 |
25 | protected function initInquisition()
26 | {
27 | $this->inquisition = SwatDBClassMap::new(InquisitionInquisition::class);
28 | $this->inquisition->setDatabase($this->app->db);
29 |
30 | if ($this->id != '') {
31 | if (!$this->inquisition->load($this->id)) {
32 | throw new AdminNotFoundException(
33 | sprintf('Inquisition with id ‘%s’ not found.', $this->id)
34 | );
35 | }
36 | }
37 | }
38 |
39 | protected function getUiXml()
40 | {
41 | return __DIR__ . '/edit.xml';
42 | }
43 |
44 | // process phase
45 |
46 | protected function saveDBData(): void
47 | {
48 | $this->updateInquisition();
49 |
50 | if ($this->inquisition->isModified()) {
51 | $this->inquisition->save();
52 | $this->app->messages->add($this->getSavedMessage());
53 | }
54 | }
55 |
56 | protected function updateInquisition()
57 | {
58 | $values = $this->ui->getValues(
59 | [
60 | 'title',
61 | ]
62 | );
63 |
64 | $this->inquisition->title = $values['title'];
65 |
66 | if ($this->ui->hasWidget('enabled')) {
67 | $this->inquisition->enabled =
68 | $this->ui->getWidget('enabled')->value;
69 | }
70 |
71 | if ($this->inquisition->id === null) {
72 | $now = new SwatDate();
73 | $now->toUTC();
74 | $this->inquisition->createdate = $now;
75 | }
76 | }
77 |
78 | protected function getSavedMessage()
79 | {
80 | return new SwatMessage(Inquisition::_('Inquisition has been saved.'));
81 | }
82 |
83 | protected function relocate()
84 | {
85 | $this->app->relocate(
86 | sprintf(
87 | 'Inquisition/Details?id=%s',
88 | $this->inquisition->id
89 | )
90 | );
91 | }
92 |
93 | // build phase
94 |
95 | protected function loadDBData()
96 | {
97 | $this->ui->setValues($this->inquisition->getAttributes());
98 | }
99 |
100 | protected function buildNavBar()
101 | {
102 | parent::buildNavBar();
103 |
104 | $last = $this->navbar->popEntry();
105 |
106 | if ($this->id != '') {
107 | $this->navbar->createEntry(
108 | $this->inquisition->title,
109 | sprintf(
110 | 'Inquisition/Details?id=%s',
111 | $this->inquisition->id
112 | )
113 | );
114 | }
115 |
116 | $this->navbar->addEntry($last);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Inquisition/Index.php:
--------------------------------------------------------------------------------
1 | ui->loadFromXML($this->getUiXml());
18 |
19 | $view = $this->ui->getWidget('inquisition_view');
20 | $view->setDefaultOrderbyColumn(
21 | $view->getColumn('title'),
22 | SwatTableViewOrderableColumn::ORDER_BY_DIR_ASCENDING
23 | );
24 | }
25 |
26 | protected function getUiXml()
27 | {
28 | return __DIR__ . '/index.xml';
29 | }
30 |
31 | // build phase
32 |
33 | protected function getTableModel(SwatView $view): ?SwatTableModel
34 | {
35 | switch ($view->id) {
36 | case 'inquisition_view':
37 | return $this->getInquisitionTableModel($view);
38 | }
39 |
40 | return null;
41 | }
42 |
43 | protected function getInquisitionTableModel(SwatView $view)
44 | {
45 | $sql = sprintf(
46 | 'select * from Inquisition
47 | order by %s',
48 | $this->getOrderByClause($view, 'title asc')
49 | );
50 |
51 | $inquisitions = SwatDB::query(
52 | $this->app->db,
53 | $sql,
54 | SwatDBClassMap::get(InquisitionInquisitionWrapper::class)
55 | );
56 |
57 | // efficiently load the question bindings
58 | $inquisitions->loadAllSubRecordsets(
59 | 'question_bindings',
60 | SwatDBClassMap::get(InquisitionInquisitionQuestionBindingWrapper::class),
61 | 'InquisitionInquisitionQuestionBinding',
62 | 'inquisition',
63 | '',
64 | 'inquisition, displayorder, question'
65 | );
66 |
67 | $locale = SwatI18NLocale::get();
68 | $store = new SwatTableStore();
69 |
70 | foreach ($inquisitions as $inquisition) {
71 | $ds = new SwatDetailsStore($inquisition);
72 | $question_count = count($inquisition->question_bindings);
73 | $visible_question_count = count(
74 | $inquisition->visible_question_bindings
75 | );
76 |
77 | if ($visible_question_count != $question_count) {
78 | $ds->question_count = sprintf(
79 | Inquisition::ngettext(
80 | '%s question, %s shown on site',
81 | '%s questions, %s shown on site',
82 | $question_count
83 | ),
84 | $locale->formatNumber($question_count),
85 | $locale->formatNumber($visible_question_count)
86 | );
87 | } else {
88 | $ds->question_count = sprintf(
89 | Inquisition::ngettext(
90 | '%s question',
91 | '%s questions',
92 | $question_count
93 | ),
94 | $locale->formatNumber($question_count)
95 | );
96 | }
97 |
98 | $store->add($ds);
99 | }
100 |
101 | return $store;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/Inquisition/InquisitionRadioEntryList.php:
--------------------------------------------------------------------------------
1 | addJavaScript(
25 | 'packages/inquisition/javascript/inquisition-radio-entry-list.js'
26 | );
27 |
28 | $yui = new SwatYUI(['dom', 'event']);
29 | $this->html_head_entry_set->addEntrySet($yui->getHtmlHeadEntrySet());
30 |
31 | $this->classes[] = 'inquisition-radio-entry-list';
32 | }
33 |
34 | /**
35 | * Processes this radio list.
36 | */
37 | public function process()
38 | {
39 | parent::process();
40 |
41 | if ($this->hasEntry($this->value)
42 | && $this->getEntryValue($this->value) === null) {
43 | $message = Inquisition::_(
44 | 'The selected option requires a value to be entered.'
45 | );
46 |
47 | $this->addMessage(new SwatMessage($message, 'error'));
48 | }
49 | }
50 |
51 | public function getEntryValue($option_value)
52 | {
53 | $value = null;
54 |
55 | if ($this->hasEntry($option_value)) {
56 | $value = $this->getCompositeWidget('entry_' . $option_value)->value;
57 | }
58 |
59 | return $value;
60 | }
61 |
62 | public function setEntryValue($option_value, $text)
63 | {
64 | if ($this->hasEntry($option_value)) {
65 | $this->getCompositeWidget('entry_' . $option_value)->value = $text;
66 | }
67 | }
68 |
69 | public function setEntryOption($value)
70 | {
71 | $this->entry_option_values[] = $value;
72 | }
73 |
74 | public function hasEntry($value)
75 | {
76 | return in_array($value, $this->entry_option_values);
77 | }
78 |
79 | public function display()
80 | {
81 | parent::display();
82 | Swat::displayInlineJavaScript($this->getInlineJavaScript());
83 | }
84 |
85 | protected function displayOptionLabel(SwatOption $option, $index)
86 | {
87 | parent::displayOptionLabel($option, $index);
88 |
89 | if ($this->hasEntry($option->value)) {
90 | echo '';
91 | $this->getCompositeWidget('entry_' . $option->value)->display();
92 | echo '';
93 | }
94 | }
95 |
96 | protected function createCompositeWidgets()
97 | {
98 | parent::createCompositeWidgets();
99 |
100 | foreach ($this->entry_option_values as $value) {
101 | $entry = new SwatEntry($this->id . '_entry_' . $value);
102 | $entry->maxlength = 255;
103 | $this->addCompositeWidget($entry, 'entry_' . $value);
104 | }
105 | }
106 |
107 | protected function getInlineJavaScript()
108 | {
109 | return sprintf(
110 | 'var %s_obj = new InquisitionRadioEntryList(%s);',
111 | $this->id,
112 | SwatString::quoteJavaScriptString($this->id)
113 | );
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Option/ImageDelete.php:
--------------------------------------------------------------------------------
1 | option = SwatDBClassMap::new(InquisitionQuestionOption::class);
21 | $this->option->setDatabase($this->app->db);
22 |
23 | if ($id == '') {
24 | throw new AdminNotFoundException(
25 | 'Option id not provided.'
26 | );
27 | }
28 |
29 | if (!$this->option->load($id)) {
30 | throw new AdminNotFoundException(
31 | sprintf(
32 | 'Option with id ‘%s’ not found.',
33 | $id
34 | )
35 | );
36 | }
37 |
38 | parent::setId($id);
39 | }
40 |
41 | protected function getImageWrapper()
42 | {
43 | return SwatDBClassMap::get(InquisitionQuestionOptionImageWrapper::class);
44 | }
45 |
46 | // build phase
47 |
48 | protected function buildNavBar()
49 | {
50 | AdminDBDelete::buildNavBar();
51 |
52 | $this->navbar->popEntry();
53 |
54 | if ($this->inquisition instanceof InquisitionInquisition) {
55 | $this->navbar->createEntry(
56 | $this->inquisition->title,
57 | sprintf(
58 | 'Inquisition/Details?id=%s',
59 | $this->inquisition->id
60 | )
61 | );
62 | }
63 |
64 | $this->navbar->createEntry(
65 | $this->getQuestionTitle(),
66 | sprintf(
67 | 'Question/Details?id=%s%s',
68 | $this->option->question->id,
69 | $this->getLinkSuffix()
70 | )
71 | );
72 |
73 | if ($this->option instanceof InquisitionQuestionOption) {
74 | $this->navbar->createEntry(
75 | $this->getOptionTitle(),
76 | sprintf(
77 | 'Option/Details?id=%s%s',
78 | $this->option->id,
79 | $this->getLinkSuffix()
80 | )
81 | );
82 | }
83 |
84 | $this->navbar->createEntry(
85 | Inquisition::ngettext(
86 | 'Delete Image',
87 | 'Delete Images',
88 | count($this->images)
89 | )
90 | );
91 | }
92 |
93 | protected function getQuestionTitle()
94 | {
95 | // TODO: Update this with some version of getPosition().
96 | return Inquisition::_('Question');
97 | }
98 |
99 | protected function getOptionTitle()
100 | {
101 | return sprintf(
102 | Inquisition::_('Option %s'),
103 | $this->option->position
104 | );
105 | }
106 |
107 | protected function getLinkSuffix()
108 | {
109 | $suffix = null;
110 | if ($this->inquisition instanceof InquisitionInquisition) {
111 | $suffix = sprintf(
112 | '&inquisition=%s',
113 | $this->inquisition->id
114 | );
115 | }
116 |
117 | return $suffix;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/Index.php:
--------------------------------------------------------------------------------
1 | ui->loadFromXML($this->getUiXml());
18 |
19 | $view = $this->ui->getWidget('index_view');
20 | $view->setDefaultOrderbyColumn(
21 | $view->getColumn('bodytext'),
22 | SwatTableViewOrderableColumn::ORDER_BY_DIR_ASCENDING
23 | );
24 | }
25 |
26 | protected function getUiXml()
27 | {
28 | return __DIR__ . '/index.xml';
29 | }
30 |
31 | // process phase
32 |
33 | protected function processActions(SwatView $view, SwatActions $actions)
34 | {
35 | switch ($actions->selected->id) {
36 | case 'delete':
37 | $this->app->replacePage('Question/Delete');
38 | $this->app->getPage()->setItems($view->getSelection());
39 | break;
40 | }
41 | }
42 |
43 | protected function processInternal()
44 | {
45 | parent::processInternal();
46 |
47 | $pager = $this->ui->getWidget('pager');
48 | $pager->total_records = SwatDB::queryOne(
49 | $this->app->db,
50 | sprintf(
51 | 'select count(id) from InquisitionQuestion where %s',
52 | $this->getWhereClause()
53 | )
54 | );
55 |
56 | $pager->process();
57 | }
58 |
59 | // build phase
60 |
61 | protected function getTableModel(SwatView $view): ?SwatTableModel
62 | {
63 | switch ($view->id) {
64 | case 'index_view':
65 | return $this->getQuestionTableModel($view);
66 | }
67 |
68 | return null;
69 | }
70 |
71 | protected function getQuestionTableModel(SwatView $view): SwatTableStore
72 | {
73 | $sql = sprintf(
74 | 'select InquisitionQuestion.*
75 | from InquisitionQuestion
76 | where %s
77 | order by %s',
78 | $this->getWhereClause(),
79 | $this->getOrderByClause($view, 'bodytext asc')
80 | );
81 |
82 | $pager = $this->ui->getWidget('pager');
83 | $this->app->db->setLimit($pager->page_size, $pager->current_record);
84 |
85 | $questions = SwatDB::query(
86 | $this->app->db,
87 | $sql,
88 | SwatDBClassMap::get(InquisitionQuestionWrapper::class)
89 | );
90 |
91 | if (count($questions) > 0) {
92 | $this->ui->getWidget('results_message')->content =
93 | $pager->getResultsMessage();
94 | }
95 |
96 | $store = new SwatTableStore();
97 | foreach ($questions as $question) {
98 | $ds = new SwatDetailsStore($question);
99 | $ds->bodytext = SwatString::condense($question->bodytext, 100);
100 | $store->add($ds);
101 | }
102 |
103 | return $store;
104 | }
105 |
106 | protected function getWhereClause()
107 | {
108 | $where = '1 = 1';
109 |
110 | // email
111 | $clause = new AdminSearchClause('bodytext');
112 | $clause->table = 'InquisitionQuestion';
113 | $clause->value = $this->ui->getWidget('search_keywords')->value;
114 | $clause->operator = AdminSearchClause::OP_CONTAINS;
115 | $where .= $clause->getClause($this->app->db);
116 |
117 | return $where;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionInquisitionQuestionBinding.php:
--------------------------------------------------------------------------------
1 | question->getView($this);
29 | }
30 |
31 | public function getPosition()
32 | {
33 | $sql = sprintf(
34 | 'select position from (
35 | select id, rank() over (
36 | partition by inquisition order by displayorder, id
37 | ) as position from InquisitionInquisitionQuestionBinding
38 | where inquisition = %s
39 | ) as temp where id = %s',
40 | $this->getInternalValue('inquisition'),
41 | $this->id
42 | );
43 |
44 | return SwatDB::queryOne($this->db, $sql);
45 | }
46 |
47 | public function getDependentOptions()
48 | {
49 | if (!is_array($this->dependent_options)) {
50 | $sql = sprintf(
51 | 'select InquisitionQuestionDependency.option,
52 | InquisitionQuestionDependency.question_binding,
53 | InquisitionInquisitionQuestionBinding.question
54 | from InquisitionQuestionDependency
55 | inner join InquisitionInquisitionQuestionBinding on
56 | InquisitionQuestionDependency.question_binding =
57 | InquisitionInquisitionQuestionBinding.id
58 | where dependent_question_binding = %s',
59 | $this->db->quote($this->id, 'integer')
60 | );
61 |
62 | $rs = SwatDB::query($this->db, $sql);
63 |
64 | $dependent_options = [];
65 |
66 | foreach ($rs as $row) {
67 | $option = [];
68 |
69 | $id = $row->question_binding . '_' . $row->question;
70 |
71 | $option['binding'] = $row->question_binding;
72 | $option['question'] = $row->question;
73 | $option['options'] = [$row->option];
74 |
75 | if (array_key_exists($id, $dependent_options)) {
76 | $dependent_options[$id]['options'][] = $row->option;
77 | } else {
78 | $dependent_options[$id] = $option;
79 | }
80 | }
81 |
82 | $this->dependent_options = array_values($dependent_options);
83 | }
84 |
85 | return $this->dependent_options;
86 | }
87 |
88 | protected function init()
89 | {
90 | $this->table = 'InquisitionInquisitionQuestionBinding';
91 | $this->id_field = 'integer:id';
92 |
93 | $this->registerInternalProperty(
94 | 'inquisition',
95 | SwatDBClassMap::get(InquisitionInquisition::class)
96 | );
97 |
98 | // We set autosave so that questions are saved before the binding.
99 | $this->registerInternalProperty(
100 | 'question',
101 | SwatDBClassMap::get(InquisitionQuestion::class),
102 | true
103 | );
104 | }
105 |
106 | protected function getSerializableSubDataObjects()
107 | {
108 | return array_merge(
109 | parent::getSerializableSubDataObjects(),
110 | ['question']
111 | );
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Inquisition/ImageUpload.php:
--------------------------------------------------------------------------------
1 | ui->loadFromXML($this->getUiXml());
23 |
24 | $this->initInquisition();
25 | }
26 |
27 | protected function initInquisition()
28 | {
29 | $inquisition_id = SiteApplication::initVar('inquisition');
30 |
31 | if ($inquisition_id !== null) {
32 | $this->inquisition = $this->loadInquisition($inquisition_id);
33 | }
34 | }
35 |
36 | protected function loadInquisition($inquisition_id)
37 | {
38 | $inquisition = SwatDBClassMap::new(InquisitionInquisition::class);
39 | $inquisition->setDatabase($this->app->db);
40 |
41 | if (!$inquisition->load($inquisition_id)) {
42 | throw new AdminNotFoundException(
43 | sprintf(
44 | 'Inquisition with id ‘%s’ not found.',
45 | $inquisition_id
46 | )
47 | );
48 | }
49 |
50 | return $inquisition;
51 | }
52 |
53 | protected function getUiXml()
54 | {
55 | return __DIR__ . '/image-upload.xml';
56 | }
57 |
58 | // process phase
59 |
60 | protected function saveDBData(): void
61 | {
62 | $original = $this->ui->getWidget('original_image');
63 |
64 | $image = $this->getImageObject();
65 | $image->process($original->getTempFileName());
66 |
67 | $this->updateBindings($image);
68 |
69 | $this->app->messages->add(
70 | new SwatMessage(
71 | sprintf(
72 | Inquisition::_('Image has been saved.'),
73 | $image->title
74 | )
75 | )
76 | );
77 | }
78 |
79 | protected function getImageObject()
80 | {
81 | $class_name = $this->getImageClass();
82 |
83 | $image = new $class_name();
84 | $image->setDatabase($this->app->db);
85 | $image->setFileBase('../images');
86 |
87 | return $image;
88 | }
89 |
90 | abstract protected function getImageClass();
91 |
92 | abstract protected function updateBindings(SiteImage $image);
93 |
94 | // build phase
95 |
96 | protected function buildForm()
97 | {
98 | parent::buildForm();
99 |
100 | if ($this->inquisition instanceof InquisitionInquisition) {
101 | $form = $this->ui->getWidget('edit_form');
102 | $form->addHiddenField('inquisition', $this->inquisition->id);
103 | }
104 | }
105 |
106 | protected function buildNavBar()
107 | {
108 | parent::buildNavBar();
109 |
110 | $this->navbar->popEntry();
111 |
112 | if ($this->inquisition instanceof InquisitionInquisition) {
113 | $this->navbar->createEntry(
114 | $this->inquisition->title,
115 | sprintf(
116 | 'Inquisition/Details?id=%s',
117 | $this->inquisition->id
118 | )
119 | );
120 | }
121 | }
122 |
123 | protected function getLinkSuffix()
124 | {
125 | $suffix = null;
126 | if ($this->inquisition instanceof InquisitionInquisition) {
127 | $suffix = sprintf(
128 | '&inquisition=%s',
129 | $this->inquisition->id
130 | );
131 | }
132 |
133 | return $suffix;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/Inquisition/Inquisition.php:
--------------------------------------------------------------------------------
1 | addJavaScript(
25 | 'packages/inquisition/javascript/inquisition-checkbox-entry-list.js'
26 | );
27 |
28 | $yui = new SwatYUI(['dom', 'event']);
29 | $this->html_head_entry_set->addEntrySet($yui->getHtmlHeadEntrySet());
30 |
31 | $this->classes[] = 'inquisition-checkbox-entry-list';
32 | }
33 |
34 | /**
35 | * Processes this checkbox list.
36 | */
37 | public function process()
38 | {
39 | parent::process();
40 |
41 | foreach ($this->values as $value) {
42 | if ($this->hasEntry($value)
43 | && $this->getEntryValue($value) == '') {
44 | $message = Inquisition::_(
45 | 'The selected option requires a value to be entered.'
46 | );
47 |
48 | $this->addMessage(new SwatMessage($message, 'error'));
49 | }
50 | }
51 | }
52 |
53 | public function getEntryValue($option_value)
54 | {
55 | $value = null;
56 |
57 | if ($this->hasEntry($option_value)) {
58 | $value = $this->getCompositeWidget('entry_' . $option_value)->value;
59 | }
60 |
61 | return $value;
62 | }
63 |
64 | public function setEntryValue($option_value, $text)
65 | {
66 | if ($this->hasEntry($option_value)) {
67 | $this->getCompositeWidget('entry_' . $option_value)->value = $text;
68 | }
69 | }
70 |
71 | public function setEntryOption($value)
72 | {
73 | $this->entry_option_values[] = $value;
74 | }
75 |
76 | public function hasEntry($value)
77 | {
78 | return in_array($value, $this->entry_option_values);
79 | }
80 |
81 | public function display()
82 | {
83 | parent::display();
84 | Swat::displayInlineJavaScript($this->getInlineJavaScript());
85 | }
86 |
87 | protected function displayOptionLabel(SwatOption $option, $index)
88 | {
89 | parent::displayOptionLabel($option, $index);
90 |
91 | if ($this->hasEntry($option->value)) {
92 | echo '';
93 | $this->getCompositeWidget('entry_' . $option->value)->display();
94 | echo '';
95 | }
96 | }
97 |
98 | protected function createCompositeWidgets()
99 | {
100 | parent::createCompositeWidgets();
101 |
102 | $options = $this->getOptions();
103 |
104 | foreach ($this->entry_option_values as $value) {
105 | // get index of checkbox
106 | $index = 0;
107 | foreach ($options as $option) {
108 | if ($option->value === $value) {
109 | break;
110 | }
111 | $index++;
112 | }
113 |
114 | $entry = new SwatEntry($this->id . '_entry_' . $index);
115 | $entry->maxlength = 255;
116 | $this->addCompositeWidget($entry, 'entry_' . $value);
117 | }
118 | }
119 |
120 | protected function getInlineJavaScript()
121 | {
122 | return sprintf(
123 | 'var %s_obj = new InquisitionCheckboxEntryList(%s);',
124 | $this->id,
125 | SwatString::quoteJavaScriptString($this->id)
126 | );
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/Order.php:
--------------------------------------------------------------------------------
1 | initInquisition();
23 | }
24 |
25 | protected function initInquisition()
26 | {
27 | $id = SiteApplication::initVar('id');
28 |
29 | if ($id == '') {
30 | throw new AdminNotFoundException(
31 | 'No inquisition id specified.'
32 | );
33 | }
34 |
35 | if (is_numeric($id)) {
36 | $id = intval($id);
37 | }
38 |
39 | $this->inquisition = SwatDBClassMap::new(InquisitionInquisition::class);
40 | $this->inquisition->setDatabase($this->app->db);
41 |
42 | if (!$this->inquisition->load($id)) {
43 | throw new AdminNotFoundException(
44 | sprintf(
45 | 'An inquisition with the id of “%s” does not exist',
46 | $id
47 | )
48 | );
49 | }
50 | }
51 |
52 | // process phase
53 |
54 | protected function saveIndex($id, $index)
55 | {
56 | SwatDB::updateColumn(
57 | $this->app->db,
58 | 'InquisitionInquisitionQuestionBinding',
59 | 'integer:displayorder',
60 | $index,
61 | 'integer:id',
62 | [$id]
63 | );
64 | }
65 |
66 | protected function getUpdatedMessage()
67 | {
68 | return new SwatMessage(
69 | Inquisition::_('Question order has been updated.')
70 | );
71 | }
72 |
73 | protected function relocate()
74 | {
75 | $this->app->relocate(
76 | sprintf(
77 | 'Inquisition/Details?id=%s',
78 | $this->inquisition->id
79 | )
80 | );
81 | }
82 |
83 | // build phase
84 |
85 | protected function buildInternal()
86 | {
87 | $this->ui->getWidget('order_frame')->title =
88 | Inquisition::_('Change Question Order');
89 |
90 | $this->ui->getWidget('order')->width = '500px';
91 | $this->ui->getWidget('order')->height = '500px';
92 |
93 | parent::buildInternal();
94 | }
95 |
96 | protected function buildNavBar()
97 | {
98 | parent::buildNavBar();
99 |
100 | $this->navbar->popEntries(2);
101 |
102 | $this->navbar->createEntry(
103 | Inquisition::_('Inquisition'),
104 | 'Inquisition'
105 | );
106 |
107 | $this->navbar->createEntry(
108 | $this->inquisition->title,
109 | sprintf(
110 | 'Inquisition/Details?id=%s',
111 | $this->inquisition->id
112 | )
113 | );
114 |
115 | $this->navbar->createEntry(Inquisition::_('Change Question Order'));
116 | }
117 |
118 | protected function buildForm()
119 | {
120 | parent::buildForm();
121 |
122 | $form = $this->ui->getWidget('order_form');
123 | $form->addHiddenField('id', $this->inquisition->id);
124 | }
125 |
126 | protected function loadData()
127 | {
128 | $sum = 0;
129 | $order_widget = $this->ui->getWidget('order');
130 |
131 | foreach ($this->inquisition->question_bindings as $question_binding) {
132 | $sum += $question_binding->displayorder;
133 |
134 | $order_widget->addOption(
135 | $question_binding->id,
136 | $question_binding->question->bodytext,
137 | 'text/xml'
138 | );
139 | }
140 |
141 | $options_list = $this->ui->getWidget('options');
142 | $options_list->value = ($sum == 0) ? 'auto' : 'custom';
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/ImageUpload.php:
--------------------------------------------------------------------------------
1 | initQuestion();
23 | }
24 |
25 | protected function initQuestion()
26 | {
27 | if ($this->id == '') {
28 | throw new AdminNotFoundException(
29 | Inquisition::_('Unable to load a question.')
30 | );
31 | }
32 |
33 | $this->question = SwatDBClassMap::new(InquisitionQuestion::class);
34 | $this->question->setDatabase($this->app->db);
35 |
36 | if (!$this->question->load($this->id)) {
37 | throw new AdminNotFoundException(
38 | sprintf(
39 | Inquisition::_(
40 | 'Unable to load question with id of “%s”.'
41 | ),
42 | $this->id
43 | )
44 | );
45 | }
46 | }
47 |
48 | // process phase
49 |
50 | protected function getImageClass()
51 | {
52 | return SwatDBClassMap::get(InquisitionQuestionImage::class);
53 | }
54 |
55 | protected function updateBindings(SiteImage $image)
56 | {
57 | // set displayorder so the new image appears at the end of the
58 | // list of the current questions by default.
59 | $sql = sprintf(
60 | 'select coalesce(max(displayorder), 0)+10
61 | from InquisitionQuestionImageBinding where question = %s',
62 | $this->app->db->quote($this->question->id, 'integer')
63 | );
64 |
65 | $displayorder = SwatDB::queryOne($this->app->db, $sql);
66 |
67 | $sql = sprintf(
68 | 'insert into InquisitionQuestionImageBinding
69 | (question, image, displayorder) values (%s, %s, %s)',
70 | $this->app->db->quote($this->question->id, 'integer'),
71 | $this->app->db->quote($image->id, 'integer'),
72 | $this->app->db->quote($displayorder, 'integer')
73 | );
74 |
75 | SwatDB::exec($this->app->db, $sql);
76 | }
77 |
78 | protected function relocate()
79 | {
80 | $this->app->relocate(
81 | sprintf(
82 | 'Question/Details?id=%s%s',
83 | $this->question->id,
84 | $this->getLinkSuffix()
85 | )
86 | );
87 | }
88 |
89 | // build phase
90 |
91 | protected function loadDBData() {}
92 |
93 | protected function buildNavBar()
94 | {
95 | parent::buildNavBar();
96 |
97 | $this->navbar->createEntry(
98 | $this->getQuestionTitle(),
99 | sprintf(
100 | 'Question/Details?id=%s%s',
101 | $this->question->id,
102 | $this->getLinkSuffix()
103 | )
104 | );
105 |
106 | $this->navbar->createEntry(Inquisition::_('Add Image'));
107 | }
108 |
109 | protected function buildFrame()
110 | {
111 | $frame = $this->ui->getWidget('edit_frame');
112 | $frame->title = $this->getQuestionTitle();
113 |
114 | $frame->subtitle = Inquisition::_('Add Image');
115 | }
116 |
117 | protected function getQuestionTitle()
118 | {
119 | // TODO: Update this with some version of getPosition().
120 | return Inquisition::_('Question');
121 | }
122 |
123 | protected function getLinkSuffix()
124 | {
125 | $suffix = null;
126 | if ($this->inquisition instanceof InquisitionInquisition) {
127 | $suffix = sprintf(
128 | '&inquisition=%s',
129 | $this->inquisition->id
130 | );
131 | }
132 |
133 | return $suffix;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Option/ImageUpload.php:
--------------------------------------------------------------------------------
1 | initOption();
23 | }
24 |
25 | protected function initOption()
26 | {
27 | if ($this->id == '') {
28 | throw new AdminNotFoundException(
29 | Inquisition::_('Unable to load a option.')
30 | );
31 | }
32 |
33 | $this->option = SwatDBClassMap::new(InquisitionQuestionOption::class);
34 | $this->option->setDatabase($this->app->db);
35 |
36 | if (!$this->option->load($this->id)) {
37 | throw new AdminNotFoundException(
38 | sprintf(
39 | Inquisition::_(
40 | 'Unable to load option with id of “%s”.'
41 | ),
42 | $this->id
43 | )
44 | );
45 | }
46 | }
47 |
48 | // process phase
49 |
50 | protected function getImageClass()
51 | {
52 | return SwatDBClassMap::get(InquisitionQuestionOptionImage::class);
53 | }
54 |
55 | protected function updateBindings(SiteImage $image)
56 | {
57 | $sql = sprintf(
58 | 'insert into InquisitionQuestionOptionImageBinding
59 | (question_option, image) values (%s, %s)',
60 | $this->app->db->quote($this->option->id, 'integer'),
61 | $this->app->db->quote($image->id, 'integer')
62 | );
63 |
64 | SwatDB::exec($this->app->db, $sql);
65 | }
66 |
67 | protected function relocate()
68 | {
69 | $this->app->relocate(
70 | sprintf(
71 | 'Option/Details?id=%s%s',
72 | $this->option->id,
73 | $this->getLinkSuffix()
74 | )
75 | );
76 | }
77 |
78 | // build phase
79 |
80 | protected function loadDBData() {}
81 |
82 | protected function buildFrame()
83 | {
84 | $frame = $this->ui->getWidget('edit_frame');
85 | $frame->title = sprintf($this->getOptionTitle());
86 | $frame->subtitle = $this->getTitle();
87 | }
88 |
89 | protected function buildNavBar()
90 | {
91 | parent::buildNavBar();
92 |
93 | $this->navbar->createEntry(
94 | $this->getQuestionTitle(),
95 | sprintf(
96 | 'Question/Details?id=%s%s',
97 | $this->option->question->id,
98 | $this->getLinkSuffix()
99 | )
100 | );
101 |
102 | $this->navbar->createEntry(
103 | $this->getOptionTitle(),
104 | sprintf(
105 | 'Option/Details?id=%s%s',
106 | $this->option->id,
107 | $this->getLinkSuffix()
108 | )
109 | );
110 |
111 | $this->navbar->createEntry($this->getTitle());
112 | }
113 |
114 | protected function getTitle()
115 | {
116 | return Inquisition::_('Add Image');
117 | }
118 |
119 | protected function getOptionTitle()
120 | {
121 | return sprintf(
122 | Inquisition::_('Option %s'),
123 | $this->option->position
124 | );
125 | }
126 |
127 | protected function getQuestionTitle()
128 | {
129 | // TODO: Update this with some version of getPosition().
130 | return Inquisition::_('Question');
131 | }
132 |
133 | protected function getLinkSuffix()
134 | {
135 | $suffix = null;
136 | if ($this->inquisition instanceof InquisitionInquisition) {
137 | $suffix = sprintf(
138 | '&inquisition=%s',
139 | $this->inquisition->id
140 | );
141 | }
142 |
143 | return $suffix;
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Inquisition/details.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Edit
9 | Inquisition/Edit?id=%s
10 | edit
11 |
12 |
13 | Delete
14 | Inquisition/Delete?id=%s
15 | delete
16 |
17 |
18 |
19 |
20 |
21 |
22 | Title
23 |
24 | title
25 |
26 |
27 |
28 | Created On
29 |
30 | createdate
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | Questions
40 |
41 |
42 |
43 | Add Question
44 | Question/Add?id=%s
45 | add
46 |
47 |
48 | Import Questions
49 | Question/Import?id=%s
50 | add
51 |
52 |
53 | Change Question Order
54 | Question/Order?id=%s
55 | change-order
56 |
57 |
58 |
59 |
60 |
61 | id
62 |
63 |
64 |
65 |
66 | title
67 | Question/Details?id=%s
68 | id
69 |
70 |
71 |
72 | Show on Site
73 | false
74 |
75 | enabled
76 | No
77 |
78 |
79 |
80 | # of Options
81 |
82 | option_count
83 |
84 |
85 |
86 | # of Images
87 |
88 | image_count
89 |
90 |
91 |
92 |
93 | bodytext
94 | text/xml
95 |
96 |
97 |
98 |
99 |
100 | delete…
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Inquisition/Delete.php:
--------------------------------------------------------------------------------
1 | initInquisition();
23 | }
24 |
25 | protected function initInquisition()
26 | {
27 | $this->inquisition = SwatDBClassMap::new(InquisitionInquisition::class);
28 | $this->inquisition->setDatabase($this->app->db);
29 |
30 | $id = $this->getFirstItem();
31 |
32 | if (!$this->inquisition->load($id)) {
33 | throw new AdminNotFoundException(sprintf(
34 | 'A inquisition with the id of “%s” does not exist',
35 | $id
36 | ));
37 | }
38 | }
39 |
40 | // process phase
41 |
42 | protected function processDBData(): void
43 | {
44 | parent::processDBData();
45 |
46 | $item_list = $this->getItemList('integer');
47 |
48 | $this->deleteQuestions($item_list);
49 |
50 | $sql = sprintf(
51 | 'delete from Inquisition where id in (%s)',
52 | $item_list
53 | );
54 |
55 | $num = SwatDB::exec($this->app->db, $sql);
56 | $this->app->messages->add($this->getDeletedMessage($num));
57 | }
58 |
59 | protected function deleteQuestions($item_list)
60 | {
61 | // By default delete questions that don't belong to other inquisitions
62 | // instead of leaving orphan questions.
63 | $sql = sprintf(
64 | 'delete from InquisitionQuestion where id in (
65 | select question from InquisitionInquisitionQuestionBinding
66 | where %s
67 | )',
68 | $this->getSingleQuizQuestionsWhere($item_list)
69 | );
70 |
71 | SwatDB::exec($this->app->db, $sql);
72 | }
73 |
74 | protected function getDeletedMessage($num)
75 | {
76 | return new SwatMessage(
77 | sprintf(
78 | Inquisition::ngettext(
79 | 'One inquisition has been deleted.',
80 | '%s inquisitions have been deleted.',
81 | $num
82 | ),
83 | SwatString::numberFormat($num)
84 | )
85 | );
86 | }
87 |
88 | // build phase
89 |
90 | protected function buildInternal()
91 | {
92 | parent::buildInternal();
93 |
94 | $item_list = $this->getItemList('integer');
95 |
96 | $dep = new AdminListDependency();
97 | $dep->entries = AdminListDependency::queryEntries(
98 | $this->app->db,
99 | 'Inquisition',
100 | 'id',
101 | null,
102 | 'text:title',
103 | 'id',
104 | 'id in (' . $item_list . ')',
105 | AdminDependency::DELETE
106 | );
107 |
108 | // check inquisition dependencies
109 | $dep_questions = new AdminSummaryDependency();
110 | $dep_questions->setTitle('question', 'questions');
111 | $dep_questions->summaries = AdminSummaryDependency::querySummaries(
112 | $this->app->db,
113 | 'InquisitionInquisitionQuestionBinding',
114 | 'integer:question',
115 | 'integer:inquisition',
116 | $this->getSingleQuizQuestionsWhere($item_list),
117 | AdminDependency::DELETE
118 | );
119 |
120 | $dep->addDependency($dep_questions);
121 |
122 | $message = $this->ui->getWidget('confirmation_message');
123 | $message->content = $dep->getMessage();
124 | $message->content_type = 'text/xml';
125 |
126 | if ($dep->getStatusLevelCount(AdminDependency::DELETE) == 0) {
127 | $this->switchToCancelButton();
128 | }
129 | }
130 |
131 | protected function buildNavBar()
132 | {
133 | parent::buildNavBar();
134 |
135 | $last = $this->navbar->popEntry();
136 |
137 | $this->navbar->createEntry(
138 | $this->inquisition->title,
139 | sprintf(
140 | 'Inquisition/Details?id=%s',
141 | $this->inquisition->id
142 | )
143 | );
144 |
145 | $this->navbar->addEntry($last);
146 | }
147 |
148 | // helper methods
149 |
150 | protected function getSingleQuizQuestionsWhere($item_list)
151 | {
152 | return sprintf(
153 | 'inquisition in (%1$s)
154 | and question not in (
155 | select question from InquisitionInquisitionQuestionBinding
156 | where inquisition not in (%1$s)
157 | )',
158 | $item_list
159 | );
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionQuestion.php:
--------------------------------------------------------------------------------
1 | question_type) {
65 | default:
66 | case self::TYPE_RADIO_LIST:
67 | $view = new InquisitionRadioListQuestionView($binding);
68 | break;
69 |
70 | case self::TYPE_FLYDOWN:
71 | $view = new InquisitionFlydownQuestionView($binding);
72 | break;
73 |
74 | case self::TYPE_RADIO_ENTRY:
75 | $view = new InquisitionRadioEntryQuestionView($binding);
76 | break;
77 |
78 | case self::TYPE_TEXT:
79 | $view = new InquisitionTextQuestionView($binding);
80 | break;
81 |
82 | case self::TYPE_CHECKBOX_LIST:
83 | $view = new InquisitionCheckboxListQuestionView($binding);
84 | break;
85 |
86 | case self::TYPE_CHECKBOX_ENTRY:
87 | $view = new InquisitionCheckboxEntryQuestionView($binding);
88 | break;
89 | }
90 |
91 | return $view;
92 | }
93 |
94 | protected function init()
95 | {
96 | $this->table = 'InquisitionQuestion';
97 | $this->id_field = 'integer:id';
98 |
99 | $this->registerInternalProperty(
100 | 'correct_option',
101 | SwatDBClassMap::get(InquisitionQuestionOption::class)
102 | );
103 |
104 | $this->registerInternalProperty(
105 | 'question_group',
106 | SwatDBClassMap::get(InquisitionQuestionGroup::class)
107 | );
108 | }
109 |
110 | protected function getSerializableSubDataObjects()
111 | {
112 | return array_merge(
113 | parent::getSerializableSubDataObjects(),
114 | ['options', 'correct_option']
115 | );
116 | }
117 |
118 | // loader methods
119 |
120 | protected function loadOptions()
121 | {
122 | $sql = sprintf(
123 | 'select * from InquisitionQuestionOption
124 | where question = %s
125 | order by displayorder',
126 | $this->db->quote($this->id, 'integer')
127 | );
128 |
129 | return SwatDB::query(
130 | $this->db,
131 | $sql,
132 | SwatDBClassMap::get(InquisitionQuestionOptionWrapper::class)
133 | );
134 | }
135 |
136 | protected function loadHints()
137 | {
138 | $sql = sprintf(
139 | 'select * from InquisitionQuestionHint
140 | where question = %s
141 | order by displayorder',
142 | $this->db->quote($this->id, 'integer')
143 | );
144 |
145 | return SwatDB::query(
146 | $this->db,
147 | $sql,
148 | SwatDBClassMap::get(InquisitionQuestionHintWrapper::class)
149 | );
150 | }
151 |
152 | protected function loadImages()
153 | {
154 | $sql = sprintf(
155 | 'select * from Image
156 | inner join InquisitionQuestionImageBinding
157 | on InquisitionQuestionImageBinding.image = Image.id
158 | where InquisitionQuestionImageBinding.question = %s
159 | order by InquisitionQuestionImageBinding.displayorder,
160 | InquisitionQuestionImageBinding.image',
161 | $this->db->quote($this->id, 'integer')
162 | );
163 |
164 | return SwatDB::query(
165 | $this->db,
166 | $sql,
167 | SwatDBClassMap::get(InquisitionQuestionImageWrapper::class)
168 | );
169 | }
170 |
171 | // saver methods
172 |
173 | protected function saveOptions()
174 | {
175 | foreach ($this->options as $option) {
176 | $option->question = $this;
177 | }
178 |
179 | $this->options->setDatabase($this->db);
180 | $this->options->save();
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/Edit.php:
--------------------------------------------------------------------------------
1 | ui->loadFromXML($this->getUiXml());
28 |
29 | $this->initQuestion();
30 | $this->initInquisition();
31 | }
32 |
33 | protected function initQuestion()
34 | {
35 | $this->question = SwatDBClassMap::new(InquisitionQuestion::class);
36 | $this->question->setDatabase($this->app->db);
37 |
38 | if ($this->id !== null && !$this->question->load($this->id)) {
39 | throw new AdminNotFoundException(
40 | sprintf(
41 | 'Question with id ‘%s’ not found.',
42 | $this->id
43 | )
44 | );
45 | }
46 | }
47 |
48 | protected function initInquisition()
49 | {
50 | $inquisition_id = SiteApplication::initVar('inquisition');
51 |
52 | if ($inquisition_id !== null) {
53 | $this->inquisition = $this->loadInquisition($inquisition_id);
54 | }
55 | }
56 |
57 | protected function loadInquisition($inquisition_id)
58 | {
59 | $inquisition = SwatDBClassMap::new(InquisitionInquisition::class);
60 | $inquisition->setDatabase($this->app->db);
61 |
62 | if (!$inquisition->load($inquisition_id)) {
63 | throw new AdminNotFoundException(
64 | sprintf(
65 | 'Inquisition with id ‘%s’ not found.',
66 | $inquisition_id
67 | )
68 | );
69 | }
70 |
71 | return $inquisition;
72 | }
73 |
74 | protected function getUiXml()
75 | {
76 | return __DIR__ . '/edit.xml';
77 | }
78 |
79 | // process phase
80 |
81 | protected function saveDBData(): void
82 | {
83 | $this->updateQuestion();
84 | $this->question->save();
85 |
86 | $this->app->messages->add(
87 | new SwatMessage(
88 | Inquisition::_('Question has been saved.')
89 | )
90 | );
91 | }
92 |
93 | protected function updateQuestion()
94 | {
95 | $values = $this->ui->getValues(
96 | [
97 | 'bodytext',
98 | 'enabled',
99 | ]
100 | );
101 |
102 | $this->question->bodytext = $values['bodytext'];
103 | $this->question->enabled = $values['enabled'];
104 | }
105 |
106 | protected function relocate()
107 | {
108 | $this->app->relocate(
109 | sprintf(
110 | 'Question/Details?id=%s%s',
111 | $this->question->id,
112 | $this->getLinkSuffix()
113 | )
114 | );
115 | }
116 |
117 | // build phase
118 |
119 | protected function loadDBData()
120 | {
121 | $this->ui->setValues($this->question->getAttributes());
122 | }
123 |
124 | protected function buildForm()
125 | {
126 | parent::buildForm();
127 |
128 | if ($this->inquisition instanceof InquisitionInquisition) {
129 | $form = $this->ui->getWidget('edit_form');
130 | $form->addHiddenField('inquisition', $this->inquisition->id);
131 | }
132 | }
133 |
134 | protected function buildNavBar()
135 | {
136 | parent::buildNavBar();
137 |
138 | $this->navbar->popEntry();
139 |
140 | if ($this->inquisition instanceof InquisitionInquisition) {
141 | $this->navbar->createEntry(
142 | $this->inquisition->title,
143 | sprintf(
144 | 'Inquisition/Details?id=%s',
145 | $this->inquisition->id
146 | )
147 | );
148 | }
149 |
150 | $this->navbar->createEntry(
151 | $this->getQuestionTitle(),
152 | sprintf(
153 | 'Question/Details?id=%s%s',
154 | $this->question->id,
155 | $this->getLinkSuffix()
156 | )
157 | );
158 |
159 | $this->navbar->createEntry(Inquisition::_('Edit Question'));
160 | }
161 |
162 | protected function getQuestionTitle()
163 | {
164 | // TODO: Update this with some version of getPosition().
165 | return Inquisition::_('Question');
166 | }
167 |
168 | protected function getLinkSuffix()
169 | {
170 | $suffix = null;
171 | if ($this->inquisition instanceof InquisitionInquisition) {
172 | $suffix = sprintf(
173 | '&inquisition=%s',
174 | $this->inquisition->id
175 | );
176 | }
177 |
178 | return $suffix;
179 | }
180 |
181 | // finalize phase
182 |
183 | public function finalize()
184 | {
185 | parent::finalize();
186 |
187 | $this->layout->addHtmlHeadEntry(
188 | 'packages/inquisition/admin/styles/inquisition-question-edit.css'
189 | );
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionResponse.php:
--------------------------------------------------------------------------------
1 | used_hint_bindings as $hint_binding) {
42 | $question_binding_id = $hint_binding->getInternalValue(
43 | 'question_binding'
44 | );
45 |
46 | if ($question_binding_id === $question_binding->id) {
47 | $wrapper->add($hint_binding);
48 | }
49 | }
50 |
51 | return $wrapper;
52 | }
53 |
54 | protected function init()
55 | {
56 | $this->table = 'InquisitionResponse';
57 | $this->id_field = 'integer:id';
58 |
59 | $this->registerDateProperty('createdate');
60 | $this->registerDateProperty('complete_date');
61 |
62 | $this->registerInternalProperty(
63 | 'inquisition',
64 | SwatDBClassMap::get(InquisitionInquisition::class)
65 | );
66 | }
67 |
68 | protected function getSerializableSubDataObjects()
69 | {
70 | return array_merge(
71 | parent::getSerializableSubDataObjects(),
72 | [
73 | 'values',
74 | 'visible_question_values',
75 | ]
76 | );
77 | }
78 |
79 | // loader methods
80 |
81 | protected function loadValues()
82 | {
83 | $sql = sprintf(
84 | 'select InquisitionResponseValue.*
85 | from InquisitionResponseValue
86 | inner join InquisitionResponse on
87 | InquisitionResponseValue.response = InquisitionResponse.id
88 | inner join InquisitionInquisitionQuestionBinding on
89 | InquisitionInquisitionQuestionBinding.id =
90 | InquisitionResponseValue.question_binding
91 | and InquisitionInquisitionQuestionBinding.inquisition =
92 | InquisitionResponse.inquisition
93 | where InquisitionResponseValue.response = %s
94 | order by InquisitionInquisitionQuestionBinding.displayorder',
95 | $this->db->quote($this->id, 'integer')
96 | );
97 |
98 | return SwatDB::query(
99 | $this->db,
100 | $sql,
101 | SwatDBClassMap::get(InquisitionResponseValueWrapper::class)
102 | );
103 | }
104 |
105 | protected function loadVisibleQuestionValues()
106 | {
107 | $sql = sprintf(
108 | 'select InquisitionResponseValue.*
109 | from InquisitionResponseValue
110 | inner join InquisitionResponse on
111 | InquisitionResponseValue.response = InquisitionResponse.id
112 | inner join InquisitionInquisitionQuestionBinding on
113 | InquisitionInquisitionQuestionBinding.id =
114 | InquisitionResponseValue.question_binding
115 | and InquisitionInquisitionQuestionBinding.inquisition =
116 | InquisitionResponse.inquisition
117 | inner join VisibleInquisitionQuestionView on
118 | InquisitionInquisitionQuestionBinding.question =
119 | VisibleInquisitionQuestionView.question
120 | where InquisitionResponseValue.response = %s
121 | order by InquisitionInquisitionQuestionBinding.displayorder',
122 | $this->db->quote($this->id, 'integer')
123 | );
124 |
125 | return SwatDB::query(
126 | $this->db,
127 | $sql,
128 | SwatDBClassMap::get(InquisitionResponseValueWrapper::class)
129 | );
130 | }
131 |
132 | protected function loadUsedHintBindings()
133 | {
134 | $sql = sprintf(
135 | 'select * from InquisitionResponseUsedHintBinding
136 | where InquisitionResponseUsedHintBinding.response = %s
137 | order by InquisitionResponseUsedHintBinding.createdate',
138 | $this->db->quote($this->id, 'integer')
139 | );
140 |
141 | $bindings = SwatDB::query(
142 | $this->db,
143 | $sql,
144 | SwatDBClassMap::get(InquisitionResponseUsedHintBindingWrapper::class)
145 | );
146 |
147 | $bindings->loadAllSubDataObjects(
148 | 'question_hint',
149 | $this->db,
150 | 'select * from InquisitionQuestionHint where id in (%s)',
151 | SwatDBClassMap::get(InquisitionQuestionHintWrapper::class)
152 | );
153 |
154 | return $bindings;
155 | }
156 |
157 | // saver methods
158 |
159 | protected function saveValues()
160 | {
161 | foreach ($this->values as $value) {
162 | $value->response = $this;
163 | }
164 |
165 | $this->values->setDatabase($this->db);
166 | $this->values->save();
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Inquisition/ImageDelete.php:
--------------------------------------------------------------------------------
1 | ui->getWidget('confirmation_form');
26 | $form->addHiddenField('id', $id);
27 | }
28 |
29 | public function setItems($items, $extended_selected = false)
30 | {
31 | parent::setItems($items, $extended_selected);
32 |
33 | $sql = sprintf(
34 | 'select Image.* from Image where id in (%s)',
35 | $this->getItemList('integer')
36 | );
37 |
38 | $this->images = SwatDB::query(
39 | $this->app->db,
40 | $sql,
41 | SwatDBClassMap::get(InquisitionQuestionImageWrapper::class)
42 | );
43 | }
44 |
45 | public function setInquisition(?InquisitionInquisition $inquisition = null)
46 | {
47 | if ($inquisition instanceof InquisitionInquisition) {
48 | $this->inquisition = $inquisition;
49 |
50 | $form = $this->ui->getWidget('confirmation_form');
51 | $form->addHiddenField('inquisition_id', $this->inquisition->id);
52 | }
53 | }
54 |
55 | abstract protected function getImageWrapper();
56 |
57 | // init phase
58 |
59 | protected function initInternal()
60 | {
61 | parent::initInternal();
62 |
63 | $form = $this->ui->getWidget('confirmation_form');
64 | $id = $form->getHiddenField('id');
65 | if ($id != '') {
66 | $this->setId($id);
67 | }
68 |
69 | $inquisition_id = $form->getHiddenField('inquisition_id');
70 | if ($inquisition_id != '') {
71 | $inquisition = $this->loadInquisition($inquisition_id);
72 | $this->setInquisition($inquisition);
73 | }
74 | }
75 |
76 | protected function loadInquisition($inquisition_id)
77 | {
78 | $inquisition = SwatDBClassMap::new(InquisitionInquisition::class);
79 | $inquisition->setDatabase($this->app->db);
80 |
81 | if (!$inquisition->load($inquisition_id)) {
82 | throw new AdminNotFoundException(
83 | sprintf(
84 | 'Inquisition with id ‘%s’ not found.',
85 | $inquisition_id
86 | )
87 | );
88 | }
89 |
90 | return $inquisition;
91 | }
92 |
93 | protected function getUiXml()
94 | {
95 | return __DIR__ . '/image-delete.xml';
96 | }
97 |
98 | // process phase
99 |
100 | protected function processDBData(): void
101 | {
102 | parent::processDBData();
103 |
104 | $delete_count = 0;
105 |
106 | foreach ($this->images as $image) {
107 | $image->setFileBase('../images');
108 | $image->delete();
109 |
110 | $delete_count++;
111 | }
112 |
113 | $this->app->messages->add(
114 | new SwatMessage(
115 | sprintf(
116 | Inquisition::ngettext(
117 | 'One image has been deleted.',
118 | '%s images have been deleted.',
119 | $delete_count
120 | ),
121 | $delete_count
122 | )
123 | )
124 | );
125 | }
126 |
127 | protected function relocate()
128 | {
129 | AdminDBConfirmation::relocate();
130 | }
131 |
132 | // build phase
133 |
134 | protected function buildInternal()
135 | {
136 | parent::buildInternal();
137 |
138 | $store = new SwatTableStore();
139 | foreach ($this->images as $image) {
140 | $ds = new SwatDetailsStore();
141 |
142 | $ds->image = $image;
143 |
144 | $store->add($ds);
145 | }
146 |
147 | $delete_view = $this->ui->getWidget('delete_view');
148 | $delete_view->model = $store;
149 |
150 | $message = $this->ui->getWidget('confirmation_message');
151 | $message->content_type = 'text/xml';
152 | $message->content = sprintf(
153 | '%s',
154 | Inquisition::ngettext(
155 | 'Are you sure you want to delete the following image?',
156 | 'Are you sure you want to delete the following images?',
157 | count($this->images)
158 | )
159 | );
160 | }
161 |
162 | protected function buildForm()
163 | {
164 | parent::buildForm();
165 |
166 | $yes_button = $this->ui->getWidget('yes_button');
167 | $yes_button->title = Inquisition::_('Delete');
168 | }
169 |
170 | protected function buildNavBar()
171 | {
172 | parent::buildNavBar();
173 |
174 | if ($this->inquisition instanceof InquisitionInquisition) {
175 | $this->navbar->createEntry(
176 | $this->inquisition->title,
177 | sprintf(
178 | 'Inquisition/Details?id=%s',
179 | $this->inquisition->id
180 | )
181 | );
182 | }
183 | }
184 |
185 | protected function getLinkSuffix()
186 | {
187 | $suffix = null;
188 | if ($this->inquisition instanceof InquisitionInquisition) {
189 | $suffix = sprintf(
190 | '&inquisition=%s',
191 | $this->inquisition->id
192 | );
193 | }
194 |
195 | return $suffix;
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Option/Order.php:
--------------------------------------------------------------------------------
1 | initQuestion();
28 | $this->initInquisition();
29 | }
30 |
31 | protected function initQuestion()
32 | {
33 | $id = SiteApplication::initVar('id');
34 |
35 | if ($id == '') {
36 | throw new AdminNotFoundException(
37 | 'No question id specified.'
38 | );
39 | }
40 |
41 | if (is_numeric($id)) {
42 | $id = intval($id);
43 | }
44 |
45 | $this->question = SwatDBClassMap::new(InquisitionQuestion::class);
46 | $this->question->setDatabase($this->app->db);
47 |
48 | if (!$this->question->load($id)) {
49 | throw new AdminNotFoundException(
50 | sprintf(
51 | 'A question with the id of “%s” does not exist',
52 | $id
53 | )
54 | );
55 | }
56 | }
57 |
58 | protected function initInquisition()
59 | {
60 | $inquisition_id = SiteApplication::initVar('inquisition');
61 |
62 | if ($inquisition_id !== null) {
63 | $this->inquisition = $this->loadInquisition($inquisition_id);
64 | }
65 | }
66 |
67 | protected function loadInquisition($inquisition_id)
68 | {
69 | $inquisition = SwatDBClassMap::new(InquisitionInquisition::class);
70 | $inquisition->setDatabase($this->app->db);
71 |
72 | if (!$inquisition->load($inquisition_id)) {
73 | throw new AdminNotFoundException(
74 | sprintf(
75 | 'Inquisition with id ‘%s’ not found.',
76 | $inquisition_id
77 | )
78 | );
79 | }
80 |
81 | return $inquisition;
82 | }
83 |
84 | // process phase
85 |
86 | protected function saveIndex($id, $index)
87 | {
88 | SwatDB::updateColumn(
89 | $this->app->db,
90 | 'InquisitionQuestionOption',
91 | 'integer:displayorder',
92 | $index,
93 | 'integer:id',
94 | [$id]
95 | );
96 | }
97 |
98 | protected function getUpdatedMessage()
99 | {
100 | return new SwatMessage(
101 | Inquisition::_('Option order has been updated.')
102 | );
103 | }
104 |
105 | protected function relocate()
106 | {
107 | $this->app->relocate(
108 | sprintf(
109 | 'Question/Details?id=%s%s',
110 | $this->question->id,
111 | $this->getLinkSuffix()
112 | )
113 | );
114 | }
115 |
116 | // build phase
117 |
118 | protected function loadData()
119 | {
120 | $sum = 0;
121 | $order_widget = $this->ui->getWidget('order');
122 |
123 | foreach ($this->question->options as $option) {
124 | $sum += $option->displayorder;
125 |
126 | $order_widget->addOption(
127 | $option->id,
128 | $option->title,
129 | 'text/xml'
130 | );
131 | }
132 |
133 | $options_list = $this->ui->getWidget('options');
134 | $options_list->value = ($sum == 0) ? 'auto' : 'custom';
135 | }
136 |
137 | protected function buildInternal()
138 | {
139 | $this->ui->getWidget('order_frame')->title = $this->getTitle();
140 |
141 | $this->ui->getWidget('order')->width = '500px';
142 | $this->ui->getWidget('order')->height = '200px';
143 |
144 | parent::buildInternal();
145 | }
146 |
147 | protected function buildForm()
148 | {
149 | parent::buildForm();
150 |
151 | $form = $this->ui->getWidget('order_form');
152 | $form->addHiddenField('id', $this->question->id);
153 |
154 | if ($this->inquisition instanceof InquisitionInquisition) {
155 | $form->addHiddenField('inquisition', $this->inquisition->id);
156 | }
157 | }
158 |
159 | protected function buildNavBar()
160 | {
161 | parent::buildNavBar();
162 |
163 | $this->navbar->popEntry();
164 |
165 | if ($this->inquisition instanceof InquisitionInquisition) {
166 | $this->navbar->createEntry(
167 | $this->inquisition->title,
168 | sprintf(
169 | 'Inquisition/Details?id=%s',
170 | $this->inquisition->id
171 | )
172 | );
173 | }
174 |
175 | $this->navbar->createEntry(
176 | $this->getQuestionTitle(),
177 | sprintf(
178 | 'Question/Details?id=%s%s',
179 | $this->question->id,
180 | $this->getLinkSuffix()
181 | )
182 | );
183 |
184 | $this->navbar->createEntry($this->getTitle());
185 | }
186 |
187 | protected function getQuestionTitle()
188 | {
189 | // TODO: Update this with some version of getPosition().
190 | return Inquisition::_('Question');
191 | }
192 |
193 | protected function getLinkSuffix()
194 | {
195 | $suffix = null;
196 | if ($this->inquisition instanceof InquisitionInquisition) {
197 | $suffix = sprintf(
198 | '&inquisition=%s',
199 | $this->inquisition->id
200 | );
201 | }
202 |
203 | return $suffix;
204 | }
205 |
206 | protected function getTitle()
207 | {
208 | return Inquisition::_('Change Option Order');
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/HintOrder.php:
--------------------------------------------------------------------------------
1 | initQuestion();
28 | $this->initInquisition();
29 | }
30 |
31 | protected function initQuestion()
32 | {
33 | $id = SiteApplication::initVar('id');
34 |
35 | if ($id == '') {
36 | throw new AdminNotFoundException(
37 | 'No question id specified.'
38 | );
39 | }
40 |
41 | if (is_numeric($id)) {
42 | $id = intval($id);
43 | }
44 |
45 | $this->question = SwatDBClassMap::new(InquisitionQuestion::class);
46 | $this->question->setDatabase($this->app->db);
47 |
48 | if (!$this->question->load($id)) {
49 | throw new AdminNotFoundException(
50 | sprintf(
51 | 'A question with the id of “%s” does not exist',
52 | $id
53 | )
54 | );
55 | }
56 | }
57 |
58 | protected function initInquisition()
59 | {
60 | $inquisition_id = SiteApplication::initVar('inquisition');
61 |
62 | if ($inquisition_id !== null) {
63 | $this->inquisition = $this->loadInquisition($inquisition_id);
64 | }
65 | }
66 |
67 | protected function loadInquisition($inquisition_id)
68 | {
69 | $inquisition = SwatDBClassMap::new(InquisitionInquisition::class);
70 | $inquisition->setDatabase($this->app->db);
71 |
72 | if (!$inquisition->load($inquisition_id)) {
73 | throw new AdminNotFoundException(
74 | sprintf(
75 | 'Inquisition with id ‘%s’ not found.',
76 | $inquisition_id
77 | )
78 | );
79 | }
80 |
81 | return $inquisition;
82 | }
83 |
84 | // process phase
85 |
86 | protected function saveIndex($id, $index)
87 | {
88 | SwatDB::updateColumn(
89 | $this->app->db,
90 | 'InquisitionQuestionHint',
91 | 'integer:displayorder',
92 | $index,
93 | 'integer:id',
94 | [$id]
95 | );
96 | }
97 |
98 | protected function getUpdatedMessage()
99 | {
100 | return new SwatMessage(Inquisition::_('Hint order has been updated.'));
101 | }
102 |
103 | protected function relocate()
104 | {
105 | $this->app->relocate(
106 | sprintf(
107 | 'Question/Details?id=%s%s',
108 | $this->question->id,
109 | $this->getLinkSuffix()
110 | )
111 | );
112 | }
113 |
114 | // build phase
115 |
116 | protected function loadData()
117 | {
118 | $sum = 0;
119 | $order_widget = $this->ui->getWidget('order');
120 |
121 | foreach ($this->question->hints as $hint) {
122 | $sum += $hint->displayorder;
123 |
124 | $order_widget->addOption(
125 | $hint->id,
126 | SwatString::condense($hint->bodytext, 50),
127 | 'text/xml'
128 | );
129 | }
130 |
131 | $options_list = $this->ui->getWidget('options');
132 | $options_list->value = ($sum == 0) ? 'auto' : 'custom';
133 | }
134 |
135 | protected function buildInternal()
136 | {
137 | $this->ui->getWidget('order_frame')->title = $this->getTitle();
138 |
139 | $this->ui->getWidget('order')->width = '500px';
140 | $this->ui->getWidget('order')->height = '200px';
141 |
142 | parent::buildInternal();
143 | }
144 |
145 | protected function buildForm()
146 | {
147 | parent::buildForm();
148 |
149 | $form = $this->ui->getWidget('order_form');
150 | $form->addHiddenField('id', $this->question->id);
151 |
152 | if ($this->inquisition instanceof InquisitionInquisition) {
153 | $form->addHiddenField('inquisition', $this->inquisition->id);
154 | }
155 | }
156 |
157 | protected function buildNavBar()
158 | {
159 | parent::buildNavBar();
160 |
161 | $this->navbar->popEntry();
162 |
163 | if ($this->inquisition instanceof InquisitionInquisition) {
164 | $this->navbar->createEntry(
165 | $this->inquisition->title,
166 | sprintf(
167 | 'Inquisition/Details?id=%s',
168 | $this->inquisition->id
169 | )
170 | );
171 | }
172 |
173 | $this->navbar->createEntry(
174 | $this->getQuestionTitle(),
175 | sprintf(
176 | 'Question/Details?id=%s%s',
177 | $this->question->id,
178 | $this->getLinkSuffix()
179 | )
180 | );
181 |
182 | $this->navbar->createEntry($this->getTitle());
183 | }
184 |
185 | protected function getQuestionTitle()
186 | {
187 | // TODO: Update this with some version of getPosition().
188 | return Inquisition::_('Question');
189 | }
190 |
191 | protected function getLinkSuffix()
192 | {
193 | $suffix = null;
194 | if ($this->inquisition instanceof InquisitionInquisition) {
195 | $suffix = sprintf(
196 | '&inquisition=%s',
197 | $this->inquisition->id
198 | );
199 | }
200 |
201 | return $suffix;
202 | }
203 |
204 | protected function getTitle()
205 | {
206 | return Inquisition::_('Change Hint Order');
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/CorrectOption.php:
--------------------------------------------------------------------------------
1 | initQuestion();
28 | $this->initInquisition();
29 |
30 | $this->ui->loadFromXML($this->getUiXml());
31 | }
32 |
33 | protected function initQuestion()
34 | {
35 | $this->question = SwatDBClassMap::new(InquisitionQuestion::class);
36 | $this->question->setDatabase($this->app->db);
37 |
38 | if ($this->id == '') {
39 | throw new AdminNotFoundException(
40 | 'Question id not provided.'
41 | );
42 | }
43 |
44 | if (!$this->question->load($this->id)) {
45 | throw new AdminNotFoundException(
46 | sprintf(
47 | 'Question with id ‘%s’ not found.',
48 | $this->id
49 | )
50 | );
51 | }
52 | }
53 |
54 | protected function initInquisition()
55 | {
56 | $inquisition_id = SiteApplication::initVar('inquisition');
57 |
58 | if ($inquisition_id !== null) {
59 | $this->inquisition = $this->loadInquisition($inquisition_id);
60 | }
61 | }
62 |
63 | protected function loadInquisition($inquisition_id)
64 | {
65 | $inquisition = SwatDBClassMap::new(InquisitionInquisition::class);
66 | $inquisition->setDatabase($this->app->db);
67 |
68 | if (!$inquisition->load($inquisition_id)) {
69 | throw new AdminNotFoundException(
70 | sprintf(
71 | 'Inquisition with id ‘%s’ not found.',
72 | $inquisition_id
73 | )
74 | );
75 | }
76 |
77 | return $inquisition;
78 | }
79 |
80 | protected function getUiXml()
81 | {
82 | return __DIR__ . '/correct-option.xml';
83 | }
84 |
85 | // process phase
86 |
87 | protected function saveDBData(): void
88 | {
89 | $values = $this->ui->getValues([
90 | 'correct_option',
91 | ]);
92 |
93 | $this->question->correct_option = $values['correct_option'];
94 | $this->question->save();
95 |
96 | $this->app->messages->add(
97 | new SwatMessage(
98 | Inquisition::_('Correct option has been updated.')
99 | )
100 | );
101 | }
102 |
103 | protected function relocate()
104 | {
105 | $uri = sprintf(
106 | 'Question/Details?id=%s',
107 | $this->question->id
108 | );
109 |
110 | if ($this->inquisition instanceof InquisitionInquisition) {
111 | $uri .= sprintf(
112 | '&inquisition=%s',
113 | $this->inquisition->id
114 | );
115 | }
116 |
117 | $this->app->relocate($uri);
118 | }
119 |
120 | // build phase
121 |
122 | protected function buildInternal()
123 | {
124 | parent::buildInternal();
125 |
126 | $list = $this->ui->getWidget('correct_option');
127 |
128 | foreach ($this->question->options as $option) {
129 | $list->addOption(
130 | $option->id,
131 | sprintf(
132 | '%s. %s',
133 | $option->position,
134 | $option->title
135 | )
136 | );
137 | }
138 | }
139 |
140 | protected function buildForm()
141 | {
142 | parent::buildForm();
143 |
144 | if ($this->inquisition instanceof InquisitionInquisition) {
145 | $form = $this->ui->getWidget('edit_form');
146 | $form->addHiddenField('inquisition', $this->inquisition->id);
147 | }
148 | }
149 |
150 | protected function loadDBData()
151 | {
152 | if ($this->question->correct_option instanceof InquisitionQuestionOption) {
153 | $this->ui->setValues(
154 | [
155 | 'correct_option' => $this->question->correct_option->id,
156 | ]
157 | );
158 | }
159 | }
160 |
161 | protected function buildNavBar()
162 | {
163 | parent::buildNavBar();
164 |
165 | $this->navbar->popEntry();
166 |
167 | if ($this->inquisition instanceof InquisitionInquisition) {
168 | $this->navbar->createEntry(
169 | $this->inquisition->title,
170 | sprintf(
171 | 'Inquisition/Details?id=%s',
172 | $this->inquisition->id
173 | )
174 | );
175 | }
176 |
177 | $this->navbar->createEntry(
178 | $this->getQuestionTitle(),
179 | sprintf(
180 | 'Question/Details?id=%s%s',
181 | $this->question->id,
182 | $this->getLinkSuffix()
183 | )
184 | );
185 |
186 | $this->navbar->createEntry(Inquisition::_('Edit Correct Question'));
187 | }
188 |
189 | protected function getQuestionTitle()
190 | {
191 | // TODO: Update this with some version of getPosition().
192 | return Inquisition::_('Question');
193 | }
194 |
195 | protected function getLinkSuffix()
196 | {
197 | $suffix = null;
198 | if ($this->inquisition instanceof InquisitionInquisition) {
199 | $suffix = sprintf(
200 | '&inquisition=%s',
201 | $this->inquisition->id
202 | );
203 | }
204 |
205 | return $suffix;
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/Inquisition/InquisitionQuestionImporter.php:
--------------------------------------------------------------------------------
1 | app = $app;
17 | }
18 |
19 | // questions
20 |
21 | public function importQuestions(InquisitionFileParser $file)
22 | {
23 | $questions = [];
24 |
25 | while (!$file->eof()) {
26 | $question = SwatDBClassMap::new(InquisitionQuestion::class);
27 | $question->setDatabase($this->app->db);
28 | $this->importQuestion($question, $file);
29 |
30 | $questions[] = $question;
31 | }
32 |
33 | return $questions;
34 | }
35 |
36 | protected function importQuestion(
37 | InquisitionQuestion $question,
38 | InquisitionFileParser $file
39 | ) {
40 | $line = $file->line();
41 | $row = $file->row();
42 |
43 | $this->importQuestionProperties($question, $file);
44 | $this->importOptions($question, $file);
45 |
46 | if (count($question->options) < 2) {
47 | throw new InquisitionImportException(
48 | sprintf(
49 | Inquisition::_(
50 | 'Question on line %s (CSV row %s) must have at ' .
51 | 'least two options.'
52 | ),
53 | $line,
54 | $row
55 | ),
56 | 0,
57 | $file
58 | );
59 | }
60 |
61 | if (!$question->correct_option instanceof InquisitionQuestionOption) {
62 | throw new InquisitionImportException(
63 | sprintf(
64 | Inquisition::_(
65 | 'Question on line %s (CSV row %s) must have a ' .
66 | 'correct answer.'
67 | ),
68 | $line,
69 | $row
70 | ),
71 | 0,
72 | $file
73 | );
74 | }
75 | }
76 |
77 | protected function importQuestionProperties(
78 | InquisitionQuestion $question,
79 | InquisitionFileParser $file
80 | ) {
81 | $line = $file->line();
82 | $row = $file->row();
83 | $data = $file->current();
84 |
85 | $question->required = true;
86 | $question->question_type = InquisitionQuestion::TYPE_RADIO_LIST;
87 |
88 | if (!isset($data[0]) || $data[0] == '') {
89 | throw new InquisitionImportException(
90 | sprintf(
91 | Inquisition::_(
92 | 'Line %s (CSV row %s) has no question text.'
93 | ),
94 | $line,
95 | $row
96 | ),
97 | 0,
98 | $file
99 | );
100 | }
101 |
102 | $question->bodytext = $data[0];
103 | }
104 |
105 | // question options
106 |
107 | protected function importOptions(
108 | InquisitionQuestion $question,
109 | InquisitionFileParser $file
110 | ) {
111 | $file->next();
112 |
113 | $option_class = SwatDBClassMap::get(InquisitionQuestionOption::class);
114 |
115 | while (!$file->eof() && $this->isOptionLine($file)) {
116 | $option = new $option_class();
117 | $option->setDatabase($this->app->db);
118 | $this->importOption($option, $file);
119 |
120 | $previous_option = $question->options->getLast();
121 |
122 | if ($previous_option instanceof $option_class) {
123 | $option->displayorder = $previous_option->displayorder + 1;
124 | } else {
125 | $option->displayorder = 1;
126 | }
127 |
128 | $question->options->add($option);
129 |
130 | if ($this->isCorrectOptionLine($file)) {
131 | $line = $file->line();
132 | $row = $file->row();
133 |
134 | if ($question->correct_option instanceof $option_class) {
135 | throw new InquisitionImportException(
136 | sprintf(
137 | Inquisition::_(
138 | 'Line %s (CSV row %s) contains a second ' .
139 | 'correct answer.'
140 | ),
141 | $line,
142 | $row
143 | ),
144 | 0,
145 | $file
146 | );
147 | }
148 |
149 | $question->correct_option = $option;
150 | }
151 |
152 | $file->next();
153 | }
154 | }
155 |
156 | protected function importOption(
157 | InquisitionQuestionOption $option,
158 | InquisitionFileParser $file
159 | ) {
160 | $line = $file->line();
161 | $row = $file->row();
162 | $data = $file->current();
163 |
164 | if (!isset($data[1]) || $data[1] == '') {
165 | throw new InquisitionImportException(
166 | sprintf(
167 | Inquisition::_('Line %s (CSV row %s) has no option text.'),
168 | $line,
169 | $row
170 | ),
171 | 0,
172 | $file
173 | );
174 | }
175 |
176 | $option->title = $data[1];
177 | }
178 |
179 | // helper methods
180 |
181 | protected function isOptionLine(InquisitionFileParser $file)
182 | {
183 | $data = $file->current();
184 |
185 | return isset($data[0]) && $data[0] === '';
186 | }
187 |
188 | protected function isCorrectOptionLine(InquisitionFileParser $file)
189 | {
190 | $data = $file->current();
191 |
192 | return isset($data[2]) && mb_strtolower(trim($data[2])) === 'x';
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/Inquisition/dataobjects/InquisitionInquisition.php:
--------------------------------------------------------------------------------
1 | checkDB();
46 |
47 | $sql = sprintf(
48 | 'select * from InquisitionResponse
49 | where account = %s and inquisition = %s',
50 | $this->db->quote($account->id, 'integer'),
51 | $this->db->quote($this->id, 'integer')
52 | );
53 |
54 | $wrapper = $this->getResolvedResponseWrapperClass();
55 | $response = SwatDB::query($this->db, $sql, $wrapper)->getFirst();
56 |
57 | if ($response instanceof InquisitionResponse) {
58 | $response->inquisition = $this;
59 | }
60 |
61 | return $response;
62 | }
63 |
64 | public function addQuestionDependency(
65 | InquisitionInquisitionQuestionBinding $dependent_question_binding,
66 | InquisitionInquisitionQuestionBinding $question_binding,
67 | InquisitionQuestionOption $option
68 | ) {
69 | $this->question_dependencies[] = [
70 | 'dependent_question_binding' => $dependent_question_binding,
71 | 'question_binding' => $question_binding,
72 | 'option' => $option,
73 | ];
74 | }
75 |
76 | protected function init()
77 | {
78 | $this->table = 'Inquisition';
79 | $this->id_field = 'integer:id';
80 | $this->registerDateProperty('createdate');
81 | }
82 |
83 | protected function getSerializableSubDataObjects()
84 | {
85 | return array_merge(
86 | parent::getSerializableSubDataObjects(),
87 | [
88 | 'question_bindings',
89 | 'visible_question_bindings',
90 | ]
91 | );
92 | }
93 |
94 | protected function getResolvedResponseWrapperClass()
95 | {
96 | return SwatDBClassMap::get($this->getResponseWrapperClass());
97 | }
98 |
99 | protected function getResponseWrapperClass()
100 | {
101 | return InquisitionResponseWrapper::class;
102 | }
103 |
104 | // saver methods
105 |
106 | protected function saveQuestionBindings()
107 | {
108 | foreach ($this->question_bindings as $question_binding) {
109 | $question_binding->inquisition = $this;
110 | }
111 |
112 | $this->question_bindings->setDatabase($this->db);
113 | $this->question_bindings->save();
114 |
115 | foreach ($this->question_dependencies as $question_dependency) {
116 | $dependent_binding = $question_dependency['dependent_question_binding'];
117 | $binding = $question_dependency['question_binding'];
118 | $option = $question_dependency['option'];
119 |
120 | SwatDB::exec(
121 | $this->db,
122 | sprintf(
123 | 'insert into InquisitionQuestionDependency
124 | (dependent_question_binding, question_binding, option)
125 | values
126 | (%s, %s, %s)
127 | ',
128 | $this->db->quote($dependent_binding->id, 'integer'),
129 | $this->db->quote($binding->id, 'integer'),
130 | $this->db->quote($option->id, 'integer')
131 | )
132 | );
133 | }
134 | }
135 |
136 | // loader methods
137 |
138 | protected function loadResponses()
139 | {
140 | $sql = sprintf(
141 | 'select * from InquisitionResponse
142 | where inquisition = %s
143 | order by createdate, id',
144 | $this->db->quote($this->id, 'integer')
145 | );
146 |
147 | return SwatDB::query(
148 | $this->db,
149 | $sql,
150 | $this->getResolvedResponseWrapperClass()
151 | );
152 | }
153 |
154 | protected function loadQuestionBindings()
155 | {
156 | $sql = sprintf(
157 | 'select * from InquisitionInquisitionQuestionBinding
158 | where inquisition = %s order by displayorder, id',
159 | $this->db->quote($this->id, 'integer')
160 | );
161 |
162 | return SwatDB::query(
163 | $this->db,
164 | $sql,
165 | SwatDBClassMap::get(InquisitionInquisitionQuestionBindingWrapper::class)
166 | );
167 | }
168 |
169 | protected function loadVisibleQuestionBindings()
170 | {
171 | $sql = sprintf(
172 | 'select InquisitionInquisitionQuestionBinding.*
173 | from InquisitionInquisitionQuestionBinding
174 | inner join VisibleInquisitionQuestionView
175 | on InquisitionInquisitionQuestionBinding.question =
176 | VisibleInquisitionQuestionView.question
177 | where InquisitionInquisitionQuestionBinding.inquisition = %s
178 | order by InquisitionInquisitionQuestionBinding.displayorder,
179 | InquisitionInquisitionQuestionBinding.id',
180 | $this->db->quote($this->id, 'integer')
181 | );
182 |
183 | return SwatDB::query(
184 | $this->db,
185 | $sql,
186 | SwatDBClassMap::get(InquisitionInquisitionQuestionBindingWrapper::class)
187 | );
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Question/ImageOrder.php:
--------------------------------------------------------------------------------
1 | initQuestion();
28 | $this->initInquisition();
29 | }
30 |
31 | protected function initQuestion()
32 | {
33 | $id = SiteApplication::initVar('id');
34 |
35 | if ($id == '') {
36 | throw new AdminNotFoundException(
37 | Inquisition::_('No question id specified.')
38 | );
39 | }
40 |
41 | if (is_numeric($id)) {
42 | $id = intval($id);
43 | }
44 |
45 | $this->question = SwatDBClassMap::new(InquisitionQuestion::class);
46 | $this->question->setDatabase($this->app->db);
47 |
48 | if (!$this->question->load($id)) {
49 | throw new AdminNotFoundException(
50 | sprintf(
51 | 'A question with the id of “%s” does not exist',
52 | $id
53 | )
54 | );
55 | }
56 | }
57 |
58 | protected function initInquisition()
59 | {
60 | $inquisition_id = SiteApplication::initVar('inquisition');
61 |
62 | if ($inquisition_id !== null) {
63 | $this->inquisition = $this->loadInquisition($inquisition_id);
64 | }
65 | }
66 |
67 | protected function loadInquisition($inquisition_id)
68 | {
69 | $inquisition = SwatDBClassMap::new(InquisitionInquisition::class);
70 | $inquisition->setDatabase($this->app->db);
71 |
72 | if (!$inquisition->load($inquisition_id)) {
73 | throw new AdminNotFoundException(
74 | sprintf(
75 | 'Inquisition with id ‘%s’ not found.',
76 | $inquisition_id
77 | )
78 | );
79 | }
80 |
81 | return $inquisition;
82 | }
83 |
84 | // process phase
85 |
86 | protected function saveIndex($id, $index)
87 | {
88 | SwatDB::updateColumn(
89 | $this->app->db,
90 | 'InquisitionQuestionImageBinding',
91 | 'integer:displayorder',
92 | $index,
93 | 'integer:image',
94 | [$id]
95 | );
96 | }
97 |
98 | protected function getUpdatedMessage()
99 | {
100 | return new SwatMessage(Inquisition::_('Image order has been updated.'));
101 | }
102 |
103 | protected function relocate()
104 | {
105 | $this->app->relocate(
106 | sprintf(
107 | 'Question/Details?id=%s%s',
108 | $this->question->id,
109 | $this->getLinkSuffix()
110 | )
111 | );
112 | }
113 |
114 | // build phase
115 |
116 | protected function loadData()
117 | {
118 | $order_widget = $this->ui->getWidget('order');
119 |
120 | foreach ($this->question->images as $image) {
121 | $order_widget->addOption(
122 | $image->id,
123 | strval($image->getImgTag('thumb', '../')),
124 | 'text/xml'
125 | );
126 | }
127 |
128 | $sql = sprintf(
129 | 'select sum(displayorder) from
130 | InquisitionQuestionImageBinding where question = %s',
131 | $this->question->id
132 | );
133 |
134 | $sum = SwatDB::queryOne($this->app->db, $sql, 'integer');
135 |
136 | $options_list = $this->ui->getWidget('options');
137 | $options_list->value = ($sum == 0) ? 'auto' : 'custom';
138 | }
139 |
140 | protected function buildInternal()
141 | {
142 | $this->ui->getWidget('order_frame')->title = $this->getTitle();
143 | $this->ui->getWidget('order')->width = '150px';
144 | $this->ui->getWidget('order')->height = '300px';
145 |
146 | parent::buildInternal();
147 | }
148 |
149 | protected function buildForm()
150 | {
151 | parent::buildForm();
152 |
153 | $form = $this->ui->getWidget('order_form');
154 | $form->addHiddenField('id', $this->question->id);
155 |
156 | if ($this->inquisition instanceof InquisitionInquisition) {
157 | $form->addHiddenField('inquisition', $this->inquisition->id);
158 | }
159 | }
160 |
161 | protected function buildNavBar()
162 | {
163 | parent::buildNavBar();
164 |
165 | $this->navbar->popEntry();
166 |
167 | if ($this->inquisition instanceof InquisitionInquisition) {
168 | $this->navbar->createEntry(
169 | $this->inquisition->title,
170 | sprintf(
171 | 'Inquisition/Details?id=%s',
172 | $this->inquisition->id
173 | )
174 | );
175 | }
176 |
177 | $this->navbar->createEntry(
178 | $this->getQuestionTitle(),
179 | sprintf(
180 | 'Question/Details?id=%s%s',
181 | $this->question->id,
182 | $this->getLinkSuffix()
183 | )
184 | );
185 |
186 | $this->navbar->createEntry($this->getTitle());
187 | }
188 |
189 | protected function getQuestionTitle()
190 | {
191 | // TODO: Update this with some version of getPosition().
192 | return Inquisition::_('Question');
193 | }
194 |
195 | protected function getLinkSuffix()
196 | {
197 | $suffix = null;
198 | if ($this->inquisition instanceof InquisitionInquisition) {
199 | $suffix = sprintf(
200 | '&inquisition=%s',
201 | $this->inquisition->id
202 | );
203 | }
204 |
205 | return $suffix;
206 | }
207 |
208 | protected function getTitle()
209 | {
210 | return Inquisition::_('Change Image Order');
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Option/ImageOrder.php:
--------------------------------------------------------------------------------
1 | initOption();
28 | $this->initInquisition();
29 | }
30 |
31 | protected function initOption()
32 | {
33 | $id = SiteApplication::initVar('id');
34 |
35 | if ($id == '') {
36 | throw new AdminNotFoundException(
37 | Inquisition::_('No option id specified.')
38 | );
39 | }
40 |
41 | if (is_numeric($id)) {
42 | $id = intval($id);
43 | }
44 |
45 | $this->option = SwatDBClassMap::new(InquisitionQuestionOption::class);
46 | $this->option->setDatabase($this->app->db);
47 |
48 | if (!$this->option->load($id)) {
49 | throw new AdminNotFoundException(
50 | sprintf(
51 | Inquisition::_(
52 | 'An option with the id of “%s” does not exist'
53 | ),
54 | $id
55 | )
56 | );
57 | }
58 | }
59 |
60 | protected function initInquisition()
61 | {
62 | $inquisition_id = SiteApplication::initVar('inquisition');
63 |
64 | if ($inquisition_id !== null) {
65 | $this->inquisition = $this->loadInquisition($inquisition_id);
66 | }
67 | }
68 |
69 | protected function loadInquisition($inquisition_id)
70 | {
71 | $inquisition = SwatDBClassMap::new(InquisitionInquisition::class);
72 | $inquisition->setDatabase($this->app->db);
73 |
74 | if (!$inquisition->load($inquisition_id)) {
75 | throw new AdminNotFoundException(
76 | sprintf(
77 | 'Inquisition with id ‘%s’ not found.',
78 | $inquisition_id
79 | )
80 | );
81 | }
82 |
83 | return $inquisition;
84 | }
85 |
86 | // process phase
87 |
88 | protected function saveIndex($id, $index)
89 | {
90 | SwatDB::updateColumn(
91 | $this->app->db,
92 | 'InquisitionQuestionOptionImageBinding',
93 | 'integer:displayorder',
94 | $index,
95 | 'integer:image',
96 | [$id]
97 | );
98 | }
99 |
100 | protected function getUpdatedMessage()
101 | {
102 | return new SwatMessage(Inquisition::_('Image order has been updated.'));
103 | }
104 |
105 | protected function relocate()
106 | {
107 | $this->app->relocate(
108 | sprintf(
109 | 'Option/Details?id=%s%s',
110 | $this->option->id,
111 | $this->getLinkSuffix()
112 | )
113 | );
114 | }
115 |
116 | // build phase
117 |
118 | protected function loadData()
119 | {
120 | $order_widget = $this->ui->getWidget('order');
121 |
122 | foreach ($this->option->images as $image) {
123 | $order_widget->addOption(
124 | $image->id,
125 | strval($image->getImgTag('thumb', '../')),
126 | 'text/xml'
127 | );
128 | }
129 |
130 | $sql = sprintf(
131 | 'select sum(displayorder)
132 | from InquisitionQuestionOptionImageBinding
133 | where question_option = %s',
134 | $this->option->id
135 | );
136 |
137 | $sum = SwatDB::queryOne($this->app->db, $sql, 'integer');
138 |
139 | $options_list = $this->ui->getWidget('options');
140 | $options_list->value = ($sum == 0) ? 'auto' : 'custom';
141 | }
142 |
143 | protected function buildInternal()
144 | {
145 | $this->ui->getWidget('order_frame')->title = $this->getTitle();
146 | $this->ui->getWidget('order')->width = '150px';
147 | $this->ui->getWidget('order')->height = '300px';
148 |
149 | parent::buildInternal();
150 | }
151 |
152 | protected function buildForm()
153 | {
154 | parent::buildForm();
155 |
156 | $form = $this->ui->getWidget('order_form');
157 | $form->addHiddenField('id', $this->option->id);
158 |
159 | if ($this->inquisition instanceof InquisitionInquisition) {
160 | $form->addHiddenField('inquisition', $this->inquisition->id);
161 | }
162 | }
163 |
164 | protected function buildNavBar()
165 | {
166 | parent::buildNavBar();
167 |
168 | $this->navbar->popEntry();
169 |
170 | if ($this->inquisition instanceof InquisitionInquisition) {
171 | $this->navbar->createEntry(
172 | $this->inquisition->title,
173 | sprintf(
174 | 'Inquisition/Details?id=%s',
175 | $this->inquisition->id
176 | )
177 | );
178 | }
179 |
180 | $this->navbar->createEntry(
181 | $this->getQuestionTitle(),
182 | sprintf(
183 | 'Question/Details?id=%s%s',
184 | $this->option->question->id,
185 | $this->getLinkSuffix()
186 | )
187 | );
188 |
189 | $this->navbar->createEntry(
190 | $this->getOptionTitle(),
191 | sprintf(
192 | 'Option/Details?id=%s%s',
193 | $this->option->id,
194 | $this->getLinkSuffix()
195 | )
196 | );
197 |
198 | $this->navbar->createEntry($this->getTitle());
199 | }
200 |
201 | protected function getOptionTitle()
202 | {
203 | return sprintf(
204 | Inquisition::_('Option %s'),
205 | $this->option->position
206 | );
207 | }
208 |
209 | protected function getQuestionTitle()
210 | {
211 | // TODO: Update this with some version of getPosition().
212 | return Inquisition::_('Question');
213 | }
214 |
215 | protected function getLinkSuffix()
216 | {
217 | $suffix = null;
218 | if ($this->inquisition instanceof InquisitionInquisition) {
219 | $suffix = sprintf(
220 | '&inquisition=%s',
221 | $this->inquisition->id
222 | );
223 | }
224 |
225 | return $suffix;
226 | }
227 |
228 | protected function getTitle()
229 | {
230 | return Inquisition::_('Change Image Order');
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/Inquisition/admin/components/Option/Delete.php:
--------------------------------------------------------------------------------
1 | question = SwatDBClassMap::new(InquisitionQuestion::class);
26 | $this->question->setDatabase($this->app->db);
27 |
28 | if ($id == '') {
29 | throw new AdminNotFoundException('Question id not provided.');
30 | }
31 |
32 | if (!$this->question->load($id)) {
33 | throw new AdminNotFoundException(
34 | sprintf('Question with id ‘%s’ not found.', $id)
35 | );
36 | }
37 |
38 | $form = $this->ui->getWidget('confirmation_form');
39 | $form->addHiddenField('id', $id);
40 | }
41 |
42 | public function setInquisition(?InquisitionInquisition $inquisition = null)
43 | {
44 | if ($inquisition instanceof InquisitionInquisition) {
45 | $this->inquisition = $inquisition;
46 |
47 | $form = $this->ui->getWidget('confirmation_form');
48 | $form->addHiddenField('inquisition_id', $this->inquisition->id);
49 | }
50 | }
51 |
52 | // init phase
53 |
54 | protected function initInternal()
55 | {
56 | parent::initInternal();
57 |
58 | $form = $this->ui->getWidget('confirmation_form');
59 | $id = $form->getHiddenField('id');
60 | if ($id != '') {
61 | $this->setId($id);
62 | }
63 |
64 | $inquisition_id = $form->getHiddenField('inquisition_id');
65 | if ($inquisition_id != '') {
66 | $inquisition = $this->loadInquisition($inquisition_id);
67 | $this->setInquisition($inquisition);
68 | }
69 | }
70 |
71 | protected function loadInquisition($inquisition_id)
72 | {
73 | $inquisition = SwatDBClassMap::new(InquisitionInquisition::class);
74 | $inquisition->setDatabase($this->app->db);
75 |
76 | if (!$inquisition->load($inquisition_id)) {
77 | throw new AdminNotFoundException(
78 | sprintf(
79 | 'Inquisition with id ‘%s’ not found.',
80 | $inquisition_id
81 | )
82 | );
83 | }
84 |
85 | return $inquisition;
86 | }
87 |
88 | // process phase
89 |
90 | protected function processDBData(): void
91 | {
92 | parent::processDBData();
93 |
94 | $locale = SwatI18NLocale::get();
95 |
96 | $sql = sprintf(
97 | 'delete from InquisitionQuestionOption where id in (%s)',
98 | $this->getItemList('integer')
99 | );
100 |
101 | $num = SwatDB::exec($this->app->db, $sql);
102 |
103 | $this->app->messages->add(
104 | new SwatMessage(
105 | sprintf(
106 | Inquisition::ngettext(
107 | 'One option has been deleted.',
108 | '%s options have been deleted.',
109 | $num
110 | ),
111 | $locale->formatNumber($num)
112 | )
113 | )
114 | );
115 | }
116 |
117 | protected function relocate()
118 | {
119 | AdminDBConfirmation::relocate();
120 | }
121 |
122 | // build phase
123 |
124 | protected function buildInternal()
125 | {
126 | parent::buildInternal();
127 |
128 | $item_list = $this->getItemList('integer');
129 |
130 | $dep = new AdminListDependency();
131 | $dep->setTitle(
132 | Inquisition::_('option'),
133 | Inquisition::_('options')
134 | );
135 |
136 | $dep->entries = AdminListDependency::queryEntries(
137 | $this->app->db,
138 | 'InquisitionQuestionOption',
139 | 'id',
140 | null,
141 | 'text:title',
142 | 'displayorder, id',
143 | 'id in (' . $item_list . ')',
144 | AdminDependency::DELETE
145 | );
146 |
147 | // check images dependencies
148 | $dep_images = new AdminSummaryDependency();
149 | $dep_images->setTitle(
150 | Inquisition::_('image'),
151 | Inquisition::_('images')
152 | );
153 |
154 | $dep_images->summaries = AdminSummaryDependency::querySummaries(
155 | $this->app->db,
156 | 'InquisitionQuestionOptionImageBinding',
157 | 'integer:image',
158 | 'integer:question_option',
159 | 'question_option in (' . $item_list . ')',
160 | AdminDependency::DELETE
161 | );
162 |
163 | $dep->addDependency($dep_images);
164 |
165 | foreach ($dep->entries as $entry) {
166 | $entry->title = SwatString::condense($entry->title);
167 | }
168 |
169 | $message = $this->ui->getWidget('confirmation_message');
170 | $message->content = $dep->getMessage();
171 | $message->content_type = 'text/xml';
172 |
173 | if ($dep->getStatusLevelCount(AdminDependency::DELETE) == 0) {
174 | $this->switchToCancelButton();
175 | }
176 | }
177 |
178 | protected function buildNavBar()
179 | {
180 | parent::buildNavBar();
181 |
182 | $this->navbar->popEntry();
183 |
184 | if ($this->inquisition instanceof InquisitionInquisition) {
185 | $this->navbar->createEntry(
186 | $this->inquisition->title,
187 | sprintf(
188 | 'Inquisition/Details?id=%s',
189 | $this->inquisition->id
190 | )
191 | );
192 | }
193 |
194 | $this->navbar->createEntry(
195 | $this->getQuestionTitle(),
196 | sprintf(
197 | 'Question/Details?id=%s%s',
198 | $this->question->id,
199 | $this->getLinkSuffix()
200 | )
201 | );
202 |
203 | $this->navbar->createEntry(
204 | Inquisition::ngettext(
205 | 'Delete Option',
206 | 'Delete Options',
207 | $this->getItemCount()
208 | )
209 | );
210 | }
211 |
212 | protected function getQuestionTitle()
213 | {
214 | // TODO: Update this with some version of getPosition().
215 | return Inquisition::_('Question');
216 | }
217 |
218 | protected function getLinkSuffix()
219 | {
220 | $suffix = null;
221 | if ($this->inquisition instanceof InquisitionInquisition) {
222 | $suffix = sprintf(
223 | '&inquisition=%s',
224 | $this->inquisition->id
225 | );
226 | }
227 |
228 | return $suffix;
229 | }
230 | }
231 |
--------------------------------------------------------------------------------