Noel Chiasson
3 years ago
committed by
GitHub
60 changed files with 6626 additions and 13 deletions
@ -1,41 +1,110 @@ |
|||||||
# DGI Fixity |
# Fixity |
||||||
|
|
||||||
## Introduction |
## Introduction |
||||||
|
|
||||||
A module to perform fixity checks on original media. |
Perform periodic fixity checks on selected files. |
||||||
|
|
||||||
|
This module defines a new content entity type `fixity_check`. This entity is |
||||||
|
used as an audit trail for fixity checks performed on a related `file` entity. |
||||||
|
Wherein the revisions of the `fixity_check` record the results of previous |
||||||
|
checks against that `file` entity. |
||||||
|
|
||||||
|
This modules requires and enforces the following constraints on `fixity_check` |
||||||
|
entities: |
||||||
|
|
||||||
|
- **Must** be related to a `file` |
||||||
|
- `file` relations **must** be unique |
||||||
|
- `file` relation **cannot** be changed after creation |
||||||
|
- `performed` and `state` properties **cannot** be modified after creation. |
||||||
|
|
||||||
|
Users with the permission `Administer Fixity Checks` can: |
||||||
|
|
||||||
|
- Manually perform checks |
||||||
|
- Manually remove `fixity_check` entities and their revisions |
||||||
|
- Manually mark files as requiring periodic checks |
||||||
|
- Generate `fixity_check` entities for all previously existing files |
||||||
|
|
||||||
|
Users with the permission `View Fixity Checks` can: |
||||||
|
|
||||||
|
- View fixity audit log of Media entities |
||||||
|
|
||||||
|
A `cron` hook is setup to automatically mark files as _requiring_ periodic |
||||||
|
checks. As well as performing those checks on a regular basis. Email |
||||||
|
notifications can be configured to alert the selected user of the status |
||||||
|
of all performed checks on a regular basis or only when an error occurs. |
||||||
|
|
||||||
## Requirements |
## Requirements |
||||||
|
|
||||||
This module requires the following modules/libraries: |
This module requires the following modules/libraries: |
||||||
|
|
||||||
* [filehash](https://www.drupal.org/project/filehash) |
* [filehash] |
||||||
|
|
||||||
|
## Configuration |
||||||
|
|
||||||
|
The module can be configured at `admin/config/fixity`. |
||||||
|
|
||||||
## Usage |
## Drush |
||||||
|
|
||||||
|
A number of drush commands come bundled with this module. |
||||||
|
|
||||||
|
```bash |
||||||
|
$ drush dgi_fixity:clear --help |
||||||
|
Sets the periodic check flag to FALSE for all files. |
||||||
|
``` |
||||||
|
|
||||||
|
```bash |
||||||
|
$ drush dgi_fixity:generate --help |
||||||
|
Creates a fixity_check entity for all previously created files. |
||||||
|
``` |
||||||
|
|
||||||
|
```bash |
||||||
|
$ drush dgi_fixity:check --help |
||||||
|
Perform fixity checks on files. |
||||||
|
|
||||||
|
Options: |
||||||
|
--fids[=FIDS] Comma separated list of file identifiers, or a path to a file containing file identifiers. |
||||||
|
The file should have each fid separated by a new line. If not specified the modules settings |
||||||
|
for sources is used to determine which files to check. |
||||||
|
--force Skip time elapsed threshold check when processing files. |
||||||
|
``` |
||||||
|
|
||||||
## Installation |
## Installation |
||||||
|
|
||||||
Install as usual, see |
Install as usual, see [this][install] for further information. |
||||||
[this](https://drupal.org/documentation/install/modules-themes/modules-8) for |
|
||||||
further information. |
Additionally after this module is first enabled, you will need to generate |
||||||
|
`fixity_check` entities for all pre-existing `file` entities. This does not |
||||||
|
require that the checks are performed, only that one `fixity_check` entity |
||||||
|
exists for every applicable `file` entity in the system. |
||||||
|
|
||||||
|
This can be done with `drush`: |
||||||
|
|
||||||
|
```bash |
||||||
|
drush dgi_fixity:generate |
||||||
|
``` |
||||||
|
|
||||||
|
Or via the admin form on the page `admin/config/fixity/generate`. |
||||||
|
|
||||||
## Troubleshooting/Issues |
## Troubleshooting/Issues |
||||||
|
|
||||||
Having problems or solved a problem? Contact |
Having problems or solved a problem? Contact [discoverygarden]. |
||||||
[discoverygarden](http://support.discoverygarden.ca). |
|
||||||
|
|
||||||
## Maintainers/Sponsors |
## Maintainers/Sponsors |
||||||
|
|
||||||
Current maintainers: |
Current maintainers: |
||||||
|
|
||||||
* [discoverygarden](http://www.discoverygarden.ca) |
* [discoverygarden] |
||||||
|
|
||||||
## Development |
## Development |
||||||
|
|
||||||
If you would like to contribute to this module create an issue, pull request |
If you would like to contribute to this module create an issue, pull request |
||||||
and or contact |
and or contact [discoverygarden]. |
||||||
[discoverygarden](http://support.discoverygarden.ca). |
|
||||||
|
|
||||||
## License |
## License |
||||||
|
|
||||||
[GPLv2](http://www.gnu.org/licenses/gpl-2.0.txt) |
[GPLv2][gplv2] |
||||||
|
|
||||||
|
[discoverygarden]: http://support.discoverygarden.ca |
||||||
|
[filehash]: https://www.drupal.org/project/filehash |
||||||
|
[gplv2]: http://www.gnu.org/licenses/gpl-2.0.txt |
||||||
|
[install]: https://drupal.org/documentation/install/modules-themes/modules-8 |
@ -0,0 +1,8 @@ |
|||||||
|
{ |
||||||
|
"name": "discoverygarden/dgi_fixity", |
||||||
|
"type": "drupal-module", |
||||||
|
"license": "GPL-2.0-or-later", |
||||||
|
"require": { |
||||||
|
"drupal/filehash": "^2.0" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
sources: |
||||||
|
'fixity_check_source:all_files': 'fixity_check_source:all_files' |
||||||
|
threshold: '-1 month' |
||||||
|
batch_size: 100 |
||||||
|
notify_user: 1 |
||||||
|
notify_user_threshold: '-1 week' |
||||||
|
notify_status: 2 |
@ -0,0 +1,246 @@ |
|||||||
|
langcode: en |
||||||
|
status: true |
||||||
|
dependencies: |
||||||
|
enforced: |
||||||
|
module: |
||||||
|
- dgi_fixity |
||||||
|
module: |
||||||
|
- file |
||||||
|
id: fixity_check_source |
||||||
|
label: 'Fixity Check Source Default' |
||||||
|
module: views |
||||||
|
description: 'Default fixity check source, selects all permanent files.' |
||||||
|
tag: fixity |
||||||
|
base_table: file_managed |
||||||
|
base_field: fid |
||||||
|
display: |
||||||
|
default: |
||||||
|
display_plugin: default |
||||||
|
id: default |
||||||
|
display_title: Default |
||||||
|
position: 0 |
||||||
|
display_options: |
||||||
|
access: |
||||||
|
type: none |
||||||
|
options: { } |
||||||
|
cache: |
||||||
|
type: none |
||||||
|
options: { } |
||||||
|
query: |
||||||
|
type: views_query |
||||||
|
options: |
||||||
|
disable_sql_rewrite: false |
||||||
|
distinct: false |
||||||
|
replica: false |
||||||
|
query_comment: '' |
||||||
|
query_tags: { } |
||||||
|
exposed_form: |
||||||
|
type: basic |
||||||
|
options: |
||||||
|
submit_button: Apply |
||||||
|
reset_button: false |
||||||
|
reset_button_label: Reset |
||||||
|
exposed_sorts_label: 'Sort by' |
||||||
|
expose_sort_order: true |
||||||
|
sort_asc_label: Asc |
||||||
|
sort_desc_label: Desc |
||||||
|
pager: |
||||||
|
type: mini |
||||||
|
options: |
||||||
|
items_per_page: 10 |
||||||
|
offset: 0 |
||||||
|
id: 0 |
||||||
|
total_pages: null |
||||||
|
expose: |
||||||
|
items_per_page: false |
||||||
|
items_per_page_label: 'Items per page' |
||||||
|
items_per_page_options: '5, 10, 25, 50' |
||||||
|
items_per_page_options_all: false |
||||||
|
items_per_page_options_all_label: '- All -' |
||||||
|
offset: false |
||||||
|
offset_label: Offset |
||||||
|
tags: |
||||||
|
previous: ‹‹ |
||||||
|
next: ›› |
||||||
|
style: |
||||||
|
type: default |
||||||
|
options: |
||||||
|
grouping: { } |
||||||
|
row_class: '' |
||||||
|
default_row_class: true |
||||||
|
uses_fields: false |
||||||
|
row: |
||||||
|
type: fields |
||||||
|
options: |
||||||
|
inline: { } |
||||||
|
separator: '' |
||||||
|
hide_empty: false |
||||||
|
default_field_elements: true |
||||||
|
fields: |
||||||
|
fid: |
||||||
|
id: fid |
||||||
|
table: file_managed |
||||||
|
field: fid |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: 'File ID' |
||||||
|
label: '' |
||||||
|
exclude: false |
||||||
|
alter: |
||||||
|
alter_text: false |
||||||
|
text: '' |
||||||
|
make_link: false |
||||||
|
path: '' |
||||||
|
absolute: false |
||||||
|
external: false |
||||||
|
replace_spaces: false |
||||||
|
path_case: none |
||||||
|
trim_whitespace: false |
||||||
|
alt: '' |
||||||
|
rel: '' |
||||||
|
link_class: '' |
||||||
|
prefix: '' |
||||||
|
suffix: '' |
||||||
|
target: '' |
||||||
|
nl2br: false |
||||||
|
max_length: 0 |
||||||
|
word_boundary: true |
||||||
|
ellipsis: true |
||||||
|
more_link: false |
||||||
|
more_link_text: '' |
||||||
|
more_link_path: '' |
||||||
|
strip_tags: false |
||||||
|
trim: false |
||||||
|
preserve_tags: '' |
||||||
|
html: false |
||||||
|
element_type: '' |
||||||
|
element_class: '' |
||||||
|
element_label_type: '' |
||||||
|
element_label_class: '' |
||||||
|
element_label_colon: false |
||||||
|
element_wrapper_type: '' |
||||||
|
element_wrapper_class: '' |
||||||
|
element_default_classes: false |
||||||
|
empty: '' |
||||||
|
hide_empty: true |
||||||
|
empty_zero: false |
||||||
|
hide_alter_empty: true |
||||||
|
click_sort_column: value |
||||||
|
type: number_unformatted |
||||||
|
settings: { } |
||||||
|
group_column: value |
||||||
|
group_columns: { } |
||||||
|
group_rows: true |
||||||
|
delta_limit: 0 |
||||||
|
delta_offset: 0 |
||||||
|
delta_reversed: false |
||||||
|
delta_first_last: false |
||||||
|
multi_type: separator |
||||||
|
separator: ', ' |
||||||
|
field_api_classes: false |
||||||
|
entity_type: file |
||||||
|
entity_field: fid |
||||||
|
plugin_id: field |
||||||
|
filters: |
||||||
|
status: |
||||||
|
id: status |
||||||
|
table: file_managed |
||||||
|
field: status |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
operator: in |
||||||
|
value: |
||||||
|
1: '1' |
||||||
|
group: 1 |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
operator_id: '' |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: '' |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: '' |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
reduce: false |
||||||
|
is_grouped: false |
||||||
|
group_info: |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
identifier: '' |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: { } |
||||||
|
entity_type: file |
||||||
|
entity_field: status |
||||||
|
plugin_id: file_status |
||||||
|
sorts: |
||||||
|
fid: |
||||||
|
id: fid |
||||||
|
table: file_managed |
||||||
|
field: fid |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: 'File ID' |
||||||
|
order: ASC |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
label: '' |
||||||
|
entity_type: file |
||||||
|
entity_field: fid |
||||||
|
plugin_id: standard |
||||||
|
header: { } |
||||||
|
footer: { } |
||||||
|
empty: { } |
||||||
|
relationships: { } |
||||||
|
arguments: { } |
||||||
|
display_extenders: { } |
||||||
|
show_admin_links: false |
||||||
|
cache_metadata: |
||||||
|
max-age: -1 |
||||||
|
contexts: |
||||||
|
- 'languages:language_content' |
||||||
|
- 'languages:language_interface' |
||||||
|
- url.query_args |
||||||
|
tags: { } |
||||||
|
all_files: |
||||||
|
display_plugin: entity_reference |
||||||
|
id: all_files |
||||||
|
display_title: 'All Files' |
||||||
|
position: 1 |
||||||
|
display_options: |
||||||
|
display_extenders: { } |
||||||
|
style: |
||||||
|
type: entity_reference |
||||||
|
options: |
||||||
|
search_fields: |
||||||
|
fid: fid |
||||||
|
row: |
||||||
|
type: entity_reference |
||||||
|
options: |
||||||
|
default_field_elements: false |
||||||
|
inline: { } |
||||||
|
separator: '-' |
||||||
|
hide_empty: true |
||||||
|
display_description: 'Gets all files regardless of state or usage' |
||||||
|
pager: |
||||||
|
type: some |
||||||
|
options: |
||||||
|
items_per_page: 10 |
||||||
|
offset: 0 |
||||||
|
cache_metadata: |
||||||
|
max-age: -1 |
||||||
|
contexts: |
||||||
|
- 'languages:language_content' |
||||||
|
- 'languages:language_interface' |
||||||
|
tags: { } |
@ -0,0 +1,824 @@ |
|||||||
|
langcode: en |
||||||
|
status: true |
||||||
|
dependencies: |
||||||
|
module: |
||||||
|
- dgi_fixity |
||||||
|
- file |
||||||
|
- user |
||||||
|
id: fixity_check_status |
||||||
|
label: 'Fixity Check Status' |
||||||
|
module: views |
||||||
|
description: 'Find and manage fixity checks' |
||||||
|
tag: fixity |
||||||
|
base_table: fixity_check |
||||||
|
base_field: id |
||||||
|
display: |
||||||
|
default: |
||||||
|
display_plugin: default |
||||||
|
id: default |
||||||
|
display_title: Default |
||||||
|
position: 0 |
||||||
|
display_options: |
||||||
|
access: |
||||||
|
type: perm |
||||||
|
options: |
||||||
|
perm: 'administer fixity checks' |
||||||
|
cache: |
||||||
|
type: tag |
||||||
|
options: { } |
||||||
|
query: |
||||||
|
type: views_query |
||||||
|
options: |
||||||
|
disable_sql_rewrite: false |
||||||
|
distinct: false |
||||||
|
replica: false |
||||||
|
query_comment: '' |
||||||
|
query_tags: { } |
||||||
|
exposed_form: |
||||||
|
type: basic |
||||||
|
options: |
||||||
|
submit_button: Filter |
||||||
|
reset_button: false |
||||||
|
reset_button_label: Reset |
||||||
|
exposed_sorts_label: 'Sort by' |
||||||
|
expose_sort_order: true |
||||||
|
sort_asc_label: Asc |
||||||
|
sort_desc_label: Desc |
||||||
|
pager: |
||||||
|
type: mini |
||||||
|
options: |
||||||
|
items_per_page: 10 |
||||||
|
offset: 0 |
||||||
|
id: 0 |
||||||
|
total_pages: null |
||||||
|
expose: |
||||||
|
items_per_page: false |
||||||
|
items_per_page_label: 'Items per page' |
||||||
|
items_per_page_options: '5, 10, 25, 50' |
||||||
|
items_per_page_options_all: false |
||||||
|
items_per_page_options_all_label: '- All -' |
||||||
|
offset: false |
||||||
|
offset_label: Offset |
||||||
|
tags: |
||||||
|
previous: ‹‹ |
||||||
|
next: ›› |
||||||
|
style: |
||||||
|
type: table |
||||||
|
options: |
||||||
|
grouping: { } |
||||||
|
row_class: '' |
||||||
|
default_row_class: true |
||||||
|
override: true |
||||||
|
sticky: false |
||||||
|
caption: '' |
||||||
|
summary: '' |
||||||
|
description: '' |
||||||
|
columns: |
||||||
|
file: file |
||||||
|
state: state |
||||||
|
performed: performed |
||||||
|
operations: operations |
||||||
|
info: |
||||||
|
file: |
||||||
|
sortable: false |
||||||
|
default_sort_order: asc |
||||||
|
align: '' |
||||||
|
separator: '' |
||||||
|
empty_column: false |
||||||
|
responsive: '' |
||||||
|
state: |
||||||
|
sortable: true |
||||||
|
default_sort_order: asc |
||||||
|
align: '' |
||||||
|
separator: '' |
||||||
|
empty_column: false |
||||||
|
responsive: '' |
||||||
|
performed: |
||||||
|
sortable: true |
||||||
|
default_sort_order: desc |
||||||
|
align: '' |
||||||
|
separator: '' |
||||||
|
empty_column: false |
||||||
|
responsive: '' |
||||||
|
operations: |
||||||
|
align: '' |
||||||
|
separator: '' |
||||||
|
empty_column: false |
||||||
|
responsive: '' |
||||||
|
default: performed |
||||||
|
empty_table: false |
||||||
|
row: |
||||||
|
type: fields |
||||||
|
fields: |
||||||
|
fixity_check_bulk_form: |
||||||
|
id: fixity_check_bulk_form |
||||||
|
table: fixity_check |
||||||
|
field: fixity_check_bulk_form |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
label: '' |
||||||
|
exclude: false |
||||||
|
alter: |
||||||
|
alter_text: false |
||||||
|
text: '' |
||||||
|
make_link: false |
||||||
|
path: '' |
||||||
|
absolute: false |
||||||
|
external: false |
||||||
|
replace_spaces: false |
||||||
|
path_case: none |
||||||
|
trim_whitespace: false |
||||||
|
alt: '' |
||||||
|
rel: '' |
||||||
|
link_class: '' |
||||||
|
prefix: '' |
||||||
|
suffix: '' |
||||||
|
target: '' |
||||||
|
nl2br: false |
||||||
|
max_length: 0 |
||||||
|
word_boundary: true |
||||||
|
ellipsis: true |
||||||
|
more_link: false |
||||||
|
more_link_text: '' |
||||||
|
more_link_path: '' |
||||||
|
strip_tags: false |
||||||
|
trim: false |
||||||
|
preserve_tags: '' |
||||||
|
html: false |
||||||
|
element_type: '' |
||||||
|
element_class: '' |
||||||
|
element_label_type: '' |
||||||
|
element_label_class: '' |
||||||
|
element_label_colon: false |
||||||
|
element_wrapper_type: '' |
||||||
|
element_wrapper_class: '' |
||||||
|
element_default_classes: true |
||||||
|
empty: '' |
||||||
|
hide_empty: false |
||||||
|
empty_zero: false |
||||||
|
hide_alter_empty: true |
||||||
|
action_title: Action |
||||||
|
include_exclude: exclude |
||||||
|
selected_actions: { } |
||||||
|
entity_type: fixity_check |
||||||
|
plugin_id: bulk_form |
||||||
|
view_fixity_check: |
||||||
|
id: view_fixity_check |
||||||
|
table: fixity_check |
||||||
|
field: view_fixity_check |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
label: '' |
||||||
|
exclude: true |
||||||
|
alter: |
||||||
|
alter_text: false |
||||||
|
text: '<a href="{{ view_fixity_check }}>{{ file }}</a>' |
||||||
|
make_link: false |
||||||
|
path: '' |
||||||
|
absolute: false |
||||||
|
external: false |
||||||
|
replace_spaces: false |
||||||
|
path_case: none |
||||||
|
trim_whitespace: false |
||||||
|
alt: '{{ file }}' |
||||||
|
rel: '' |
||||||
|
link_class: '' |
||||||
|
prefix: '' |
||||||
|
suffix: '' |
||||||
|
target: '' |
||||||
|
nl2br: false |
||||||
|
max_length: 0 |
||||||
|
word_boundary: true |
||||||
|
ellipsis: true |
||||||
|
more_link: false |
||||||
|
more_link_text: '' |
||||||
|
more_link_path: '' |
||||||
|
strip_tags: false |
||||||
|
trim: false |
||||||
|
preserve_tags: '' |
||||||
|
html: false |
||||||
|
element_type: '' |
||||||
|
element_class: '' |
||||||
|
element_label_type: '' |
||||||
|
element_label_class: '' |
||||||
|
element_label_colon: false |
||||||
|
element_wrapper_type: '' |
||||||
|
element_wrapper_class: '' |
||||||
|
element_default_classes: true |
||||||
|
empty: '' |
||||||
|
hide_empty: false |
||||||
|
empty_zero: false |
||||||
|
hide_alter_empty: true |
||||||
|
text: '' |
||||||
|
output_url_as_text: true |
||||||
|
absolute: false |
||||||
|
entity_type: fixity_check |
||||||
|
plugin_id: entity_link |
||||||
|
file_1: |
||||||
|
id: file_1 |
||||||
|
table: fixity_check |
||||||
|
field: file |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
label: Check |
||||||
|
exclude: false |
||||||
|
alter: |
||||||
|
alter_text: false |
||||||
|
text: '<a href="{{ view_fixity_check }}">{{ file_1 }}</a>' |
||||||
|
make_link: true |
||||||
|
path: '{{ view_fixity_check }}' |
||||||
|
absolute: false |
||||||
|
external: false |
||||||
|
replace_spaces: false |
||||||
|
path_case: none |
||||||
|
trim_whitespace: false |
||||||
|
alt: '' |
||||||
|
rel: '' |
||||||
|
link_class: '' |
||||||
|
prefix: '' |
||||||
|
suffix: '' |
||||||
|
target: '' |
||||||
|
nl2br: false |
||||||
|
max_length: 0 |
||||||
|
word_boundary: true |
||||||
|
ellipsis: true |
||||||
|
more_link: false |
||||||
|
more_link_text: '' |
||||||
|
more_link_path: '' |
||||||
|
strip_tags: false |
||||||
|
trim: false |
||||||
|
preserve_tags: '' |
||||||
|
html: false |
||||||
|
element_type: '' |
||||||
|
element_class: '' |
||||||
|
element_label_type: '' |
||||||
|
element_label_class: '' |
||||||
|
element_label_colon: false |
||||||
|
element_wrapper_type: '' |
||||||
|
element_wrapper_class: '' |
||||||
|
element_default_classes: true |
||||||
|
empty: '' |
||||||
|
hide_empty: false |
||||||
|
empty_zero: false |
||||||
|
hide_alter_empty: true |
||||||
|
click_sort_column: target_id |
||||||
|
type: entity_reference_label |
||||||
|
settings: |
||||||
|
link: false |
||||||
|
group_column: target_id |
||||||
|
group_columns: { } |
||||||
|
group_rows: true |
||||||
|
delta_limit: 0 |
||||||
|
delta_offset: 0 |
||||||
|
delta_reversed: false |
||||||
|
delta_first_last: false |
||||||
|
multi_type: separator |
||||||
|
separator: ', ' |
||||||
|
field_api_classes: false |
||||||
|
entity_type: fixity_check |
||||||
|
entity_field: file |
||||||
|
plugin_id: field |
||||||
|
state: |
||||||
|
id: state |
||||||
|
table: fixity_check |
||||||
|
field: state |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
label: State |
||||||
|
exclude: false |
||||||
|
alter: |
||||||
|
alter_text: false |
||||||
|
text: '' |
||||||
|
make_link: false |
||||||
|
path: '' |
||||||
|
absolute: false |
||||||
|
external: false |
||||||
|
replace_spaces: false |
||||||
|
path_case: none |
||||||
|
trim_whitespace: false |
||||||
|
alt: '' |
||||||
|
rel: '' |
||||||
|
link_class: '' |
||||||
|
prefix: '' |
||||||
|
suffix: '' |
||||||
|
target: '' |
||||||
|
nl2br: false |
||||||
|
max_length: 0 |
||||||
|
word_boundary: true |
||||||
|
ellipsis: true |
||||||
|
more_link: false |
||||||
|
more_link_text: '' |
||||||
|
more_link_path: '' |
||||||
|
strip_tags: false |
||||||
|
trim: false |
||||||
|
preserve_tags: '' |
||||||
|
html: false |
||||||
|
element_type: '' |
||||||
|
element_class: '' |
||||||
|
element_label_type: '' |
||||||
|
element_label_class: '' |
||||||
|
element_label_colon: false |
||||||
|
element_wrapper_type: '' |
||||||
|
element_wrapper_class: '' |
||||||
|
element_default_classes: true |
||||||
|
empty: '' |
||||||
|
hide_empty: false |
||||||
|
empty_zero: false |
||||||
|
hide_alter_empty: true |
||||||
|
click_sort_column: value |
||||||
|
type: dgi_fixity_state |
||||||
|
settings: { } |
||||||
|
group_column: value |
||||||
|
group_columns: { } |
||||||
|
group_rows: true |
||||||
|
delta_limit: 0 |
||||||
|
delta_offset: 0 |
||||||
|
delta_reversed: false |
||||||
|
delta_first_last: false |
||||||
|
multi_type: separator |
||||||
|
separator: ', ' |
||||||
|
field_api_classes: false |
||||||
|
entity_type: fixity_check |
||||||
|
entity_field: state |
||||||
|
plugin_id: field |
||||||
|
performed: |
||||||
|
id: performed |
||||||
|
table: fixity_check |
||||||
|
field: performed |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
label: Performed |
||||||
|
exclude: false |
||||||
|
alter: |
||||||
|
alter_text: false |
||||||
|
text: '' |
||||||
|
make_link: false |
||||||
|
path: '' |
||||||
|
absolute: false |
||||||
|
external: false |
||||||
|
replace_spaces: false |
||||||
|
path_case: none |
||||||
|
trim_whitespace: false |
||||||
|
alt: '' |
||||||
|
rel: '' |
||||||
|
link_class: '' |
||||||
|
prefix: '' |
||||||
|
suffix: '' |
||||||
|
target: '' |
||||||
|
nl2br: false |
||||||
|
max_length: 0 |
||||||
|
word_boundary: true |
||||||
|
ellipsis: true |
||||||
|
more_link: false |
||||||
|
more_link_text: '' |
||||||
|
more_link_path: '' |
||||||
|
strip_tags: false |
||||||
|
trim: false |
||||||
|
preserve_tags: '' |
||||||
|
html: false |
||||||
|
element_type: '' |
||||||
|
element_class: '' |
||||||
|
element_label_type: '' |
||||||
|
element_label_class: '' |
||||||
|
element_label_colon: false |
||||||
|
element_wrapper_type: '' |
||||||
|
element_wrapper_class: '' |
||||||
|
element_default_classes: true |
||||||
|
empty: '' |
||||||
|
hide_empty: false |
||||||
|
empty_zero: false |
||||||
|
hide_alter_empty: true |
||||||
|
click_sort_column: value |
||||||
|
type: timestamp_ago |
||||||
|
settings: |
||||||
|
future_format: '@interval hence' |
||||||
|
past_format: '@interval ago' |
||||||
|
granularity: 2 |
||||||
|
group_column: value |
||||||
|
group_columns: { } |
||||||
|
group_rows: true |
||||||
|
delta_limit: 0 |
||||||
|
delta_offset: 0 |
||||||
|
delta_reversed: false |
||||||
|
delta_first_last: false |
||||||
|
multi_type: separator |
||||||
|
separator: ', ' |
||||||
|
field_api_classes: false |
||||||
|
entity_type: fixity_check |
||||||
|
entity_field: performed |
||||||
|
plugin_id: field |
||||||
|
periodic: |
||||||
|
id: periodic |
||||||
|
table: fixity_check |
||||||
|
field: periodic |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
label: Periodic |
||||||
|
exclude: false |
||||||
|
alter: |
||||||
|
alter_text: false |
||||||
|
text: '' |
||||||
|
make_link: false |
||||||
|
path: '' |
||||||
|
absolute: false |
||||||
|
external: false |
||||||
|
replace_spaces: false |
||||||
|
path_case: none |
||||||
|
trim_whitespace: false |
||||||
|
alt: '' |
||||||
|
rel: '' |
||||||
|
link_class: '' |
||||||
|
prefix: '' |
||||||
|
suffix: '' |
||||||
|
target: '' |
||||||
|
nl2br: false |
||||||
|
max_length: 0 |
||||||
|
word_boundary: true |
||||||
|
ellipsis: true |
||||||
|
more_link: false |
||||||
|
more_link_text: '' |
||||||
|
more_link_path: '' |
||||||
|
strip_tags: false |
||||||
|
trim: false |
||||||
|
preserve_tags: '' |
||||||
|
html: false |
||||||
|
element_type: '' |
||||||
|
element_class: '' |
||||||
|
element_label_type: '' |
||||||
|
element_label_class: '' |
||||||
|
element_label_colon: false |
||||||
|
element_wrapper_type: '' |
||||||
|
element_wrapper_class: '' |
||||||
|
element_default_classes: true |
||||||
|
empty: '' |
||||||
|
hide_empty: false |
||||||
|
empty_zero: false |
||||||
|
hide_alter_empty: true |
||||||
|
click_sort_column: value |
||||||
|
type: boolean |
||||||
|
settings: |
||||||
|
format: default |
||||||
|
format_custom_true: '' |
||||||
|
format_custom_false: '' |
||||||
|
group_column: value |
||||||
|
group_columns: { } |
||||||
|
group_rows: true |
||||||
|
delta_limit: 0 |
||||||
|
delta_offset: 0 |
||||||
|
delta_reversed: false |
||||||
|
delta_first_last: false |
||||||
|
multi_type: separator |
||||||
|
separator: ', ' |
||||||
|
field_api_classes: false |
||||||
|
entity_type: fixity_check |
||||||
|
entity_field: periodic |
||||||
|
plugin_id: field |
||||||
|
operations: |
||||||
|
table: fixity_check |
||||||
|
field: operations |
||||||
|
id: operations |
||||||
|
entity_type: null |
||||||
|
entity_field: null |
||||||
|
plugin_id: entity_operations |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
label: 'Operations links' |
||||||
|
exclude: false |
||||||
|
alter: |
||||||
|
alter_text: false |
||||||
|
text: '' |
||||||
|
make_link: false |
||||||
|
path: '' |
||||||
|
absolute: false |
||||||
|
external: false |
||||||
|
replace_spaces: false |
||||||
|
path_case: none |
||||||
|
trim_whitespace: false |
||||||
|
alt: '' |
||||||
|
rel: '' |
||||||
|
link_class: '' |
||||||
|
prefix: '' |
||||||
|
suffix: '' |
||||||
|
target: '' |
||||||
|
nl2br: false |
||||||
|
max_length: 0 |
||||||
|
word_boundary: true |
||||||
|
ellipsis: true |
||||||
|
more_link: false |
||||||
|
more_link_text: '' |
||||||
|
more_link_path: '' |
||||||
|
strip_tags: false |
||||||
|
trim: false |
||||||
|
preserve_tags: '' |
||||||
|
html: false |
||||||
|
element_type: '' |
||||||
|
element_class: '' |
||||||
|
element_label_type: '' |
||||||
|
element_label_class: '' |
||||||
|
element_label_colon: true |
||||||
|
element_wrapper_type: '' |
||||||
|
element_wrapper_class: '' |
||||||
|
element_default_classes: true |
||||||
|
empty: '' |
||||||
|
hide_empty: false |
||||||
|
empty_zero: false |
||||||
|
hide_alter_empty: true |
||||||
|
destination: false |
||||||
|
filters: |
||||||
|
filename: |
||||||
|
id: filename |
||||||
|
table: file_managed |
||||||
|
field: filename |
||||||
|
relationship: file |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
operator: contains |
||||||
|
value: '' |
||||||
|
group: 1 |
||||||
|
exposed: true |
||||||
|
expose: |
||||||
|
operator_id: filename_op |
||||||
|
label: Filename |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: filename_op |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: filename |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
anonymous: '0' |
||||||
|
administrator: '0' |
||||||
|
placeholder: '' |
||||||
|
is_grouped: false |
||||||
|
group_info: |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
identifier: '' |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: { } |
||||||
|
entity_type: file |
||||||
|
entity_field: filename |
||||||
|
plugin_id: string |
||||||
|
state: |
||||||
|
id: state |
||||||
|
table: fixity_check |
||||||
|
field: state |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
operator: '=' |
||||||
|
value: |
||||||
|
min: '' |
||||||
|
max: '' |
||||||
|
value: '' |
||||||
|
group: 1 |
||||||
|
exposed: true |
||||||
|
expose: |
||||||
|
operator_id: state_op |
||||||
|
label: State |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: state_op |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: state |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
anonymous: '0' |
||||||
|
administrator: '0' |
||||||
|
fedoraadmin: '0' |
||||||
|
placeholder: '' |
||||||
|
min_placeholder: '' |
||||||
|
max_placeholder: '' |
||||||
|
is_grouped: true |
||||||
|
group_info: |
||||||
|
label: State |
||||||
|
description: '' |
||||||
|
identifier: state |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: |
||||||
|
1: |
||||||
|
title: Passed |
||||||
|
operator: '=' |
||||||
|
value: |
||||||
|
value: '1' |
||||||
|
min: '' |
||||||
|
max: '' |
||||||
|
2: |
||||||
|
title: Failed |
||||||
|
operator: '!=' |
||||||
|
value: |
||||||
|
value: '1' |
||||||
|
min: '' |
||||||
|
max: '' |
||||||
|
entity_type: fixity_check |
||||||
|
entity_field: state |
||||||
|
plugin_id: numeric |
||||||
|
performed: |
||||||
|
id: performed |
||||||
|
table: fixity_check |
||||||
|
field: performed |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
operator: '=' |
||||||
|
value: |
||||||
|
min: '' |
||||||
|
max: '' |
||||||
|
value: '1970-01-01 00:00:00' |
||||||
|
type: date |
||||||
|
group: 1 |
||||||
|
exposed: true |
||||||
|
expose: |
||||||
|
operator_id: performed_op |
||||||
|
label: Performed |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: performed_op |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: performed |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
anonymous: '0' |
||||||
|
administrator: '0' |
||||||
|
fedoraadmin: '0' |
||||||
|
placeholder: '' |
||||||
|
min_placeholder: '' |
||||||
|
max_placeholder: '' |
||||||
|
is_grouped: true |
||||||
|
group_info: |
||||||
|
label: Performed |
||||||
|
description: '' |
||||||
|
identifier: performed |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: |
||||||
|
1: |
||||||
|
title: 'True' |
||||||
|
operator: '!=' |
||||||
|
value: |
||||||
|
type: date |
||||||
|
value: '1970-01-01 00:00:00' |
||||||
|
min: '' |
||||||
|
max: '' |
||||||
|
2: |
||||||
|
title: 'False' |
||||||
|
operator: '=' |
||||||
|
value: |
||||||
|
type: date |
||||||
|
value: '1970-01-01 00:00:00' |
||||||
|
min: '' |
||||||
|
max: '' |
||||||
|
entity_type: fixity_check |
||||||
|
entity_field: performed |
||||||
|
plugin_id: date |
||||||
|
periodic: |
||||||
|
id: periodic |
||||||
|
table: fixity_check |
||||||
|
field: periodic |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
operator: '=' |
||||||
|
value: All |
||||||
|
group: 1 |
||||||
|
exposed: true |
||||||
|
expose: |
||||||
|
operator_id: '' |
||||||
|
label: Periodic |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: periodic_op |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: periodic |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
anonymous: '0' |
||||||
|
administrator: '0' |
||||||
|
is_grouped: false |
||||||
|
group_info: |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
identifier: '' |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: { } |
||||||
|
entity_type: fixity_check |
||||||
|
entity_field: periodic |
||||||
|
plugin_id: boolean |
||||||
|
sorts: |
||||||
|
performed: |
||||||
|
id: performed |
||||||
|
table: fixity_check |
||||||
|
field: performed |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
order: DESC |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
label: Performed |
||||||
|
granularity: second |
||||||
|
entity_type: fixity_check |
||||||
|
entity_field: performed |
||||||
|
plugin_id: date |
||||||
|
title: Status |
||||||
|
header: { } |
||||||
|
footer: { } |
||||||
|
empty: |
||||||
|
area_text_custom: |
||||||
|
id: area_text_custom |
||||||
|
table: views |
||||||
|
field: area_text_custom |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
empty: true |
||||||
|
tokenize: false |
||||||
|
content: 'No fixity checks have been performed.' |
||||||
|
plugin_id: text_custom |
||||||
|
relationships: |
||||||
|
file: |
||||||
|
id: file |
||||||
|
table: fixity_check |
||||||
|
field: file |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: File |
||||||
|
required: false |
||||||
|
entity_type: fixity_check |
||||||
|
entity_field: file |
||||||
|
plugin_id: standard |
||||||
|
arguments: { } |
||||||
|
display_extenders: { } |
||||||
|
filter_groups: |
||||||
|
operator: AND |
||||||
|
groups: |
||||||
|
1: AND |
||||||
|
cache_metadata: |
||||||
|
max-age: 0 |
||||||
|
contexts: |
||||||
|
- 'languages:language_content' |
||||||
|
- 'languages:language_interface' |
||||||
|
- url |
||||||
|
- url.query_args |
||||||
|
- user.permissions |
||||||
|
tags: { } |
||||||
|
fixity_checks: |
||||||
|
display_plugin: page |
||||||
|
id: fixity_checks |
||||||
|
display_title: Page |
||||||
|
position: 1 |
||||||
|
display_options: |
||||||
|
display_extenders: { } |
||||||
|
path: admin/reports/fixity |
||||||
|
enabled: true |
||||||
|
cache_metadata: |
||||||
|
max-age: 0 |
||||||
|
contexts: |
||||||
|
- 'languages:language_content' |
||||||
|
- 'languages:language_interface' |
||||||
|
- url |
||||||
|
- url.query_args |
||||||
|
- user.permissions |
||||||
|
tags: { } |
@ -0,0 +1,10 @@ |
|||||||
|
langcode: en |
||||||
|
status: true |
||||||
|
dependencies: |
||||||
|
module: |
||||||
|
- dgi_fixity |
||||||
|
id: fixity_check_check_action |
||||||
|
label: 'Perform check' |
||||||
|
type: fixity_check |
||||||
|
plugin: dgi_fixity:check_action:fixity_check |
||||||
|
configuration: { } |
@ -0,0 +1,10 @@ |
|||||||
|
langcode: en |
||||||
|
status: true |
||||||
|
dependencies: |
||||||
|
module: |
||||||
|
- dgi_fixity |
||||||
|
id: fixity_check_delete_action |
||||||
|
label: 'Delete check' |
||||||
|
type: fixity_check |
||||||
|
plugin: entity:delete_action:fixity_check |
||||||
|
configuration: { } |
@ -0,0 +1,10 @@ |
|||||||
|
langcode: en |
||||||
|
status: true |
||||||
|
dependencies: |
||||||
|
module: |
||||||
|
- dgi_fixity |
||||||
|
id: fixity_check_periodic_disable_action |
||||||
|
label: 'Disable periodic checks' |
||||||
|
type: fixity_check |
||||||
|
plugin: dgi_fixity:periodic_disable_action:fixity_check |
||||||
|
configuration: { } |
@ -0,0 +1,10 @@ |
|||||||
|
langcode: en |
||||||
|
status: true |
||||||
|
dependencies: |
||||||
|
module: |
||||||
|
- dgi_fixity |
||||||
|
id: fixity_check_periodic_enable_action |
||||||
|
label: 'Enable periodic checks' |
||||||
|
type: fixity_check |
||||||
|
plugin: dgi_fixity:periodic_enable_action:fixity_check |
||||||
|
configuration: { } |
@ -0,0 +1,11 @@ |
|||||||
|
langcode: en |
||||||
|
status: true |
||||||
|
dependencies: |
||||||
|
module: |
||||||
|
- media |
||||||
|
- dgi_fixity |
||||||
|
id: media_check_action |
||||||
|
label: 'Check media' |
||||||
|
type: media |
||||||
|
plugin: dgi_fixity:check_action:media |
||||||
|
configuration: { } |
@ -0,0 +1,11 @@ |
|||||||
|
langcode: en |
||||||
|
status: true |
||||||
|
dependencies: |
||||||
|
module: |
||||||
|
- dgi_fixity |
||||||
|
- media |
||||||
|
id: media_periodic_disable_action |
||||||
|
label: 'Disable periodic media checks' |
||||||
|
type: media |
||||||
|
plugin: dgi_fixity:periodic_disable_action:media |
||||||
|
configuration: { } |
@ -0,0 +1,11 @@ |
|||||||
|
langcode: en |
||||||
|
status: true |
||||||
|
dependencies: |
||||||
|
module: |
||||||
|
- dgi_fixity |
||||||
|
- media |
||||||
|
id: media_periodic_enable_action |
||||||
|
label: 'Enable periodic media checks' |
||||||
|
type: media |
||||||
|
plugin: dgi_fixity:periodic_enable_action:media |
||||||
|
configuration: { } |
@ -0,0 +1,901 @@ |
|||||||
|
langcode: en |
||||||
|
status: true |
||||||
|
dependencies: |
||||||
|
enforced: |
||||||
|
config: |
||||||
|
# Requires the following fields to work. |
||||||
|
- field.storage.media.field_media_use |
||||||
|
- field.storage.taxonomy_term.field_external_uri |
||||||
|
module: |
||||||
|
- dgi_fixity |
||||||
|
module: |
||||||
|
- file |
||||||
|
- media |
||||||
|
- taxonomy |
||||||
|
id: fixity_check_source_islandora |
||||||
|
label: 'Fixity Check Source Islandora' |
||||||
|
module: views |
||||||
|
description: 'Fixity Check Source for islandora, selects all "Original Files"' |
||||||
|
tag: fixity |
||||||
|
base_table: file_managed |
||||||
|
base_field: fid |
||||||
|
display: |
||||||
|
default: |
||||||
|
display_plugin: default |
||||||
|
id: default |
||||||
|
display_title: Default |
||||||
|
position: 0 |
||||||
|
display_options: |
||||||
|
access: |
||||||
|
type: none |
||||||
|
options: { } |
||||||
|
cache: |
||||||
|
type: none |
||||||
|
options: { } |
||||||
|
query: |
||||||
|
type: views_query |
||||||
|
options: |
||||||
|
disable_sql_rewrite: false |
||||||
|
distinct: false |
||||||
|
replica: false |
||||||
|
query_comment: '' |
||||||
|
query_tags: { } |
||||||
|
exposed_form: |
||||||
|
type: basic |
||||||
|
options: |
||||||
|
submit_button: Apply |
||||||
|
reset_button: false |
||||||
|
reset_button_label: Reset |
||||||
|
exposed_sorts_label: 'Sort by' |
||||||
|
expose_sort_order: true |
||||||
|
sort_asc_label: Asc |
||||||
|
sort_desc_label: Desc |
||||||
|
pager: |
||||||
|
type: mini |
||||||
|
options: |
||||||
|
items_per_page: 10 |
||||||
|
offset: 0 |
||||||
|
id: 0 |
||||||
|
total_pages: null |
||||||
|
expose: |
||||||
|
items_per_page: false |
||||||
|
items_per_page_label: 'Items per page' |
||||||
|
items_per_page_options: '5, 10, 25, 50' |
||||||
|
items_per_page_options_all: false |
||||||
|
items_per_page_options_all_label: '- All -' |
||||||
|
offset: false |
||||||
|
offset_label: Offset |
||||||
|
tags: |
||||||
|
previous: ‹‹ |
||||||
|
next: ›› |
||||||
|
style: |
||||||
|
type: default |
||||||
|
options: |
||||||
|
grouping: { } |
||||||
|
row_class: '' |
||||||
|
default_row_class: true |
||||||
|
uses_fields: false |
||||||
|
row: |
||||||
|
type: fields |
||||||
|
options: |
||||||
|
inline: { } |
||||||
|
separator: '' |
||||||
|
hide_empty: false |
||||||
|
default_field_elements: true |
||||||
|
fields: |
||||||
|
fid: |
||||||
|
id: fid |
||||||
|
table: file_managed |
||||||
|
field: fid |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
label: '' |
||||||
|
exclude: false |
||||||
|
alter: |
||||||
|
alter_text: false |
||||||
|
text: '' |
||||||
|
make_link: false |
||||||
|
path: '' |
||||||
|
absolute: false |
||||||
|
external: false |
||||||
|
replace_spaces: false |
||||||
|
path_case: none |
||||||
|
trim_whitespace: false |
||||||
|
alt: '' |
||||||
|
rel: '' |
||||||
|
link_class: '' |
||||||
|
prefix: '' |
||||||
|
suffix: '' |
||||||
|
target: '' |
||||||
|
nl2br: false |
||||||
|
max_length: 0 |
||||||
|
word_boundary: true |
||||||
|
ellipsis: true |
||||||
|
more_link: false |
||||||
|
more_link_text: '' |
||||||
|
more_link_path: '' |
||||||
|
strip_tags: false |
||||||
|
trim: false |
||||||
|
preserve_tags: '' |
||||||
|
html: false |
||||||
|
element_type: '' |
||||||
|
element_class: '' |
||||||
|
element_label_type: '' |
||||||
|
element_label_class: '' |
||||||
|
element_label_colon: false |
||||||
|
element_wrapper_type: '' |
||||||
|
element_wrapper_class: '' |
||||||
|
element_default_classes: true |
||||||
|
empty: '' |
||||||
|
hide_empty: false |
||||||
|
empty_zero: false |
||||||
|
hide_alter_empty: true |
||||||
|
click_sort_column: value |
||||||
|
type: number_unformatted |
||||||
|
settings: { } |
||||||
|
group_column: value |
||||||
|
group_columns: { } |
||||||
|
group_rows: true |
||||||
|
delta_limit: 0 |
||||||
|
delta_offset: 0 |
||||||
|
delta_reversed: false |
||||||
|
delta_first_last: false |
||||||
|
multi_type: separator |
||||||
|
separator: ', ' |
||||||
|
field_api_classes: false |
||||||
|
entity_type: file |
||||||
|
entity_field: fid |
||||||
|
plugin_id: field |
||||||
|
filters: |
||||||
|
status: |
||||||
|
id: status |
||||||
|
table: file_managed |
||||||
|
field: status |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
operator: in |
||||||
|
value: |
||||||
|
1: '1' |
||||||
|
group: 1 |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
operator_id: '' |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: '' |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: '' |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
reduce: false |
||||||
|
is_grouped: false |
||||||
|
group_info: |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
identifier: '' |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: { } |
||||||
|
entity_type: file |
||||||
|
entity_field: status |
||||||
|
plugin_id: file_status |
||||||
|
sorts: |
||||||
|
fid: |
||||||
|
id: fid |
||||||
|
table: file_managed |
||||||
|
field: fid |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
order: ASC |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
label: '' |
||||||
|
entity_type: file |
||||||
|
entity_field: fid |
||||||
|
plugin_id: standard |
||||||
|
header: { } |
||||||
|
footer: { } |
||||||
|
empty: { } |
||||||
|
relationships: { } |
||||||
|
arguments: { } |
||||||
|
display_extenders: { } |
||||||
|
show_admin_links: false |
||||||
|
cache_metadata: |
||||||
|
max-age: -1 |
||||||
|
contexts: |
||||||
|
- 'languages:language_content' |
||||||
|
- 'languages:language_interface' |
||||||
|
- url.query_args |
||||||
|
tags: { } |
||||||
|
original_files_audio: |
||||||
|
display_plugin: entity_reference |
||||||
|
id: original_files_audio |
||||||
|
display_title: 'Original File: Audio' |
||||||
|
position: 1 |
||||||
|
display_options: |
||||||
|
display_extenders: { } |
||||||
|
style: |
||||||
|
type: entity_reference |
||||||
|
options: |
||||||
|
search_fields: |
||||||
|
fid: fid |
||||||
|
display_description: 'Gets the "Original File" files of Audio Media' |
||||||
|
row: |
||||||
|
type: entity_reference |
||||||
|
options: |
||||||
|
default_field_elements: false |
||||||
|
inline: { } |
||||||
|
separator: '-' |
||||||
|
hide_empty: true |
||||||
|
relationships: |
||||||
|
reverse_field_media_audio_file_media: |
||||||
|
id: reverse_field_media_audio_file_media |
||||||
|
table: file_managed |
||||||
|
field: reverse_field_media_audio_file_media |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: Audio |
||||||
|
required: true |
||||||
|
entity_type: file |
||||||
|
plugin_id: entity_reverse |
||||||
|
field_media_use: |
||||||
|
id: field_media_use |
||||||
|
table: media__field_media_use |
||||||
|
field: field_media_use |
||||||
|
relationship: reverse_field_media_audio_file_media |
||||||
|
group_type: group |
||||||
|
admin_label: 'Media Use' |
||||||
|
required: true |
||||||
|
plugin_id: standard |
||||||
|
defaults: |
||||||
|
relationships: false |
||||||
|
filters: false |
||||||
|
filter_groups: false |
||||||
|
filters: |
||||||
|
status: |
||||||
|
id: status |
||||||
|
table: file_managed |
||||||
|
field: status |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
operator: in |
||||||
|
value: |
||||||
|
1: '1' |
||||||
|
group: 1 |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
operator_id: '' |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: '' |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: '' |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
reduce: false |
||||||
|
is_grouped: false |
||||||
|
group_info: |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
identifier: '' |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: { } |
||||||
|
entity_type: file |
||||||
|
entity_field: status |
||||||
|
plugin_id: file_status |
||||||
|
field_external_uri_uri: |
||||||
|
id: field_external_uri_uri |
||||||
|
table: taxonomy_term__field_external_uri |
||||||
|
field: field_external_uri_uri |
||||||
|
relationship: field_media_use |
||||||
|
group_type: group |
||||||
|
admin_label: 'Original File' |
||||||
|
operator: '=' |
||||||
|
value: 'http://pcdm.org/use#OriginalFile' |
||||||
|
group: 1 |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
operator_id: '' |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: '' |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: '' |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
placeholder: '' |
||||||
|
is_grouped: false |
||||||
|
group_info: |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
identifier: '' |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: { } |
||||||
|
plugin_id: string |
||||||
|
filter_groups: |
||||||
|
operator: AND |
||||||
|
groups: |
||||||
|
1: AND |
||||||
|
cache_metadata: |
||||||
|
max-age: -1 |
||||||
|
contexts: |
||||||
|
- 'languages:language_content' |
||||||
|
- 'languages:language_interface' |
||||||
|
tags: { } |
||||||
|
original_files_document: |
||||||
|
display_plugin: entity_reference |
||||||
|
id: original_files_document |
||||||
|
display_title: 'Original File: Document' |
||||||
|
position: 2 |
||||||
|
display_options: |
||||||
|
display_extenders: { } |
||||||
|
style: |
||||||
|
type: entity_reference |
||||||
|
options: |
||||||
|
search_fields: |
||||||
|
fid: fid |
||||||
|
display_description: 'Gets the "Original File" files of Document Media' |
||||||
|
row: |
||||||
|
type: entity_reference |
||||||
|
options: |
||||||
|
default_field_elements: false |
||||||
|
inline: { } |
||||||
|
separator: '-' |
||||||
|
hide_empty: true |
||||||
|
relationships: |
||||||
|
reverse_field_media_document_media: |
||||||
|
id: reverse_field_media_document_media |
||||||
|
table: file_managed |
||||||
|
field: reverse_field_media_document_media |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: Document |
||||||
|
required: true |
||||||
|
entity_type: file |
||||||
|
plugin_id: entity_reverse |
||||||
|
field_media_use: |
||||||
|
id: field_media_use |
||||||
|
table: media__field_media_use |
||||||
|
field: field_media_use |
||||||
|
relationship: reverse_field_media_document_media |
||||||
|
group_type: group |
||||||
|
admin_label: 'Media Use' |
||||||
|
required: true |
||||||
|
plugin_id: standard |
||||||
|
defaults: |
||||||
|
relationships: false |
||||||
|
filters: false |
||||||
|
filter_groups: false |
||||||
|
filters: |
||||||
|
status: |
||||||
|
id: status |
||||||
|
table: file_managed |
||||||
|
field: status |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
operator: in |
||||||
|
value: |
||||||
|
1: '1' |
||||||
|
group: 1 |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
operator_id: '' |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: '' |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: '' |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
reduce: false |
||||||
|
is_grouped: false |
||||||
|
group_info: |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
identifier: '' |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: { } |
||||||
|
entity_type: file |
||||||
|
entity_field: status |
||||||
|
plugin_id: file_status |
||||||
|
field_external_uri_uri: |
||||||
|
id: field_external_uri_uri |
||||||
|
table: taxonomy_term__field_external_uri |
||||||
|
field: field_external_uri_uri |
||||||
|
relationship: field_media_use |
||||||
|
group_type: group |
||||||
|
admin_label: 'Original File' |
||||||
|
operator: '=' |
||||||
|
value: 'http://pcdm.org/use#OriginalFile' |
||||||
|
group: 1 |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
operator_id: '' |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: '' |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: '' |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
placeholder: '' |
||||||
|
is_grouped: false |
||||||
|
group_info: |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
identifier: '' |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: { } |
||||||
|
plugin_id: string |
||||||
|
filter_groups: |
||||||
|
operator: AND |
||||||
|
groups: |
||||||
|
1: AND |
||||||
|
cache_metadata: |
||||||
|
max-age: -1 |
||||||
|
contexts: |
||||||
|
- 'languages:language_content' |
||||||
|
- 'languages:language_interface' |
||||||
|
tags: { } |
||||||
|
original_files_file: |
||||||
|
display_plugin: entity_reference |
||||||
|
id: original_files_file |
||||||
|
display_title: 'Original File: File' |
||||||
|
position: 3 |
||||||
|
display_options: |
||||||
|
display_extenders: { } |
||||||
|
style: |
||||||
|
type: entity_reference |
||||||
|
options: |
||||||
|
search_fields: |
||||||
|
fid: fid |
||||||
|
display_description: 'Gets the "Original File" files of File Media' |
||||||
|
row: |
||||||
|
type: entity_reference |
||||||
|
options: |
||||||
|
default_field_elements: false |
||||||
|
inline: { } |
||||||
|
separator: '-' |
||||||
|
hide_empty: true |
||||||
|
relationships: |
||||||
|
reverse_field_media_file_media: |
||||||
|
id: reverse_field_media_file_media |
||||||
|
table: file_managed |
||||||
|
field: reverse_field_media_file_media |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: File |
||||||
|
required: true |
||||||
|
entity_type: file |
||||||
|
plugin_id: entity_reverse |
||||||
|
field_media_use: |
||||||
|
id: field_media_use |
||||||
|
table: media__field_media_use |
||||||
|
field: field_media_use |
||||||
|
relationship: reverse_field_media_file_media |
||||||
|
group_type: group |
||||||
|
admin_label: 'Media Use' |
||||||
|
required: true |
||||||
|
plugin_id: standard |
||||||
|
defaults: |
||||||
|
relationships: false |
||||||
|
filters: false |
||||||
|
filter_groups: false |
||||||
|
filters: |
||||||
|
status: |
||||||
|
id: status |
||||||
|
table: file_managed |
||||||
|
field: status |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
operator: in |
||||||
|
value: |
||||||
|
1: '1' |
||||||
|
group: 1 |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
operator_id: '' |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: '' |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: '' |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
reduce: false |
||||||
|
is_grouped: false |
||||||
|
group_info: |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
identifier: '' |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: { } |
||||||
|
entity_type: file |
||||||
|
entity_field: status |
||||||
|
plugin_id: file_status |
||||||
|
field_external_uri_uri: |
||||||
|
id: field_external_uri_uri |
||||||
|
table: taxonomy_term__field_external_uri |
||||||
|
field: field_external_uri_uri |
||||||
|
relationship: field_media_use |
||||||
|
group_type: group |
||||||
|
admin_label: 'Original File' |
||||||
|
operator: '=' |
||||||
|
value: 'http://pcdm.org/use#OriginalFile' |
||||||
|
group: 1 |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
operator_id: '' |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: '' |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: '' |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
placeholder: '' |
||||||
|
is_grouped: false |
||||||
|
group_info: |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
identifier: '' |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: { } |
||||||
|
plugin_id: string |
||||||
|
filter_groups: |
||||||
|
operator: AND |
||||||
|
groups: |
||||||
|
1: AND |
||||||
|
cache_metadata: |
||||||
|
max-age: -1 |
||||||
|
contexts: |
||||||
|
- 'languages:language_content' |
||||||
|
- 'languages:language_interface' |
||||||
|
tags: { } |
||||||
|
original_files_image: |
||||||
|
display_plugin: entity_reference |
||||||
|
id: original_files_image |
||||||
|
display_title: 'Original File: Image' |
||||||
|
position: 3 |
||||||
|
display_options: |
||||||
|
display_extenders: { } |
||||||
|
style: |
||||||
|
type: entity_reference |
||||||
|
options: |
||||||
|
search_fields: |
||||||
|
fid: fid |
||||||
|
display_description: 'Gets the "Original File" files of Image Media' |
||||||
|
row: |
||||||
|
type: entity_reference |
||||||
|
options: |
||||||
|
default_field_elements: false |
||||||
|
inline: { } |
||||||
|
separator: '-' |
||||||
|
hide_empty: true |
||||||
|
relationships: |
||||||
|
reverse_field_media_image_media: |
||||||
|
id: reverse_field_media_image_media |
||||||
|
table: file_managed |
||||||
|
field: reverse_field_media_image_media |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: Image |
||||||
|
required: true |
||||||
|
entity_type: file |
||||||
|
plugin_id: entity_reverse |
||||||
|
field_media_use: |
||||||
|
id: field_media_use |
||||||
|
table: media__field_media_use |
||||||
|
field: field_media_use |
||||||
|
relationship: reverse_field_media_image_media |
||||||
|
group_type: group |
||||||
|
admin_label: 'Media Use' |
||||||
|
required: true |
||||||
|
plugin_id: standard |
||||||
|
defaults: |
||||||
|
relationships: false |
||||||
|
filters: false |
||||||
|
filter_groups: false |
||||||
|
filters: |
||||||
|
status: |
||||||
|
id: status |
||||||
|
table: file_managed |
||||||
|
field: status |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
operator: in |
||||||
|
value: |
||||||
|
1: '1' |
||||||
|
group: 1 |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
operator_id: '' |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: '' |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: '' |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
reduce: false |
||||||
|
is_grouped: false |
||||||
|
group_info: |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
identifier: '' |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: { } |
||||||
|
entity_type: file |
||||||
|
entity_field: status |
||||||
|
plugin_id: file_status |
||||||
|
field_external_uri_uri: |
||||||
|
id: field_external_uri_uri |
||||||
|
table: taxonomy_term__field_external_uri |
||||||
|
field: field_external_uri_uri |
||||||
|
relationship: field_media_use |
||||||
|
group_type: group |
||||||
|
admin_label: 'Original File' |
||||||
|
operator: '=' |
||||||
|
value: 'http://pcdm.org/use#OriginalFile' |
||||||
|
group: 1 |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
operator_id: '' |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: '' |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: '' |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
placeholder: '' |
||||||
|
is_grouped: false |
||||||
|
group_info: |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
identifier: '' |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: { } |
||||||
|
plugin_id: string |
||||||
|
filter_groups: |
||||||
|
operator: AND |
||||||
|
groups: |
||||||
|
1: AND |
||||||
|
cache_metadata: |
||||||
|
max-age: -1 |
||||||
|
contexts: |
||||||
|
- 'languages:language_content' |
||||||
|
- 'languages:language_interface' |
||||||
|
tags: { } |
||||||
|
original_files_video: |
||||||
|
display_plugin: entity_reference |
||||||
|
id: original_files_video |
||||||
|
display_title: 'Original File: Video' |
||||||
|
position: 3 |
||||||
|
display_options: |
||||||
|
display_extenders: { } |
||||||
|
style: |
||||||
|
type: entity_reference |
||||||
|
options: |
||||||
|
search_fields: |
||||||
|
fid: fid |
||||||
|
display_description: 'Gets the "Original File" files of Video Media' |
||||||
|
row: |
||||||
|
type: entity_reference |
||||||
|
options: |
||||||
|
default_field_elements: false |
||||||
|
inline: { } |
||||||
|
separator: '-' |
||||||
|
hide_empty: true |
||||||
|
relationships: |
||||||
|
reverse_field_media_video_file_media: |
||||||
|
id: reverse_field_media_video_file_media |
||||||
|
table: file_managed |
||||||
|
field: reverse_field_media_video_file_media |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: Video |
||||||
|
required: true |
||||||
|
entity_type: file |
||||||
|
plugin_id: entity_reverse |
||||||
|
field_media_use: |
||||||
|
id: field_media_use |
||||||
|
table: media__field_media_use |
||||||
|
field: field_media_use |
||||||
|
relationship: reverse_field_media_video_file_media |
||||||
|
group_type: group |
||||||
|
admin_label: 'Media Use' |
||||||
|
required: true |
||||||
|
plugin_id: standard |
||||||
|
defaults: |
||||||
|
relationships: false |
||||||
|
filters: false |
||||||
|
filter_groups: false |
||||||
|
filters: |
||||||
|
status: |
||||||
|
id: status |
||||||
|
table: file_managed |
||||||
|
field: status |
||||||
|
relationship: none |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
operator: in |
||||||
|
value: |
||||||
|
1: '1' |
||||||
|
group: 1 |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
operator_id: '' |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: '' |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: '' |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
reduce: false |
||||||
|
is_grouped: false |
||||||
|
group_info: |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
identifier: '' |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: { } |
||||||
|
entity_type: file |
||||||
|
entity_field: status |
||||||
|
plugin_id: file_status |
||||||
|
field_external_uri_uri: |
||||||
|
id: field_external_uri_uri |
||||||
|
table: taxonomy_term__field_external_uri |
||||||
|
field: field_external_uri_uri |
||||||
|
relationship: field_media_use |
||||||
|
group_type: group |
||||||
|
admin_label: '' |
||||||
|
operator: '=' |
||||||
|
value: 'http://pcdm.org/use#OriginalFile' |
||||||
|
group: 1 |
||||||
|
exposed: false |
||||||
|
expose: |
||||||
|
operator_id: '' |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
use_operator: false |
||||||
|
operator: '' |
||||||
|
operator_limit_selection: false |
||||||
|
operator_list: { } |
||||||
|
identifier: '' |
||||||
|
required: false |
||||||
|
remember: false |
||||||
|
multiple: false |
||||||
|
remember_roles: |
||||||
|
authenticated: authenticated |
||||||
|
placeholder: '' |
||||||
|
is_grouped: false |
||||||
|
group_info: |
||||||
|
label: '' |
||||||
|
description: '' |
||||||
|
identifier: '' |
||||||
|
optional: true |
||||||
|
widget: select |
||||||
|
multiple: false |
||||||
|
remember: false |
||||||
|
default_group: All |
||||||
|
default_group_multiple: { } |
||||||
|
group_items: { } |
||||||
|
plugin_id: string |
||||||
|
filter_groups: |
||||||
|
operator: AND |
||||||
|
groups: |
||||||
|
1: AND |
||||||
|
cache_metadata: |
||||||
|
max-age: -1 |
||||||
|
contexts: |
||||||
|
- 'languages:language_content' |
||||||
|
- 'languages:language_interface' |
||||||
|
tags: { } |
@ -0,0 +1,25 @@ |
|||||||
|
dgi_fixity.settings: |
||||||
|
type: config_object |
||||||
|
label: 'Fixity check settings' |
||||||
|
mapping: |
||||||
|
sources: |
||||||
|
type: sequence |
||||||
|
label: 'File Selection for Fixity Checks' |
||||||
|
sequence: |
||||||
|
type: string |
||||||
|
label: 'View and Display Identifier' |
||||||
|
threshold: |
||||||
|
type: string |
||||||
|
label: 'Time elapsed between Fixity Checks' |
||||||
|
batch_size: |
||||||
|
type: integer |
||||||
|
label: 'How many files will be processed at once when performing a batch / cron job' |
||||||
|
notify_status: |
||||||
|
type: integer |
||||||
|
label: 'Notification trigger on status' |
||||||
|
notify_user: |
||||||
|
type: integer |
||||||
|
label: 'User to notify' |
||||||
|
notify_user_threshold: |
||||||
|
type: string |
||||||
|
label: 'Time elapsed between notifications' |
@ -0,0 +1,12 @@ |
|||||||
|
name: 'Fixity' |
||||||
|
description: "Performs fixity checks on files." |
||||||
|
type: module |
||||||
|
package: DGI |
||||||
|
core_version_requirement: ^8 || ^9 |
||||||
|
configure: dgi_fixity.settings |
||||||
|
dependencies: |
||||||
|
- drupal:file |
||||||
|
- drupal:media |
||||||
|
- drupal:user |
||||||
|
- drupal:views |
||||||
|
- filehash:filehash |
@ -0,0 +1,34 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/** |
||||||
|
* @file |
||||||
|
* Install hook implementations. |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements hook_requirements(). |
||||||
|
*/ |
||||||
|
function dgi_fixity_requirements($phase) { |
||||||
|
$requirements = []; |
||||||
|
if ($phase == 'runtime') { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckServiceInterface $fixity */ |
||||||
|
$fixity = \Drupal::service('dgi_fixity.fixity_check'); |
||||||
|
$stats = $fixity->stats(); |
||||||
|
$elements = []; |
||||||
|
foreach ($fixity->summary($stats) as $summary) { |
||||||
|
$elements[] = [ |
||||||
|
'#markup' => $summary, |
||||||
|
'#suffix' => '<br/>', |
||||||
|
]; |
||||||
|
} |
||||||
|
$failed = $stats['failed'] > 0; |
||||||
|
$out_to_date = $stats['periodic']['expired'] > 0; |
||||||
|
$requirements['dgi_fixity'] = [ |
||||||
|
'title' => \t('Fixity'), |
||||||
|
'value' => $failed ? \t('Error') : ($out_to_date ? \t('Out of date') : \t('Up to date')), |
||||||
|
'description' => \Drupal::service('renderer')->render($elements), |
||||||
|
'severity' => $failed ? REQUIREMENT_ERROR : ($out_to_date ? REQUIREMENT_WARNING : REQUIREMENT_OK), |
||||||
|
]; |
||||||
|
} |
||||||
|
return $requirements; |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
dgi_fixity.settings: |
||||||
|
title: 'Fixity' |
||||||
|
description: 'Configure Fixity Settings.' |
||||||
|
route_name: dgi_fixity.settings |
||||||
|
parent: 'system.admin_config_media' |
||||||
|
|
||||||
|
dgi_fixity.report: |
||||||
|
title: 'Fixity report' |
||||||
|
description: 'Report of all fixity checks.' |
||||||
|
route_name: entity.fixity_check.collection |
||||||
|
parent: 'system.admin_reports' |
@ -0,0 +1,45 @@ |
|||||||
|
dgi_fixity.entities: |
||||||
|
class: \Drupal\Core\Menu\LocalTaskDefault |
||||||
|
deriver: 'Drupal\dgi_fixity\Plugin\Derivative\FixityCheckLocalTasks' |
||||||
|
|
||||||
|
dgi_fixity.config: |
||||||
|
route_name: dgi_fixity.settings |
||||||
|
base_route: dgi_fixity.settings |
||||||
|
title: Configuration |
||||||
|
|
||||||
|
dgi_fixity.batch: |
||||||
|
route_name: dgi_fixity.batch |
||||||
|
base_route: dgi_fixity.settings |
||||||
|
title: Check |
||||||
|
|
||||||
|
dgi_fixity.generate: |
||||||
|
route_name: dgi_fixity.generate |
||||||
|
base_route: dgi_fixity.settings |
||||||
|
title: Generate |
||||||
|
|
||||||
|
entity.fixity_check.view: |
||||||
|
route_name: entity.fixity_check.canonical |
||||||
|
base_route: entity.fixity_check.canonical |
||||||
|
title: View |
||||||
|
|
||||||
|
entity.fixity_check.edit: |
||||||
|
route_name: entity.fixity_check.edit_form |
||||||
|
base_route: entity.fixity_check.canonical |
||||||
|
title: Edit |
||||||
|
|
||||||
|
entity.fixity_check.delete_form: |
||||||
|
route_name: entity.fixity_check.delete_form |
||||||
|
base_route: entity.fixity_check.canonical |
||||||
|
weight: 10 |
||||||
|
title: Delete |
||||||
|
|
||||||
|
entity.fixity_check.revision: |
||||||
|
route_name: entity.fixity_check.revision |
||||||
|
base_route: entity.fixity_check.revision |
||||||
|
title: Revision |
||||||
|
|
||||||
|
entity.fixity_check.revision.delete_form: |
||||||
|
route_name: entity.fixity_check.revision_delete_confirm |
||||||
|
base_route: entity.fixity_check.revision |
||||||
|
weight: 10 |
||||||
|
title: Delete |
@ -0,0 +1,233 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/** |
||||||
|
* @file |
||||||
|
* General hook implementations. |
||||||
|
*/ |
||||||
|
|
||||||
|
use Drupal\Core\Cache\Cache; |
||||||
|
use Drupal\Core\Config\FileStorage; |
||||||
|
use Drupal\Core\Config\InstallStorage; |
||||||
|
use Drupal\Core\Config\StorageInterface; |
||||||
|
use Drupal\Core\Entity\EntityInterface; |
||||||
|
use Drupal\Core\Mail\MailFormatHelper; |
||||||
|
use Drupal\Core\Routing\RouteMatchInterface; |
||||||
|
use Drupal\Core\Url; |
||||||
|
use Drupal\dgi_fixity\Form\SettingsForm; |
||||||
|
use Drupal\user\Entity\User; |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements hook_modules_installed(). |
||||||
|
*/ |
||||||
|
function dgi_fixity_modules_installed($modules) { |
||||||
|
// Install optional configuration for islandora / action. |
||||||
|
// This section is only entered when this module is installed prior to either |
||||||
|
// of these optional dependencies installation. |
||||||
|
// In particular the optional view: |
||||||
|
// - views.view.fixity_check_source_islandora |
||||||
|
// Which requires the following fields: |
||||||
|
// - field.storage.media.field_media_use |
||||||
|
// - field.storage.taxonomy_term.field_external_uri |
||||||
|
// Which are typically provided by `islandora_core_feature`. |
||||||
|
// All other optional configuration is for the `action` module. |
||||||
|
if (in_array('islandora_core_feature', $modules) || in_array('action', $modules)) { |
||||||
|
$optional_install_path = \Drupal::moduleHandler() |
||||||
|
->getModule('dgi_fixity') |
||||||
|
->getPath() . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY; |
||||||
|
/** @var \Drupal\Core\Config\ConfigInstallerInterface $config_installer */ |
||||||
|
$config_installer = \Drupal::service('config.installer'); |
||||||
|
$storage = new FileStorage($optional_install_path, StorageInterface::DEFAULT_COLLECTION); |
||||||
|
// This will not overwrite the existing optional configuration if already |
||||||
|
// installed. |
||||||
|
$config_installer->installOptionalConfig($storage); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements hook_mail(). |
||||||
|
*/ |
||||||
|
function dgi_fixity_mail($key, &$message, $params) { |
||||||
|
switch ($key) { |
||||||
|
case 'notify': |
||||||
|
$config = \Drupal::config(SettingsForm::CONFIG_NAME); |
||||||
|
$last = \Drupal::state()->get(SettingsForm::STATE_LAST_NOTIFICATION); |
||||||
|
|
||||||
|
if ($last !== NULL) { |
||||||
|
// If enough time has not elapsed since the last notification do not |
||||||
|
// send again. |
||||||
|
$threshold = strtotime($config->get(SettingsForm::NOTIFY_USER_THRESHOLD)); |
||||||
|
if ($last > $threshold) { |
||||||
|
$message['send'] = FALSE; |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Check if the configuration has enabled notifications. |
||||||
|
$notify_status = $config->get(SettingsForm::NOTIFY_STATUS); |
||||||
|
if ($notify_status === SettingsForm::NOTIFY_STATUS_NEVER) { |
||||||
|
$message['send'] = FALSE; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckServiceInterface $fixity */ |
||||||
|
$fixity = \Drupal::service('dgi_fixity.fixity_check'); |
||||||
|
$stats = $fixity->stats(); |
||||||
|
// Only notify if an error has occurred. |
||||||
|
if ($notify_status == SettingsForm::NOTIFY_STATUS_ERROR && $stats['failed'] === FALSE) { |
||||||
|
$message['send'] = FALSE; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
$options = ['langcode' => $message['langcode']]; |
||||||
|
$now = \Drupal::time()->getRequestTime(); |
||||||
|
$subject = \t('Fixity Check Report - @now', ['@now' => date(DATE_RFC7231, $now)], $options)->render(); |
||||||
|
$body = $fixity->summary($stats, $options); |
||||||
|
if ($stats['failed'] !== 0) { |
||||||
|
$body[] = \t( |
||||||
|
'There are failed checks which require your attention please review the current state of checks <a href="@site">here</a>.', |
||||||
|
['@site' => Url::fromRoute('entity.fixity_check.collection', [], ['absolute' => TRUE])->toString()], |
||||||
|
$options |
||||||
|
)->render(); |
||||||
|
} |
||||||
|
|
||||||
|
$message['subject'] = $subject; |
||||||
|
foreach ($body as $line) { |
||||||
|
$message['body'][] = MailFormatHelper::htmlToText($line); |
||||||
|
} |
||||||
|
|
||||||
|
// Track when the last message was sent. |
||||||
|
\Drupal::state()->set(SettingsForm::STATE_LAST_NOTIFICATION, $now); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements hook_cron(). |
||||||
|
*/ |
||||||
|
function dgi_fixity_cron() { |
||||||
|
$queued = \Drupal::time()->getRequestTime(); |
||||||
|
$settings = \Drupal::config(SettingsForm::CONFIG_NAME); |
||||||
|
$threshold = strtotime($settings->get(SettingsForm::THRESHOLD)); |
||||||
|
$sources = $settings->get(SettingsForm::SOURCES); |
||||||
|
|
||||||
|
// Update enabled periodic checks. |
||||||
|
$queue = \Drupal::queue('dgi_fixity.process_source'); |
||||||
|
foreach ($sources as $source) { |
||||||
|
// It safe to have queue processing a source multiple times, |
||||||
|
// they will steal work from each other but will not conflict. |
||||||
|
$queue->createItem($source); |
||||||
|
} |
||||||
|
|
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckStorageInterface $storage */ |
||||||
|
$storage = \Drupal::entityTypeManager()->getStorage('fixity_check'); |
||||||
|
|
||||||
|
// Queue items that exceed the current threshold. |
||||||
|
$storage->queue($queued, $threshold, 100); |
||||||
|
|
||||||
|
// Dequeued items after 6 hours assuming the check has failed. |
||||||
|
// They will be re-queued if appropriate on the next cron run. |
||||||
|
$storage->dequeue($queued - (3600 * 6)); |
||||||
|
|
||||||
|
// Send notification if appropriate. |
||||||
|
$uid = $settings->get(SettingsForm::NOTIFY_USER); |
||||||
|
$user = User::load($uid); |
||||||
|
if ($user) { |
||||||
|
\Drupal::service('plugin.manager.mail')->mail('dgi_fixity', 'notify', $user->getEmail(), $user->getPreferredLangcode(TRUE)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements hook_entity_type_alter(). |
||||||
|
*/ |
||||||
|
function dgi_fixity_entity_type_alter(array &$entity_types) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckServiceInterface $fixity */ |
||||||
|
$fixity = \Drupal::service('dgi_fixity.fixity_check'); |
||||||
|
$supported_entity_types = $fixity->fromEntityTypes(); |
||||||
|
foreach ($supported_entity_types as $entity_type_id) { |
||||||
|
$entity_type = &$entity_types[$entity_type_id]; |
||||||
|
$entity_type->setLinkTemplate('fixity-audit', "/fixity/$entity_type_id/{{$entity_type_id}}"); |
||||||
|
$entity_type->setLinkTemplate('fixity-check', "/fixity/$entity_type_id/{{$entity_type_id}}/check"); |
||||||
|
$entity_type->setFormClass('fixity-check', 'Drupal\dgi_fixity\Form\CheckForm'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements hook_entity_operation(). |
||||||
|
*/ |
||||||
|
function dgi_fixity_entity_operation(EntityInterface $entity) { |
||||||
|
$current_user = \Drupal::service('current_user'); |
||||||
|
$operations = []; |
||||||
|
if ($entity->hasLinkTemplate('fixity-audit') && $current_user->hasPermission('view fixity checks')) { |
||||||
|
$operations['fixity-audit'] = [ |
||||||
|
'title' => \t('Audit'), |
||||||
|
'weight' => 10, |
||||||
|
'url' => $entity->toUrl('fixity-audit'), |
||||||
|
]; |
||||||
|
if ($entity->hasLinkTemplate('fixity-check') && $current_user->hasPermission('administer fixity checks')) { |
||||||
|
$operations['fixity-check'] = [ |
||||||
|
'title' => \t('Check'), |
||||||
|
'weight' => 13, |
||||||
|
'url' => $entity->toUrl('fixity-check', ['query' => \Drupal::service('redirect.destination')->getAsArray()]), |
||||||
|
]; |
||||||
|
} |
||||||
|
} |
||||||
|
return $operations; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements hook_ENTITY_TYPE_insert(). |
||||||
|
*/ |
||||||
|
function dgi_fixity_file_insert(EntityInterface $entity) { |
||||||
|
// Make sure the fixity_check table contains a row for every file. |
||||||
|
\Drupal::entityTypeManager() |
||||||
|
->getStorage('fixity_check') |
||||||
|
->create([ |
||||||
|
'file' => $entity->id(), |
||||||
|
]) |
||||||
|
->save(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements hook_ENTITY_TYPE_delete(). |
||||||
|
*/ |
||||||
|
function dgi_fixity_file_delete(EntityInterface $entity) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckStorageInterface $storage */ |
||||||
|
$storage = \Drupal::entityTypeManager()->getStorage('fixity_check'); |
||||||
|
$checks = $storage->loadByProperties([ |
||||||
|
'file' => $entity->id(), |
||||||
|
]); |
||||||
|
// Remove checks for non-existent files. |
||||||
|
$storage->delete($checks); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements hook_ENTITY_TYPE_revision_create(). |
||||||
|
*/ |
||||||
|
function dgi_fixity_fixity_check_revision_create(EntityInterface $entity) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckInterface $entity*/ |
||||||
|
Cache::invalidateTags($entity->getAuditCacheTags()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements hook_ENTITY_TYPE_revision_delete(). |
||||||
|
*/ |
||||||
|
function dgi_fixity_fixity_check_revision_delete(EntityInterface $entity) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckInterface $entity*/ |
||||||
|
Cache::invalidateTags($entity->getAuditCacheTags()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements hook_help(). |
||||||
|
*/ |
||||||
|
function dgi_fixity_help($route_name, RouteMatchInterface $route_match) { |
||||||
|
switch ($route_name) { |
||||||
|
case 'help.page.dgi_fixity': |
||||||
|
case 'dgi_fixity.settings': |
||||||
|
$output = array_fill(0, 2, ['#type' => 'html_tag', '#tag' => 'p']); |
||||||
|
$output[0]['#value'] = \t( |
||||||
|
'The Fixity module validates selected files by generating hashes and comparing it against stored values produced by the <a href="@file_hash">File Hash module</a> for selected files uploaded to the site.', |
||||||
|
['@file_hash' => URL::fromRoute('help.page', ['name' => 'filehash'])->toString()], |
||||||
|
); |
||||||
|
return $output; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
administer fixity checks: |
||||||
|
title: 'Administer Fixity Checks' |
||||||
|
view fixity checks: |
||||||
|
title: 'View Fixity Checks' |
@ -0,0 +1,57 @@ |
|||||||
|
dgi_fixity.settings: |
||||||
|
path: '/admin/config/fixity' |
||||||
|
defaults: |
||||||
|
_form: '\Drupal\dgi_fixity\Form\SettingsForm' |
||||||
|
_title: 'Fixity' |
||||||
|
requirements: |
||||||
|
_permission: 'access administration pages,administer site configuration,administer fixity checks' |
||||||
|
|
||||||
|
dgi_fixity.batch: |
||||||
|
path: '/admin/config/fixity/check' |
||||||
|
defaults: |
||||||
|
_form: '\Drupal\dgi_fixity\Form\BatchForm' |
||||||
|
_title: 'Check' |
||||||
|
requirements: |
||||||
|
_permission: 'access administration pages,administer fixity checks' |
||||||
|
|
||||||
|
dgi_fixity.generate: |
||||||
|
path: '/admin/config/fixity/generate' |
||||||
|
defaults: |
||||||
|
_form: '\Drupal\dgi_fixity\Form\GenerateForm' |
||||||
|
_title: 'Generate' |
||||||
|
requirements: |
||||||
|
_permission: 'access administration pages,administer fixity checks' |
||||||
|
|
||||||
|
entity.fixity_check.revision: |
||||||
|
path: '/fixity/{fixity_check}/revisions/{fixity_check_revision}' |
||||||
|
defaults: |
||||||
|
_controller: '\Drupal\Core\Entity\Controller\EntityViewController::viewRevision' |
||||||
|
_title_callback: '\Drupal\Core\Entity\Controller\EntityController::title' |
||||||
|
requirements: |
||||||
|
_entity_access: 'fixity_check_revision.view revision' |
||||||
|
fixity_check: \d+ |
||||||
|
fixity_check_revision: \d+ |
||||||
|
options: |
||||||
|
_admin_route: TRUE |
||||||
|
parameters: |
||||||
|
fixity_check: |
||||||
|
type: entity:fixity_check |
||||||
|
fixity_check_revision: |
||||||
|
type: entity_revision:fixity_check |
||||||
|
|
||||||
|
entity.fixity_check.revision_delete_confirm: |
||||||
|
path: '/fixity/{fixity_check}/revisions/{fixity_check_revision}/delete' |
||||||
|
defaults: |
||||||
|
_form: '\Drupal\dgi_fixity\Form\RevisionDeleteForm' |
||||||
|
_title: 'Delete earlier check' |
||||||
|
requirements: |
||||||
|
_entity_access: 'fixity_check_revision.delete revision' |
||||||
|
fixity_check: \d+ |
||||||
|
fixity_check_revision: \d+ |
||||||
|
options: |
||||||
|
_admin_route: TRUE |
||||||
|
parameters: |
||||||
|
fixity_check: |
||||||
|
type: entity:fixity_check |
||||||
|
fixity_check_revision: |
||||||
|
type: entity_revision:fixity_check |
@ -0,0 +1,18 @@ |
|||||||
|
services: |
||||||
|
logger.channel.dgi_fixity: |
||||||
|
class: Drupal\Core\Logger\LoggerChannel |
||||||
|
factory: logger.factory:get |
||||||
|
arguments: ['dgi_fixity'] |
||||||
|
dgi_fixity.fixity_check: |
||||||
|
class: Drupal\dgi_fixity\FixityCheckService |
||||||
|
arguments: ['@string_translation', '@config.factory', '@entity_type.manager', '@datetime.time', '@plugin.manager.mail', '@logger.channel.dgi_fixity', '@filehash'] |
||||||
|
dgi_fixity.route_subscriber: |
||||||
|
class: Drupal\dgi_fixity\Routing\FixityCheckRouteSubscriber |
||||||
|
arguments: ['@entity_type.manager', '@dgi_fixity.fixity_check'] |
||||||
|
tags: |
||||||
|
- { name: event_subscriber } |
||||||
|
dgi_fixity.paramconverter.fixity: |
||||||
|
class: Drupal\dgi_fixity\Routing\FixityCheckConverter |
||||||
|
arguments: ['@entity_type.manager', '@entity.repository', '@dgi_fixity.fixity_check'] |
||||||
|
tags: |
||||||
|
- { name: paramconverter } |
@ -0,0 +1,54 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
/** |
||||||
|
* @file |
||||||
|
* Provide views data for file.module. |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* Implements hook_views_data_alter(). |
||||||
|
*/ |
||||||
|
function dgi_fixity_views_data_alter(&$data) { |
||||||
|
// Reverse relationship on fixity_check.file to file_managed.fid. |
||||||
|
$field_id = 'file'; |
||||||
|
$entity_type_id = 'fixity_check'; |
||||||
|
|
||||||
|
$entity_type_manager = \Drupal::entityTypeManager(); |
||||||
|
$entity_type = $entity_type_manager->getDefinition($entity_type_id); |
||||||
|
|
||||||
|
/** @var \Drupal\Core\Entity\EntityFieldManager $entity_field_manager */ |
||||||
|
$entity_field_manager = \Drupal::service('entity_field.manager'); |
||||||
|
$field_definitions = $entity_field_manager->getBaseFieldDefinitions($entity_type_id); |
||||||
|
$field_type = $field_definitions[$field_id]; |
||||||
|
|
||||||
|
// Allow relations to both the entity and revision base table. |
||||||
|
// The fixity_check entity does use data tables. |
||||||
|
$tables = [$entity_type->getBaseTable(), $entity_type->getRevisionTable()]; |
||||||
|
foreach ($tables as $table) { |
||||||
|
$group = $data[$table]['table']['group']; |
||||||
|
$pseudo_field_name = 'reverse_' . $field_type->getName() . '_' . $table; |
||||||
|
$data['file_managed'][$pseudo_field_name] = [ |
||||||
|
'real field' => $field_type->getName(), |
||||||
|
'relationship' => [ |
||||||
|
'title' => \t('@entity using @field', |
||||||
|
[ |
||||||
|
'@entity' => $entity_type->getLabel(), |
||||||
|
'@field' => $field_type->getLabel(), |
||||||
|
], |
||||||
|
), |
||||||
|
'label' => $group, |
||||||
|
'help' => \t('Relate each @entity with a @field set to the file.', |
||||||
|
[ |
||||||
|
'@entity' => $entity_type->getLabel(), |
||||||
|
'@field' => $field_type->getLabel(), |
||||||
|
], |
||||||
|
), |
||||||
|
'group' => $group, |
||||||
|
'id' => 'standard', |
||||||
|
'base' => $table, |
||||||
|
'base field' => $field_type->getName(), |
||||||
|
'relationship field' => 'fid', |
||||||
|
], |
||||||
|
]; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
services: |
||||||
|
dgi_fixity.commands.fixity_check: |
||||||
|
class: \Drupal\dgi_fixity\Commands\FixityCheck |
||||||
|
arguments: ['@string_translation', '@logger.dblog', '@entity_type.manager'] |
||||||
|
tags: |
||||||
|
- { name: drush.command } |
@ -0,0 +1,113 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Commands; |
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface; |
||||||
|
use Drupal\Core\StringTranslation\StringTranslationTrait; |
||||||
|
use Drupal\Core\StringTranslation\TranslationInterface; |
||||||
|
use Drupal\dgi_fixity\FixityCheckBatchCheck; |
||||||
|
use Drupal\dgi_fixity\FixityCheckBatchGenerate; |
||||||
|
use Drush\Commands\DrushCommands; |
||||||
|
use Psr\Log\LoggerInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Drush command to perform fixity checks. |
||||||
|
*/ |
||||||
|
class FixityCheck extends DrushCommands { |
||||||
|
|
||||||
|
use StringTranslationTrait; |
||||||
|
|
||||||
|
/** |
||||||
|
* A logger instance. |
||||||
|
* |
||||||
|
* @var \Psr\Log\LoggerInterface |
||||||
|
*/ |
||||||
|
protected $logger; |
||||||
|
|
||||||
|
/** |
||||||
|
* The entity type manager. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Entity\EntityTypeManagerInterface |
||||||
|
*/ |
||||||
|
protected $entityTypeManager; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates the drush command object. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation |
||||||
|
* The translation manager. |
||||||
|
* @param \Psr\Log\LoggerInterface $logger |
||||||
|
* A logger instance. |
||||||
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager |
||||||
|
* The entity manager. |
||||||
|
*/ |
||||||
|
public function __construct(TranslationInterface $string_translation, LoggerInterface $logger, EntityTypeManagerInterface $entity_type_manager) { |
||||||
|
parent::__construct(); |
||||||
|
$this->stringTranslation = $string_translation; |
||||||
|
$this->logger = $logger; |
||||||
|
$this->entityTypeManager = $entity_type_manager; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the periodic check flag to FALSE for all files. |
||||||
|
* |
||||||
|
* @command dgi_fixity:clear |
||||||
|
*/ |
||||||
|
public function clear() { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckStorageInterface $storage */ |
||||||
|
$storage = $this->entityTypeManager->getStorage('fixity_check'); |
||||||
|
$count = $storage->countPeriodic(); |
||||||
|
if ($this->io()->confirm("This will remove periodic checks on ${count} files, are you sure?", FALSE)) { |
||||||
|
$storage->clearPeriodic(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a fixity_check entity for all previously created files. |
||||||
|
* |
||||||
|
* @command dgi_fixity:generate |
||||||
|
*/ |
||||||
|
public function generate() { |
||||||
|
$batch = FixityCheckBatchGenerate::build(); |
||||||
|
batch_set($batch); |
||||||
|
drush_backend_batch_process(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Perform fixity checks on files. |
||||||
|
* |
||||||
|
* @option fids Comma separated list of file identifiers, or a path to a |
||||||
|
* file containing file identifiers. The file should have each |
||||||
|
* fid separated by a new line. If not specified the modules |
||||||
|
* settings for sources is used to determine which files to |
||||||
|
* check. |
||||||
|
* @option force Skip time elapsed threshold check when processing files. |
||||||
|
* |
||||||
|
* @command dgi_fixity:check |
||||||
|
*/ |
||||||
|
public function check(array $options = [ |
||||||
|
'fids' => NULL, |
||||||
|
'force' => FALSE, |
||||||
|
]) { |
||||||
|
$fids = $options['fids']; |
||||||
|
if (!is_null($fids)) { |
||||||
|
// If a file path is provided, parse it. |
||||||
|
if (is_file($fids)) { |
||||||
|
if (is_readable($fids)) { |
||||||
|
$fids = explode("\n", trim(file_get_contents($fids))); |
||||||
|
} |
||||||
|
else { |
||||||
|
$this->logger->error($this->t('Cannot read file @file', ['@file' => $fids])); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
$fids = explode(',', $fids); |
||||||
|
} |
||||||
|
} |
||||||
|
$batch = FixityCheckBatchCheck::build($fids, $options['force']); |
||||||
|
batch_set($batch); |
||||||
|
drush_backend_batch_process(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,259 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Controller; |
||||||
|
|
||||||
|
use Drupal\Core\Controller\ControllerBase; |
||||||
|
use Drupal\Core\Datetime\DateFormatterInterface; |
||||||
|
use Drupal\Core\Entity\EntityStorageInterface; |
||||||
|
use Drupal\Core\Link; |
||||||
|
use Drupal\Core\Render\RendererInterface; |
||||||
|
use Drupal\Core\Routing\RouteMatchInterface; |
||||||
|
use Drupal\Core\Url; |
||||||
|
use Drupal\dgi_fixity\FixityCheckInterface; |
||||||
|
use Drupal\dgi_fixity\FixityCheckServiceInterface; |
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Controller for fixity_check tasks. |
||||||
|
*/ |
||||||
|
class FixityCheckController extends ControllerBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* The date formatter service. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Datetime\DateFormatterInterface |
||||||
|
*/ |
||||||
|
protected $dateFormatter; |
||||||
|
|
||||||
|
/** |
||||||
|
* The renderer service. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Render\RendererInterface |
||||||
|
*/ |
||||||
|
protected $renderer; |
||||||
|
|
||||||
|
/** |
||||||
|
* The fixity check service. |
||||||
|
* |
||||||
|
* @var \Drupal\dgi_fixity\FixityCheckServiceInterface |
||||||
|
*/ |
||||||
|
protected $fixity; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs a controller for displaying fixity_check related tasks. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter |
||||||
|
* The date formatter service. |
||||||
|
* @param \Drupal\Core\Render\RendererInterface $renderer |
||||||
|
* The renderer service. |
||||||
|
* @param \Drupal\dgi_fixity\FixityCheckServiceInterface $fixity |
||||||
|
* The fixity service. |
||||||
|
*/ |
||||||
|
public function __construct(DateFormatterInterface $date_formatter, RendererInterface $renderer, FixityCheckServiceInterface $fixity) { |
||||||
|
$this->dateFormatter = $date_formatter; |
||||||
|
$this->renderer = $renderer; |
||||||
|
$this->fixity = $fixity; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public static function create(ContainerInterface $container) { |
||||||
|
return new static( |
||||||
|
$container->get('date.formatter'), |
||||||
|
$container->get('renderer'), |
||||||
|
$container->get('dgi_fixity.fixity_check'), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the audit display for the current entity. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match |
||||||
|
* A RouteMatch object. |
||||||
|
* |
||||||
|
* @return array |
||||||
|
* Array of page elements to render. |
||||||
|
*/ |
||||||
|
public function entityAudit(RouteMatchInterface $route_match) { |
||||||
|
$entity = $this->getEntityFromRouteMatch($route_match); |
||||||
|
return $this->audit($entity); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates an overview table of all revisions of a given fixity_check. |
||||||
|
* |
||||||
|
* @param \Drupal\dgi_fixity\FixityCheckInterface $fixity_check |
||||||
|
* A fixity_check entity. |
||||||
|
* |
||||||
|
* @return array |
||||||
|
* An array expected by \Drupal\Core\Render\RendererInterface::render(). |
||||||
|
*/ |
||||||
|
public function audit(FixityCheckInterface $fixity_check) { |
||||||
|
$account = $this->currentUser(); |
||||||
|
$storage = $this->entityTypeManager()->getStorage('fixity_check'); |
||||||
|
|
||||||
|
$build['#title'] = $this->t('Audit for %title', [ |
||||||
|
'%title' => $fixity_check->label(), |
||||||
|
]); |
||||||
|
|
||||||
|
$header = [ |
||||||
|
$this->t('Performed'), |
||||||
|
$this->t('State'), |
||||||
|
$this->t('Operations'), |
||||||
|
]; |
||||||
|
|
||||||
|
$canDelete = $account->hasPermission('administer fixity checks') && $fixity_check->access('delete'); |
||||||
|
|
||||||
|
$rows = []; |
||||||
|
$defaultRevision = $fixity_check->getRevisionId(); |
||||||
|
$currentRevisionDisplayed = FALSE; |
||||||
|
|
||||||
|
foreach ($this->getRevisionIds($fixity_check, $storage) as $revision_id) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckInterface $revision */ |
||||||
|
$revision = $storage->loadRevision($revision_id); |
||||||
|
|
||||||
|
// Use timestamp rather than timestamp_ago to allow for caching. |
||||||
|
$date = ($revision->wasPerformed()) ? |
||||||
|
$revision->performed->view([ |
||||||
|
'label' => 'hidden', |
||||||
|
'type' => 'timestamp', |
||||||
|
]) : |
||||||
|
$this->t('never'); |
||||||
|
|
||||||
|
$isCurrentRevision = $revision_id == $defaultRevision || (!$currentRevisionDisplayed && $revision->wasDefaultRevision()); |
||||||
|
if (!$isCurrentRevision) { |
||||||
|
$link = Link::fromTextAndUrl($date, new Url( |
||||||
|
'entity.fixity_check.revision', |
||||||
|
[ |
||||||
|
'fixity_check' => $fixity_check->id(), |
||||||
|
'fixity_check_revision' => $revision_id, |
||||||
|
] |
||||||
|
)); |
||||||
|
} |
||||||
|
else { |
||||||
|
$link = $fixity_check->toLink($date); |
||||||
|
$currentRevisionDisplayed = TRUE; |
||||||
|
} |
||||||
|
|
||||||
|
$state = $revision->state->view([ |
||||||
|
'label' => 'hidden', |
||||||
|
'type' => 'dgi_fixity_state', |
||||||
|
]); |
||||||
|
|
||||||
|
$row = [ |
||||||
|
[ |
||||||
|
'data' => $link->toRenderable(), |
||||||
|
], |
||||||
|
[ |
||||||
|
'data' => $state, |
||||||
|
], |
||||||
|
]; |
||||||
|
|
||||||
|
if ($isCurrentRevision) { |
||||||
|
$row[] = [ |
||||||
|
'data' => [ |
||||||
|
'#prefix' => '<em>', |
||||||
|
'#markup' => $this->t('Current revision'), |
||||||
|
'#suffix' => '</em>', |
||||||
|
], |
||||||
|
]; |
||||||
|
|
||||||
|
$rows[] = [ |
||||||
|
'data' => $row, |
||||||
|
'class' => ['revision-current'], |
||||||
|
]; |
||||||
|
} |
||||||
|
else { |
||||||
|
$links = []; |
||||||
|
|
||||||
|
if ($canDelete) { |
||||||
|
$links['delete'] = [ |
||||||
|
'title' => $this->t('Delete'), |
||||||
|
'url' => Url::fromRoute( |
||||||
|
'entity.fixity_check.revision_delete_confirm', |
||||||
|
[ |
||||||
|
'fixity_check' => $fixity_check->id(), |
||||||
|
'fixity_check_revision' => $revision_id, |
||||||
|
] |
||||||
|
), |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
$row[] = [ |
||||||
|
'data' => [ |
||||||
|
'#type' => 'operations', |
||||||
|
'#links' => $links, |
||||||
|
], |
||||||
|
]; |
||||||
|
|
||||||
|
$rows[] = $row; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$build['fixity_check_revisions_table'] = [ |
||||||
|
'#theme' => 'table', |
||||||
|
'#rows' => $rows, |
||||||
|
'#header' => $header, |
||||||
|
'#attributes' => [ |
||||||
|
'class' => 'fixity_check-revision-table', |
||||||
|
], |
||||||
|
]; |
||||||
|
$build['pager'] = [ |
||||||
|
'#type' => 'pager', |
||||||
|
]; |
||||||
|
|
||||||
|
$build['#cache'] = [ |
||||||
|
'keys' => [ |
||||||
|
'entity_view', 'fixity_check', $fixity_check->id(), 'revisions', |
||||||
|
], |
||||||
|
'contexts' => [ |
||||||
|
// Date displayed varies by timezone. |
||||||
|
'timezone', |
||||||
|
], |
||||||
|
'tags' => $fixity_check->getAuditCacheTags(), |
||||||
|
'bin' => 'render', |
||||||
|
]; |
||||||
|
$this->renderer->addCacheableDependency($build, $fixity_check); |
||||||
|
return $build; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets a list of fixity_check revision IDs for a given fixity_check. |
||||||
|
* |
||||||
|
* @param \Drupal\dgi_fixity\FixityCheckInterface $fixity_check |
||||||
|
* Media entity to search for revisions. |
||||||
|
* @param \Drupal\Core\Entity\EntityStorageInterface $storage |
||||||
|
* Media storage to load revisions from. |
||||||
|
* |
||||||
|
* @return int[] |
||||||
|
* fixity_check revision IDs in descending order. |
||||||
|
*/ |
||||||
|
protected function getRevisionIds(FixityCheckInterface $fixity_check, EntityStorageInterface $storage) { |
||||||
|
$result = $storage->getQuery() |
||||||
|
->allRevisions() |
||||||
|
->condition('id', $fixity_check->id()) |
||||||
|
->sort('performed', 'DESC') |
||||||
|
->pager(50) |
||||||
|
->execute(); |
||||||
|
return array_keys($result); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves entity from route match. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match |
||||||
|
* The route match. |
||||||
|
* |
||||||
|
* @return \Drupal\dgi_fixity\FixityCheckInterface|null |
||||||
|
* The fixity check entity from the passed-in route match. |
||||||
|
*/ |
||||||
|
protected function getEntityFromRouteMatch(RouteMatchInterface $route_match): ?FixityCheckInterface { |
||||||
|
// Option added by Route Subscriber. |
||||||
|
$parameter_name = $route_match->getRouteObject()->getOption('_fixity_entity_type_id'); |
||||||
|
return ($parameter_name == 'fixity_check') ? |
||||||
|
$route_match->getParameter($parameter_name) : |
||||||
|
$this->fixity->fromEntity($route_match->getParameter($parameter_name)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,343 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Entity; |
||||||
|
|
||||||
|
use Drupal\Core\Entity\ContentEntityBase; |
||||||
|
use Drupal\Core\Entity\EntityStorageInterface; |
||||||
|
use Drupal\Core\Entity\EntityTypeInterface; |
||||||
|
use Drupal\Core\Field\BaseFieldDefinition; |
||||||
|
use Drupal\Core\Language\LanguageInterface; |
||||||
|
use Drupal\Core\StringTranslation\StringTranslationTrait; |
||||||
|
use Drupal\dgi_fixity\FixityCheckInterface; |
||||||
|
use Drupal\file\Entity\File; |
||||||
|
|
||||||
|
/** |
||||||
|
* Defines the fixity_check entity class. |
||||||
|
* |
||||||
|
* @ContentEntityType( |
||||||
|
* id = "fixity_check", |
||||||
|
* label = @Translation("Fixity Check"), |
||||||
|
* label_collection = @Translation("Audit"), |
||||||
|
* label_singular = @Translation("Fixity Check"), |
||||||
|
* label_plural = @Translation("Fixity Checks"), |
||||||
|
* label_count = @PluralTranslation( |
||||||
|
* singular = "@count Fixity Check", |
||||||
|
* plural = "@count Fixity Checks" |
||||||
|
* ), |
||||||
|
* handlers = { |
||||||
|
* "storage" = "Drupal\dgi_fixity\FixityCheckStorage", |
||||||
|
* "storage_schema" = "Drupal\dgi_fixity\FixityCheckStorageSchema", |
||||||
|
* "list_builder" = "Drupal\dgi_fixity\FixityCheckListBuilder", |
||||||
|
* "views_data" = "Drupal\dgi_fixity\FixityCheckViewsData", |
||||||
|
* "access" = "Drupal\dgi_fixity\FixityCheckAccessControlHandler", |
||||||
|
* "form" = { |
||||||
|
* "edit" = "Drupal\Core\Entity\ContentEntityForm", |
||||||
|
* "fixity-check" = "Drupal\dgi_fixity\Form\CheckForm", |
||||||
|
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm", |
||||||
|
* "delete-multiple-confirm" = "Drupal\Core\Entity\Form\DeleteMultipleForm", |
||||||
|
* }, |
||||||
|
* "route_provider" = { |
||||||
|
* "html" = "Drupal\dgi_fixity\Routing\FixityCheckRouteProvider" |
||||||
|
* }, |
||||||
|
* }, |
||||||
|
* base_table = "fixity_check", |
||||||
|
* revision_table = "fixity_check_revision", |
||||||
|
* show_revision_ui = FALSE, |
||||||
|
* translatable = FALSE, |
||||||
|
* common_reference_target = FALSE, |
||||||
|
* entity_keys = { |
||||||
|
* "id" = "id", |
||||||
|
* "revision" = "revision_id", |
||||||
|
* }, |
||||||
|
* links = { |
||||||
|
* "canonical" = "/fixity/{fixity_check}", |
||||||
|
* "edit-form" = "/fixity/{fixity_check}/edit", |
||||||
|
* "fixity-audit" = "/fixity/{fixity_check}/audit", |
||||||
|
* "fixity-check" = "/fixity/{fixity_check}/check", |
||||||
|
* "delete-form" = "/fixity/{fixity_check}/delete", |
||||||
|
* "delete-multiple-form" = "/fixity/delete", |
||||||
|
* "collection" = "/admin/reports/fixity", |
||||||
|
* }, |
||||||
|
* admin_permission = "administer fixity checks", |
||||||
|
* ) |
||||||
|
*/ |
||||||
|
class FixityCheck extends ContentEntityBase implements FixityCheckInterface { |
||||||
|
|
||||||
|
use StringTranslationTrait; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { |
||||||
|
$fields = parent::baseFieldDefinitions($entity_type); |
||||||
|
|
||||||
|
$fields['file'] = BaseFieldDefinition::create('entity_reference') |
||||||
|
->setLabel(\t('File')) |
||||||
|
->setDescription(\t('The file entity the fixity check was performed against.')) |
||||||
|
->setRequired(TRUE) |
||||||
|
->setRevisionable(FALSE) |
||||||
|
->setTranslatable(FALSE) |
||||||
|
// It's not possible to have two fixity checks for the same file, they |
||||||
|
// should result in different versions of the same fixity_check entity. |
||||||
|
->addConstraint('UniqueFieldEntityReference') |
||||||
|
->setSetting('target_type', 'file') |
||||||
|
->setDisplayConfigurable('view', FALSE) |
||||||
|
->setDisplayOptions('view', [ |
||||||
|
'type' => 'dgi_fixity_file_reference', |
||||||
|
'weight' => 0, |
||||||
|
]); |
||||||
|
|
||||||
|
$fields['state'] = BaseFieldDefinition::create('integer') |
||||||
|
->setLabel(\t('State')) |
||||||
|
->setDescription(\t('A flag indicating the state of the whether the check passed or not.')) |
||||||
|
->setTranslatable(FALSE) |
||||||
|
->setRevisionable(TRUE) |
||||||
|
->setInitialValue(static::STATE_UNDEFINED) |
||||||
|
->setDefaultValue(static::STATE_UNDEFINED) |
||||||
|
// Define this via an options provider once. |
||||||
|
// https://www.drupal.org/node/2329937 is completed. |
||||||
|
->addPropertyConstraints('value', [ |
||||||
|
'AllowedValues' => ['callback' => static::class . '::getAllowedStates'], |
||||||
|
]) |
||||||
|
->setDisplayConfigurable('view', FALSE) |
||||||
|
->setDisplayOptions('view', [ |
||||||
|
'type' => 'dgi_fixity_state', |
||||||
|
'weight' => 1, |
||||||
|
]); |
||||||
|
|
||||||
|
// A value of 0 indicates the check was never performed even though it is |
||||||
|
// a unix-timestamp, which means it is technically 1970-01-01 00:00:00. |
||||||
|
$fields['performed'] = BaseFieldDefinition::create('timestamp') |
||||||
|
->setLabel(t('Performed')) |
||||||
|
->setDescription(t('The time the check was performed, 0 if never performed.')) |
||||||
|
->setTranslatable(FALSE) |
||||||
|
->setRevisionable(TRUE) |
||||||
|
->setInitialValue(0) |
||||||
|
->setDefaultValue(0) |
||||||
|
->setDisplayConfigurable('view', FALSE) |
||||||
|
->setDisplayOptions('view', [ |
||||||
|
'type' => 'timestamp_ago', |
||||||
|
'weight' => 2, |
||||||
|
]); |
||||||
|
|
||||||
|
$fields['periodic'] = BaseFieldDefinition::create('boolean') |
||||||
|
->setLabel(t('Periodic')) |
||||||
|
->setDescription(t('Enable/disable periodic fixity checks.')) |
||||||
|
->setRequired(TRUE) |
||||||
|
->setTranslatable(FALSE) |
||||||
|
->setRevisionable(FALSE) |
||||||
|
->setInitialValue(FALSE) |
||||||
|
->setDefaultValue(FALSE) |
||||||
|
->setDisplayConfigurable('view', FALSE) |
||||||
|
->setDisplayOptions('form', [ |
||||||
|
'type' => 'options_buttons', |
||||||
|
'weight' => 3, |
||||||
|
]) |
||||||
|
->setDisplayOptions('view', [ |
||||||
|
'type' => 'boolean', |
||||||
|
'weight' => 3, |
||||||
|
]); |
||||||
|
|
||||||
|
$fields['queued'] = BaseFieldDefinition::create('timestamp') |
||||||
|
->setLabel(t('Queued')) |
||||||
|
->setDescription(t('Time when this file was queued for fixity, 0 if not queued.')) |
||||||
|
->setTranslatable(FALSE) |
||||||
|
->setRevisionable(FALSE) |
||||||
|
->setInitialValue(0) |
||||||
|
->setDefaultValue(0) |
||||||
|
->setDisplayConfigurable('view', FALSE) |
||||||
|
->setDisplayOptions('view', [ |
||||||
|
'region' => 'hidden', |
||||||
|
]); |
||||||
|
|
||||||
|
return $fields; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function preSave(EntityStorageInterface $storage) { |
||||||
|
parent::preSave($storage); |
||||||
|
// Disallow changes to state / performed on existing revisions. |
||||||
|
// Unless this is the first revision and it the check was never performed. |
||||||
|
if (!$this->isNewRevision() && !($this->isLatestRevision() && !$this->original->wasPerformed())) { |
||||||
|
$immutable_fields = ['state', 'performed']; |
||||||
|
foreach ($immutable_fields as $field) { |
||||||
|
if ($this->{$field}->hasAffectingChanges($this->original->{$field}, LanguageInterface::LANGCODE_NOT_SPECIFIED)) { |
||||||
|
throw new \LogicException("Entity type {$this->getEntityTypeId()} does not support modifying the '{$field}' field of existing revisions."); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// The file field is immutable after creation. |
||||||
|
if ($this->original && $this->file->hasAffectingChanges($this->original->file, LanguageInterface::LANGCODE_NOT_SPECIFIED)) { |
||||||
|
throw new \LogicException("Entity type {$this->getEntityTypeId()} does not support modifying the file field after creation."); |
||||||
|
} |
||||||
|
// If performed has changed force queued to zero. |
||||||
|
if ($this->original && $this->performed->hasAffectingChanges($this->original->performed, LanguageInterface::LANGCODE_NOT_SPECIFIED)) { |
||||||
|
$this->setQueued(0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) { |
||||||
|
parent::preSaveRevision($storage, $record); |
||||||
|
// Disallow creating revisions if performed is not set. |
||||||
|
if (!$this->isNew() && $this->isNewRevision() && $record->performed === 0) { |
||||||
|
throw new \LogicException("Entity type {$this->getEntityTypeId()} does not support creating new revisions without where the performed field is not set."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function setNewRevision($value = TRUE) { |
||||||
|
parent::setNewRevision($value); |
||||||
|
// Reset the state and performed timestamp when creating a new revision |
||||||
|
// from an existing fixity_check. |
||||||
|
if (!$this->isNew() && $value) { |
||||||
|
$this->state = static::STATE_UNDEFINED; |
||||||
|
unset($this->performed); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getFile(): ?File { |
||||||
|
/** @var \Drupal\Core\Field\EntityReferenceFieldItemList $file */ |
||||||
|
$file = $this->file; |
||||||
|
return $file->isEmpty() ? NULL : $file->referencedEntities()[0]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function setFile(File $file): FixityCheckInterface { |
||||||
|
$this->set('file', $file); |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getState(): int { |
||||||
|
return $this->state->value; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function setState(int $state): FixityCheckInterface { |
||||||
|
if (!in_array($state, static::getAllowedStates())) { |
||||||
|
throw new \InvalidArgumentException("Invalid state '$state' has been given"); |
||||||
|
} |
||||||
|
$this->set('state', $state); |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getStateLabel(): string { |
||||||
|
return static::getStateProperty($this->getState(), 'label'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public static function getStateProperty(int $state, string $property) { |
||||||
|
return static::STATES[$state][$property] ?? NULL; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function passed(): bool { |
||||||
|
$state = $this->getState(); |
||||||
|
return static::STATES[$state]['passed'] ?? FALSE; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getPeriodic(): bool { |
||||||
|
return $this->periodic->value; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function setPeriodic(bool $periodic): FixityCheckInterface { |
||||||
|
$this->set('periodic', $periodic); |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getPerformed(): int { |
||||||
|
return $this->performed->value; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function setPerformed(int $performed): FixityCheckInterface { |
||||||
|
$this->set('performed', $performed); |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function wasPerformed(): bool { |
||||||
|
return $this->getPerformed() !== 0; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getQueued(): int { |
||||||
|
return $this->queued->value; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function setQueued(int $queued): FixityCheckInterface { |
||||||
|
$this->set('queued', $queued); |
||||||
|
return $this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function label() { |
||||||
|
$file = $this->getFile(); |
||||||
|
return ($file === NULL) ? |
||||||
|
$this->t('Fixity Check') : |
||||||
|
$file->label(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getAuditCacheTags() { |
||||||
|
return [ |
||||||
|
'fixity_check:' . $this->id() . ':revisions_list', |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Defines allowed states for AllowedValues constraints. |
||||||
|
* |
||||||
|
* @return int[] |
||||||
|
* The allowed states. |
||||||
|
*/ |
||||||
|
public static function getAllowedStates() { |
||||||
|
return array_keys(static::STATES); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,50 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity; |
||||||
|
|
||||||
|
use Drupal\Core\Access\AccessResult; |
||||||
|
use Drupal\Core\Entity\EntityAccessControlHandler; |
||||||
|
use Drupal\Core\Entity\EntityInterface; |
||||||
|
use Drupal\Core\Session\AccountInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Defines an access control handler for fixity_check entities. |
||||||
|
*/ |
||||||
|
class FixityCheckAccessControlHandler extends EntityAccessControlHandler { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckInterface $entity */ |
||||||
|
$admin_permission = $this->entityType->getAdminPermission(); |
||||||
|
|
||||||
|
switch ($operation) { |
||||||
|
case 'view': |
||||||
|
case 'view revision': |
||||||
|
return AccessResult::allowedIfHasPermission($account, 'view fixity checks')->cachePerPermissions(); |
||||||
|
|
||||||
|
case 'delete': |
||||||
|
return AccessResult::allowedIfHasPermission($account, $admin_permission)->cachePerPermissions(); |
||||||
|
|
||||||
|
case 'delete revision': |
||||||
|
// Not possible to delete the default revision, instead the user |
||||||
|
// should delete the actual entity. |
||||||
|
if ($entity->isDefaultRevision()) { |
||||||
|
return AccessResult::forbidden()->addCacheableDependency($entity); |
||||||
|
} |
||||||
|
return AccessResult::allowedIfHasPermission($account, $admin_permission)->cachePerPermissions(); |
||||||
|
|
||||||
|
default: |
||||||
|
return AccessResult::forbidden()->cachePerPermissions(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { |
||||||
|
return AccessResult::allowedIfHasPermission($account, $this->entityType->getAdminPermission()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,286 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity; |
||||||
|
|
||||||
|
use Drupal\Core\Batch\BatchBuilder; |
||||||
|
use Drupal\Core\StringTranslation\PluralTranslatableMarkup; |
||||||
|
use Drupal\dgi_fixity\Form\SettingsForm; |
||||||
|
|
||||||
|
/** |
||||||
|
* Performs fixity checks. |
||||||
|
*/ |
||||||
|
class FixityCheckBatchCheck { |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a batch for performing fixity checks. |
||||||
|
* |
||||||
|
* @param int[] $fids |
||||||
|
* A list of file identifiers, if not specified files with periodic checks |
||||||
|
* enabled will be selected. |
||||||
|
* @param bool $force |
||||||
|
* A flag to indicate if the check should be performed even if the time |
||||||
|
* elapsed since the last check has not exceed the required threshold. |
||||||
|
* @param int $batch_size |
||||||
|
* The number of of files to process at a time. |
||||||
|
* If not specified it will default to the modules configuration. |
||||||
|
*/ |
||||||
|
public static function build(array $fids = NULL, bool $force = FALSE, int $batch_size = NULL) { |
||||||
|
$batch_size = is_null($batch_size) ? \Drupal::config(SettingsForm::CONFIG_NAME)->get(SettingsForm::BATCH_SIZE) : $batch_size; |
||||||
|
return is_null($fids) ? |
||||||
|
static::buildPeriodic($force, $batch_size) : |
||||||
|
static::buildFixed($fids, $force, $batch_size); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a batch for processing a fixed list of file identifiers. |
||||||
|
*/ |
||||||
|
protected static function buildFixed(array $fids, bool $force, int $batch_size) { |
||||||
|
$builder = new BatchBuilder(); |
||||||
|
return $builder |
||||||
|
->setTitle(\t('Performing checks on @count file(s)', ['@count' => count($fids)])) |
||||||
|
->setInitMessage(\t('Starting')) |
||||||
|
->setErrorMessage(\t('Batch has encountered an error')) |
||||||
|
->addOperation([static::class, 'processFixedList'], [ |
||||||
|
$fids, |
||||||
|
$force, |
||||||
|
$batch_size, |
||||||
|
]) |
||||||
|
->setFinishCallback([static::class, 'finished']) |
||||||
|
->toArray(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a batch for processing files that have periodic checks enabled. |
||||||
|
*/ |
||||||
|
public static function buildPeriodic(bool $force, int $batch_size) { |
||||||
|
$sources = \Drupal::config(SettingsForm::CONFIG_NAME)->get(SettingsForm::SOURCES); |
||||||
|
if (empty($sources)) { |
||||||
|
throw new \InvalidArgumentException("No sources specified, check the modules configuration."); |
||||||
|
} |
||||||
|
$builder = new BatchBuilder(); |
||||||
|
foreach ($sources as $source) { |
||||||
|
$builder->addOperation( |
||||||
|
[static::class, 'processSource'], |
||||||
|
[$source, $batch_size], |
||||||
|
); |
||||||
|
} |
||||||
|
return $builder |
||||||
|
->setTitle(\t('Enumerating periodic checks from @count Source(s)', ['@count' => count($sources)])) |
||||||
|
->setInitMessage(\t('Starting')) |
||||||
|
->setErrorMessage(\t('Batch has encountered an error')) |
||||||
|
->addOperation([static::class, 'processPeriodic'], [$force, $batch_size]) |
||||||
|
->setFinishCallback([static::class, 'finished']) |
||||||
|
->toArray(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Check the given files. |
||||||
|
* |
||||||
|
* @param int[] $fids |
||||||
|
* A list of file identifiers. |
||||||
|
* @param bool $force |
||||||
|
* A flag to indicate if the check should be performed even if the time |
||||||
|
* elapsed since the last check has not exceed the required threshold. |
||||||
|
* @param int $batch_size |
||||||
|
* The amount of files each time this process runs. |
||||||
|
* @param array|object $context |
||||||
|
* Context for operations. |
||||||
|
*/ |
||||||
|
public static function processFixedList(array $fids, bool $force, int $batch_size, &$context) { |
||||||
|
$sandbox = &$context['sandbox']; |
||||||
|
$results = &$context['results']; |
||||||
|
if (!isset($sandbox['total'])) { |
||||||
|
$sandbox['offset'] = 0; |
||||||
|
$sandbox['total'] = count($fids); |
||||||
|
$results['successful'] = 0; |
||||||
|
$results['ignored'] = 0; |
||||||
|
$results['skipped'] = 0; |
||||||
|
$results['failed'] = 0; |
||||||
|
$results['errors'] = []; |
||||||
|
} |
||||||
|
$chunk = array_slice($fids, $sandbox['offset'], $batch_size); |
||||||
|
$end = min($sandbox['total'], $sandbox['offset'] + count($chunk)); |
||||||
|
$context['message'] = \t('Processing @start to @end of @total', [ |
||||||
|
'@start' => $sandbox['offset'], |
||||||
|
'@end' => $end, |
||||||
|
'@total' => $sandbox['total'], |
||||||
|
]); |
||||||
|
$files = \Drupal::service('entity_type.manager')->getStorage('file')->loadMultiple($chunk); |
||||||
|
// It is possible for non existing fids to be listed in $chunk, in such |
||||||
|
// cases this is ignored. |
||||||
|
$results['ignored'] += count(array_diff($chunk, array_keys($files))); |
||||||
|
static::check($files, $force, $results); |
||||||
|
$sandbox['offset'] = $end; |
||||||
|
$context['finished'] = $sandbox['offset'] / $sandbox['total']; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Enable periodic checks on files as returned by the give source view. |
||||||
|
*/ |
||||||
|
public static function processSource(string $source, $batch_size, &$context) { |
||||||
|
$results = &$context['results']; |
||||||
|
// Do not track success/failure on processing source, as that is done for |
||||||
|
// checks only. Errors however do get passed though. |
||||||
|
if (!isset($results['errors'])) { |
||||||
|
$results['errors'] = []; |
||||||
|
} |
||||||
|
|
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckServiceInterface $fixity */ |
||||||
|
$fixity = \Drupal::service('dgi_fixity.fixity_check'); |
||||||
|
$view = $fixity->source($source, $batch_size); |
||||||
|
$view->execute(); |
||||||
|
// Only processes those which have not already enabled periodic checks. |
||||||
|
foreach ($view->result as $row) { |
||||||
|
try { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckInterface $check */ |
||||||
|
$check = $view->field['periodic']->getEntity($row); |
||||||
|
$check->setPeriodic(TRUE); |
||||||
|
$check->save(); |
||||||
|
} |
||||||
|
catch (\Exception $e) { |
||||||
|
$results['errors'][] = \t('Encountered an exception: @exception', [ |
||||||
|
'@exception' => $e, |
||||||
|
]); |
||||||
|
// In practice exceptions in this case shouldn't arise, but if they do |
||||||
|
// exit to prevent an infinite loop by exiting the operation. |
||||||
|
$context['finished'] = 1; |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
// End when we have exhausted all inputs. |
||||||
|
$context['finished'] = count($view->result) == 0; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks all files which have enabled periodic fixity checks. |
||||||
|
* |
||||||
|
* @param bool $force |
||||||
|
* A flag to indicate if the check should be performed even if the time |
||||||
|
* elapsed since the last check has not exceed the required threshold. |
||||||
|
* @param int $batch_size |
||||||
|
* The amount of files each time this process runs. |
||||||
|
* @param array|object $context |
||||||
|
* Context for operations. |
||||||
|
*/ |
||||||
|
public static function processPeriodic(bool $force, int $batch_size, &$context) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckStorageInterface $storage */ |
||||||
|
$storage = \Drupal::entityTypeManager()->getStorage('fixity_check'); |
||||||
|
|
||||||
|
$sandbox = &$context['sandbox']; |
||||||
|
$results = &$context['results']; |
||||||
|
if (!isset($sandbox['offset'])) { |
||||||
|
$sandbox['offset'] = 0; |
||||||
|
$sandbox['remaining'] = $storage->countPeriodic(); |
||||||
|
$results['successful'] = 0; |
||||||
|
$results['ignored'] = 0; |
||||||
|
$results['skipped'] = 0; |
||||||
|
$results['failed'] = 0; |
||||||
|
$results['errors'] = $results['errors'] ?? []; |
||||||
|
} |
||||||
|
|
||||||
|
$files = $storage->getPeriodic($sandbox['offset'], $batch_size); |
||||||
|
$end = min($sandbox['total'], $sandbox['offset'] + count($files)); |
||||||
|
$context['message'] = \t('Processing @start to @end', [ |
||||||
|
'@start' => $sandbox['offset'], |
||||||
|
'@end' => $end, |
||||||
|
]); |
||||||
|
static::check($files, $force, $results); |
||||||
|
$sandbox['offset'] = $end; |
||||||
|
|
||||||
|
$remaining = $storage->countPeriodic(); |
||||||
|
$progress_halted = $sandbox['remaining'] == $remaining; |
||||||
|
$sandbox['remaining'] = $remaining; |
||||||
|
|
||||||
|
// End when we have exhausted all inputs or progress has halted. |
||||||
|
$context['finished'] = empty($files) || $progress_halted; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Performs a fixity check on the given list of files. |
||||||
|
* |
||||||
|
* @param \Drupal\file\FileInterface[] $files |
||||||
|
* A list of file identifiers. |
||||||
|
* @param bool $force |
||||||
|
* A flag to indicate if the check should be performed even if the time |
||||||
|
* elapsed since the last check has not exceed the required threshold. |
||||||
|
* @param array &$results |
||||||
|
* An associative array with the results of the fixity checks |
||||||
|
* - missing: The number of file identifiers for which no files exist. |
||||||
|
* - successful: The number of files that were successfully checked. |
||||||
|
* - errors: A list of error messages if any occurred. |
||||||
|
*/ |
||||||
|
protected static function check(array $files, bool $force, array &$results) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckServiceInterface $fixity */ |
||||||
|
$fixity = \Drupal::service('dgi_fixity.fixity_check'); |
||||||
|
foreach ($files as $file) { |
||||||
|
try { |
||||||
|
$result = $fixity->check($file, $force); |
||||||
|
if ($result instanceof FixityCheckInterface) { |
||||||
|
if ($result->passed()) { |
||||||
|
$results['successful']++; |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
// The check was not performed as the time elapsed since the last |
||||||
|
// check did not exceed the required threshold. |
||||||
|
$results['skipped']++; |
||||||
|
} |
||||||
|
} |
||||||
|
catch (\Exception $e) { |
||||||
|
$results['failed']++; |
||||||
|
$results['errors'][] = \t('Encountered an exception: @exception', [ |
||||||
|
'@exception' => $e, |
||||||
|
]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Batch Finished callback. |
||||||
|
* |
||||||
|
* @param bool $success |
||||||
|
* Success of the operation. |
||||||
|
* @param array $results |
||||||
|
* Array of results for post processing. |
||||||
|
* @param array $operations |
||||||
|
* Array of operations. |
||||||
|
*/ |
||||||
|
public static function finished($success, array $results, array $operations) { |
||||||
|
$messenger = \Drupal::messenger(); |
||||||
|
$messenger->addStatus(new PluralTranslatableMarkup( |
||||||
|
$results['successful'] + $results['ignored'] + $results['skipped'] + $results['failed'], |
||||||
|
'Processed @count item in total.', |
||||||
|
'Processed @count items in total.' |
||||||
|
)); |
||||||
|
$messenger->addStatus(new PluralTranslatableMarkup( |
||||||
|
$results['successful'], |
||||||
|
'@count was successful.', |
||||||
|
'@count were successful.', |
||||||
|
)); |
||||||
|
$messenger->addStatus(new PluralTranslatableMarkup( |
||||||
|
$results['ignored'], |
||||||
|
'@count was ignored.', |
||||||
|
'@count were ignored.', |
||||||
|
)); |
||||||
|
$messenger->addStatus(new PluralTranslatableMarkup( |
||||||
|
$results['skipped'], |
||||||
|
'@count was skipped.', |
||||||
|
'@count were skipped.', |
||||||
|
)); |
||||||
|
$messenger->addStatus(\t( |
||||||
|
'@count failed.', ['@count' => $results['failed']] |
||||||
|
)); |
||||||
|
$error_count = count($results['errors']); |
||||||
|
if ($error_count > 0) { |
||||||
|
$messenger->addMessage(new PluralTranslatableMarkup( |
||||||
|
$error_count, |
||||||
|
'@count error occurred.', |
||||||
|
'@count errors occurred.', |
||||||
|
)); |
||||||
|
foreach ($results['errors'] as $error) { |
||||||
|
$messenger->addError($error); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,117 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity; |
||||||
|
|
||||||
|
use Drupal\Core\Batch\BatchBuilder; |
||||||
|
use Drupal\Core\StringTranslation\PluralTranslatableMarkup; |
||||||
|
use Drupal\dgi_fixity\Form\SettingsForm; |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates a fixity_check for all previously created files. |
||||||
|
*/ |
||||||
|
class FixityCheckBatchGenerate { |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a batch for this service. |
||||||
|
* |
||||||
|
* @param int $batch_size |
||||||
|
* The number of of files to process at a time. |
||||||
|
* If not specified it will default to the modules configuration. |
||||||
|
*/ |
||||||
|
public static function build($batch_size = NULL) { |
||||||
|
if (is_null($batch_size)) { |
||||||
|
$batch_size = \Drupal::config(SettingsForm::CONFIG_NAME)->get(SettingsForm::BATCH_SIZE); |
||||||
|
} |
||||||
|
$builder = new BatchBuilder(); |
||||||
|
return $builder |
||||||
|
->setTitle(\t('Generating Fixity Checks for previously created files')) |
||||||
|
->setInitMessage(\t('Starting')) |
||||||
|
->setErrorMessage(\t('Batch has encountered an error')) |
||||||
|
->addOperation([static::class, 'generate'], [$batch_size]) |
||||||
|
->setFinishCallback([static::class, 'finished']) |
||||||
|
->toArray(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates fixity_check entity for previously created files. |
||||||
|
* |
||||||
|
* @param int $batch_size |
||||||
|
* The number of of files to process at a time. |
||||||
|
* @param array|object $context |
||||||
|
* Context for operations. |
||||||
|
*/ |
||||||
|
public static function generate($batch_size, &$context) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckStorageInterface $storage */ |
||||||
|
$storage = \Drupal::entityTypeManager()->getStorage('fixity_check'); |
||||||
|
|
||||||
|
$sandbox = &$context['sandbox']; |
||||||
|
$results = &$context['results']; |
||||||
|
if (!isset($results['successful'])) { |
||||||
|
$results['successful'] = 0; |
||||||
|
$results['failed'] = 0; |
||||||
|
$results['errors'] = []; |
||||||
|
$sandbox['remaining'] = $storage->countMissing(); |
||||||
|
} |
||||||
|
|
||||||
|
$files = $storage->getMissing(0, $batch_size); |
||||||
|
foreach ($files as $file) { |
||||||
|
$check = $storage->create(['file' => $file->id()]); |
||||||
|
try { |
||||||
|
$check->save(); |
||||||
|
$results['successful']++; |
||||||
|
} |
||||||
|
catch (\Exception $e) { |
||||||
|
$results['failed']++; |
||||||
|
$results['errors'][] = \t('Encountered an exception: @exception', [ |
||||||
|
'@exception' => $e, |
||||||
|
]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$remaining = $storage->countMissing(); |
||||||
|
$progress_halted = $sandbox['remaining'] == $remaining; |
||||||
|
$sandbox['remaining'] = $remaining; |
||||||
|
|
||||||
|
// End when we have exhausted all inputs or progress has halted. |
||||||
|
$context['finished'] = empty($files) || $progress_halted; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Batch Finished callback. |
||||||
|
* |
||||||
|
* @param bool $success |
||||||
|
* Success of the operation. |
||||||
|
* @param array $results |
||||||
|
* Array of results for post processing. |
||||||
|
* @param array $operations |
||||||
|
* Array of operations. |
||||||
|
*/ |
||||||
|
public static function finished($success, array $results, array $operations) { |
||||||
|
$messenger = \Drupal::messenger(); |
||||||
|
$messenger->addStatus(new PluralTranslatableMarkup( |
||||||
|
$results['successful'] + $results['failed'], |
||||||
|
'Processed @count item in total.', |
||||||
|
'Processed @count items in total.' |
||||||
|
)); |
||||||
|
$messenger->addStatus(new PluralTranslatableMarkup( |
||||||
|
$results['successful'], |
||||||
|
'@count was successful.', |
||||||
|
'@count were successful.', |
||||||
|
)); |
||||||
|
$messenger->addStatus(\t( |
||||||
|
'@count failed.', ['@count' => $results['failed']] |
||||||
|
)); |
||||||
|
$error_count = count($results['errors']); |
||||||
|
if ($error_count > 0) { |
||||||
|
$messenger->addMessage(new PluralTranslatableMarkup( |
||||||
|
$error_count, |
||||||
|
'@count error occurred.', |
||||||
|
'@count errors occurred.', |
||||||
|
)); |
||||||
|
foreach ($results['errors'] as $error) { |
||||||
|
$messenger->addError($error); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,237 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity; |
||||||
|
|
||||||
|
use Drupal\Core\Entity\ContentEntityInterface; |
||||||
|
use Drupal\Core\Entity\RevisionableInterface; |
||||||
|
use Drupal\file\Entity\File; |
||||||
|
|
||||||
|
/** |
||||||
|
* Provides an interface defining a fixity_check entity. |
||||||
|
*/ |
||||||
|
interface FixityCheckInterface extends ContentEntityInterface, RevisionableInterface { |
||||||
|
|
||||||
|
/** |
||||||
|
* The check has not been performed or is some other undefined state. |
||||||
|
*/ |
||||||
|
const STATE_UNDEFINED = 0; |
||||||
|
|
||||||
|
/** |
||||||
|
* The generated checksums match the recorded values. |
||||||
|
*/ |
||||||
|
const STATE_MATCHES = 1; |
||||||
|
|
||||||
|
/** |
||||||
|
* The generated checksums do not match the recorded values. |
||||||
|
*/ |
||||||
|
const STATE_MISMATCHES = 2; |
||||||
|
|
||||||
|
/** |
||||||
|
* The file is missing. |
||||||
|
*/ |
||||||
|
const STATE_MISSING = 3; |
||||||
|
|
||||||
|
/** |
||||||
|
* One or more checksum(s) are missing from the files recorded checksums. |
||||||
|
*/ |
||||||
|
const STATE_NO_CHECKSUM = 4; |
||||||
|
|
||||||
|
/** |
||||||
|
* One or more checksum(s) could not be generated. |
||||||
|
*/ |
||||||
|
const STATE_GENERATION_FAILED = 5; |
||||||
|
|
||||||
|
/** |
||||||
|
* Properties of each state. |
||||||
|
* |
||||||
|
* @var array |
||||||
|
* An associative array with the following properties. |
||||||
|
* - label: The label to use when displaying a single check. |
||||||
|
* - singular: The singular label to use when aggregating checks. |
||||||
|
* - plural: The plural label to use when aggregating checks. |
||||||
|
* - passed: TRUE if this state indicates the check passed FALSE otherwise. |
||||||
|
*/ |
||||||
|
const STATES = [ |
||||||
|
self::STATE_UNDEFINED => [ |
||||||
|
'label' => 'Undefined', |
||||||
|
'singular' => '@count check is undefined', |
||||||
|
'plural' => '@count checks are undefined', |
||||||
|
'passed' => FALSE, |
||||||
|
], |
||||||
|
self::STATE_MATCHES => [ |
||||||
|
'label' => 'Matched recorded values', |
||||||
|
'singular' => '@count check match the recorded checksum(s)', |
||||||
|
'plural' => '@count checks matched the recorded checksum(s)', |
||||||
|
'passed' => TRUE, |
||||||
|
], |
||||||
|
self::STATE_MISMATCHES => [ |
||||||
|
'label' => 'Did not match recorded values', |
||||||
|
'singular' => '@count check did not match the recorded checksum(s)', |
||||||
|
'plural' => '@count checks did not match the recorded checksum(s)', |
||||||
|
'passed' => FALSE, |
||||||
|
], |
||||||
|
self::STATE_MISSING => [ |
||||||
|
'label' => 'Could not be performed: File missing', |
||||||
|
'singular' => '@count file is missing and could not be checked', |
||||||
|
'plural' => '@count files are missing and could not be checked', |
||||||
|
'passed' => FALSE, |
||||||
|
], |
||||||
|
self::STATE_NO_CHECKSUM => [ |
||||||
|
'label' => 'Could not be performed: Missing recorded checksum(s)', |
||||||
|
'singular' => '@count file is missing a recorded checksum', |
||||||
|
'plural' => '@count files are missing recorded checksums', |
||||||
|
'passed' => FALSE, |
||||||
|
], |
||||||
|
self::STATE_GENERATION_FAILED => [ |
||||||
|
'label' => 'Could not be performed: Could not generate checksum(s)', |
||||||
|
'singular' => '@count check could not generate a checksum', |
||||||
|
'plural' => '@count checks could not generate checksums', |
||||||
|
'passed' => FALSE, |
||||||
|
], |
||||||
|
]; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the file this check was performed against. |
||||||
|
* |
||||||
|
* @return \Drupal\file\Entity\File |
||||||
|
* The file associated with this check or NULL if not set. |
||||||
|
*/ |
||||||
|
public function getFile(): ?File; |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the state of the check. |
||||||
|
* |
||||||
|
* @param \Drupal\file\Entity\File $file |
||||||
|
* The state of the check. |
||||||
|
* |
||||||
|
* @return $this |
||||||
|
*/ |
||||||
|
public function setFile(File $file): FixityCheckInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the state of the check. |
||||||
|
* |
||||||
|
* @return int |
||||||
|
* The state of the check. |
||||||
|
*/ |
||||||
|
public function getState(): int; |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the state of the check. |
||||||
|
* |
||||||
|
* @param int $state |
||||||
|
* The state of the check. |
||||||
|
* |
||||||
|
* @return $this |
||||||
|
* |
||||||
|
* @throws \InvalidArgumentException |
||||||
|
* If $state is not valid. |
||||||
|
*/ |
||||||
|
public function setState(int $state): FixityCheckInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the human readable representation of the state. |
||||||
|
* |
||||||
|
* @return string |
||||||
|
* The state. |
||||||
|
*/ |
||||||
|
public function getStateLabel(): string; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the given property of the given state if defined. |
||||||
|
* |
||||||
|
* @param int $state |
||||||
|
* The state of whose properties are fetched. |
||||||
|
* @param string $property |
||||||
|
* The property to get. |
||||||
|
* |
||||||
|
* @return mixed|null |
||||||
|
* The property if defined otherwise NULL. |
||||||
|
* |
||||||
|
* @see \Drupal\dgi_fixity\FixityCheckInterface::STATES |
||||||
|
*/ |
||||||
|
public static function getStateProperty(int $state, string $property); |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if the check passed. |
||||||
|
* |
||||||
|
* @see \Drupal\dgi_fixity\FixityCheckInterface::STATES |
||||||
|
* |
||||||
|
* @return bool |
||||||
|
* TRUE if the generated checksums match the recorded values, FALSE |
||||||
|
* otherwise. |
||||||
|
*/ |
||||||
|
public function passed(): bool; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the timestamp of when the check was performed. |
||||||
|
* |
||||||
|
* @return int |
||||||
|
* The timestamp of the check. 0 indicates the check was not performed. |
||||||
|
*/ |
||||||
|
public function getPerformed(): int; |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the timestamp of when the check was performed. |
||||||
|
* |
||||||
|
* @param int $performed |
||||||
|
* The timestamp when the check was performed. |
||||||
|
* |
||||||
|
* @return $this |
||||||
|
*/ |
||||||
|
public function setPerformed(int $performed): FixityCheckInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* TRUE if this check was performed, FALSE otherwise. |
||||||
|
* |
||||||
|
* @return bool |
||||||
|
* TRUE if this check was performed, FALSE otherwise. |
||||||
|
*/ |
||||||
|
public function wasPerformed(): bool; |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if periodic checks are enabled. |
||||||
|
* |
||||||
|
* @return int |
||||||
|
* TRUE if periodic checks are enabled, FALSE otherwise. |
||||||
|
*/ |
||||||
|
public function getPeriodic(): bool; |
||||||
|
|
||||||
|
/** |
||||||
|
* Enable or disable periodic checks. |
||||||
|
* |
||||||
|
* @param bool $periodic |
||||||
|
* TRUE to enable periodic checks, FALSE otherwise. |
||||||
|
* |
||||||
|
* @return $this |
||||||
|
*/ |
||||||
|
public function setPeriodic(bool $periodic): FixityCheckInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the timestamp of when the check was queued. |
||||||
|
* |
||||||
|
* @return int |
||||||
|
* The timestamp when the check was queued. |
||||||
|
* 0 indicates the check is not queued. |
||||||
|
*/ |
||||||
|
public function getQueued(): int; |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the timestamp of when the check was queued. |
||||||
|
* |
||||||
|
* @param int $queued |
||||||
|
* The timestamp when queued. |
||||||
|
* |
||||||
|
* @return $this |
||||||
|
*/ |
||||||
|
public function setQueued(int $queued): FixityCheckInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* The cache tags associated with the audit display of this entity. |
||||||
|
* |
||||||
|
* @return string[] |
||||||
|
* The cache tags. |
||||||
|
*/ |
||||||
|
public function getAuditCacheTags(); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,123 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity; |
||||||
|
|
||||||
|
use Drupal\Core\Datetime\DateFormatterInterface; |
||||||
|
use Drupal\Core\Entity\EntityInterface; |
||||||
|
use Drupal\Core\Entity\EntityListBuilder; |
||||||
|
use Drupal\Core\Entity\EntityStorageInterface; |
||||||
|
use Drupal\Core\Entity\EntityTypeInterface; |
||||||
|
use Drupal\Core\Render\RendererInterface; |
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Provides a listing of fixity check items. |
||||||
|
*/ |
||||||
|
class FixityCheckListBuilder extends EntityListBuilder { |
||||||
|
|
||||||
|
/** |
||||||
|
* The date formatter service. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Datetime\DateFormatterInterface |
||||||
|
*/ |
||||||
|
protected $dateFormatter; |
||||||
|
|
||||||
|
/** |
||||||
|
* The renderer service. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Render\RendererInterface |
||||||
|
*/ |
||||||
|
protected $renderer; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs a new FixityCheckListBuilder object. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type |
||||||
|
* The entity type definition. |
||||||
|
* @param \Drupal\Core\Entity\EntityStorageInterface $storage |
||||||
|
* The entity storage class. |
||||||
|
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter |
||||||
|
* The date formatter service. |
||||||
|
* @param \Drupal\Core\Render\RendererInterface $renderer |
||||||
|
* The renderer service. |
||||||
|
*/ |
||||||
|
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, DateFormatterInterface $date_formatter, RendererInterface $renderer) { |
||||||
|
parent::__construct($entity_type, $storage); |
||||||
|
$this->dateFormatter = $date_formatter; |
||||||
|
$this->renderer = $renderer; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { |
||||||
|
$entity_type_manager = $container->get('entity_type.manager'); |
||||||
|
return new static( |
||||||
|
$entity_type, |
||||||
|
$entity_type_manager->getStorage($entity_type->id()), |
||||||
|
$container->get('date.formatter'), |
||||||
|
$container->get('renderer'), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function buildHeader() { |
||||||
|
$header = []; |
||||||
|
$header += [ |
||||||
|
'link' => [ |
||||||
|
'data' => $this->t('Check'), |
||||||
|
], |
||||||
|
'state' => [ |
||||||
|
'data' => $this->t('State'), |
||||||
|
], |
||||||
|
'performed' => [ |
||||||
|
'data' => $this->t('Performed'), |
||||||
|
], |
||||||
|
]; |
||||||
|
return $header + parent::buildHeader(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function buildRow(EntityInterface $entity) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckInterface $entity */ |
||||||
|
$row = [ |
||||||
|
'link' => $entity->toLink(), |
||||||
|
]; |
||||||
|
|
||||||
|
$row['state']['data'] = $entity->state->view([ |
||||||
|
'label' => 'hidden', |
||||||
|
'type' => 'dgi_fixity_state', |
||||||
|
]); |
||||||
|
|
||||||
|
// Use timestamp rather than timestamp_ago to allow for caching. |
||||||
|
$row['performed']['data'] = $entity->wasPerformed() ? |
||||||
|
$entity->performed->view([ |
||||||
|
'label' => 'hidden', |
||||||
|
'type' => 'timestamp', |
||||||
|
'weight' => 1, |
||||||
|
]) : |
||||||
|
['#markup' => $this->t('never')]; |
||||||
|
|
||||||
|
return $row + parent::buildRow($entity); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
protected function getEntityIds() { |
||||||
|
$query = $this->getStorage()->getQuery() |
||||||
|
->accessCheck(TRUE) |
||||||
|
->sort('performed', 'DESC'); |
||||||
|
|
||||||
|
// Only add the pager if a limit is specified. |
||||||
|
if ($this->limit) { |
||||||
|
$query->pager($this->limit); |
||||||
|
} |
||||||
|
return $query->execute(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,402 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity; |
||||||
|
|
||||||
|
use Drupal\Component\Datetime\TimeInterface; |
||||||
|
use Drupal\Core\Config\ConfigFactoryInterface; |
||||||
|
use Drupal\Core\Entity\EntityInterface; |
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface; |
||||||
|
use Drupal\Core\Link; |
||||||
|
use Drupal\Core\Mail\MailManagerInterface; |
||||||
|
use Drupal\Core\StringTranslation\StringTranslationTrait; |
||||||
|
use Drupal\Core\StringTranslation\TranslationInterface; |
||||||
|
use Drupal\dgi_fixity\Entity\FixityCheck; |
||||||
|
use Drupal\dgi_fixity\Form\SettingsForm; |
||||||
|
use Drupal\file\Entity\File; |
||||||
|
use Drupal\file\FileInterface; |
||||||
|
use Drupal\filehash\FileHash; |
||||||
|
use Drupal\media\MediaInterface; |
||||||
|
use Drupal\views\ViewExecutable; |
||||||
|
use Drupal\views\Views; |
||||||
|
use Psr\Log\LoggerInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Decorates the FileHash services adding additional functionality. |
||||||
|
*/ |
||||||
|
class FixityCheckService implements FixityCheckServiceInterface { |
||||||
|
|
||||||
|
use StringTranslationTrait; |
||||||
|
|
||||||
|
/** |
||||||
|
* Config factory. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Config\ConfigFactoryInterface |
||||||
|
*/ |
||||||
|
protected $config; |
||||||
|
|
||||||
|
/** |
||||||
|
* The entity type manager. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Entity\EntityTypeManagerInterface |
||||||
|
*/ |
||||||
|
protected $entityTypeManager; |
||||||
|
|
||||||
|
/** |
||||||
|
* A date time instance. |
||||||
|
* |
||||||
|
* @var \Drupal\Component\Datetime\TimeInterface |
||||||
|
*/ |
||||||
|
protected $time; |
||||||
|
|
||||||
|
/** |
||||||
|
* The mail manager service. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Mail\MailManagerInterface |
||||||
|
*/ |
||||||
|
protected $mailManager; |
||||||
|
|
||||||
|
/** |
||||||
|
* The logger for this service. |
||||||
|
* |
||||||
|
* @var Psr\Log\LoggerInterface |
||||||
|
*/ |
||||||
|
protected $logger; |
||||||
|
|
||||||
|
/** |
||||||
|
* The service to decorate. |
||||||
|
* |
||||||
|
* @var \Drupal\filehash\FileHash |
||||||
|
*/ |
||||||
|
protected $filehash; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. |
||||||
|
*/ |
||||||
|
public function __construct(TranslationInterface $string_translation, ConfigFactoryInterface $config, EntityTypeManagerInterface $entity_type_manager, TimeInterface $time, MailManagerInterface $mail_manager, LoggerInterface $logger, FileHash $filehash) { |
||||||
|
$this->stringTranslation = $string_translation; |
||||||
|
$this->config = $config; |
||||||
|
$this->entityTypeManager = $entity_type_manager; |
||||||
|
$this->time = $time; |
||||||
|
$this->mailManager = $mail_manager; |
||||||
|
$this->logger = $logger; |
||||||
|
$this->filehash = $filehash; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function fromEntityTypes(): array { |
||||||
|
return [ |
||||||
|
'media', |
||||||
|
'file', |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function fromEntity(EntityInterface $entity): ?FixityCheckInterface { |
||||||
|
$entity_type_id = $entity->getEntityTypeId(); |
||||||
|
switch ($entity_type_id) { |
||||||
|
case 'media': |
||||||
|
/** @var \Drupal\media\MediaInterface $entity */ |
||||||
|
return $this->fromMedia($entity); |
||||||
|
|
||||||
|
case 'file': |
||||||
|
/** @var \Drupal\file\FileInterface $entity */ |
||||||
|
return $this->fromFile($entity); |
||||||
|
|
||||||
|
default: |
||||||
|
throw new \InvalidArgumentException("Cannot convert {$entity_type_id} to fixity_check."); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function fromFile($file): ?FixityCheckInterface { |
||||||
|
$fid = $file instanceof FileInterface ? $file->id() : (int) $file; |
||||||
|
// It is only possible to have a single fixity_check entity per-file. |
||||||
|
$results = $this->entityTypeManager->getStorage('fixity_check')->loadByProperties(['file' => $fid]); |
||||||
|
if (count($results) === 1) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckInterface $fixity_check */ |
||||||
|
$fixity_check = reset($results); |
||||||
|
} |
||||||
|
else { |
||||||
|
$fixity_check = FixityCheck::create([ |
||||||
|
'file' => $fid, |
||||||
|
]); |
||||||
|
} |
||||||
|
return $fixity_check; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function fromMedia(MediaInterface $media): ?FixityCheckInterface { |
||||||
|
$fid = $media->getSource()->getSourceFieldValue($media); |
||||||
|
return $this->fromFile($fid); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function threshold(): int { |
||||||
|
$threshold = &drupal_static(__FUNCTION__); |
||||||
|
if (is_null($threshold)) { |
||||||
|
$settings = $this->config->get(SettingsForm::CONFIG_NAME); |
||||||
|
$threshold = strtotime($settings->get(SettingsForm::THRESHOLD), $this->time->getRequestTime()); |
||||||
|
} |
||||||
|
return $threshold; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function scheduled(FixityCheckInterface $check): ?int { |
||||||
|
if ($check->getPeriodic()) { |
||||||
|
$now = time(); |
||||||
|
if ($check->wasPerformed()) { |
||||||
|
$diff = $now - $this->threshold(); |
||||||
|
return $check->getPerformed() + $diff; |
||||||
|
} |
||||||
|
// Never performed, can be performed immediately. |
||||||
|
return $now; |
||||||
|
} |
||||||
|
// Not periodic therefore not scheduled. |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function source(string $source, int $limit): ?ViewExecutable { |
||||||
|
// Only process those which have not already enabled periodic checks. |
||||||
|
[$view_id, $display_id] = explode(':', $source); |
||||||
|
$view = Views::getView($view_id); |
||||||
|
if ($view) { |
||||||
|
$view->setDisplay($display_id); |
||||||
|
$view->getDisplay()->setOption('entity_reference_options', ['limit' => $limit]); |
||||||
|
$view->addHandler($display_id, 'relationship', 'file_managed', 'reverse_file_fixity_check'); |
||||||
|
$view->addHandler( |
||||||
|
$display_id, 'filter', 'fixity_check', 'periodic', |
||||||
|
['relationship' => 'reverse_file_fixity_check', 'value' => 0], |
||||||
|
'periodic' |
||||||
|
); |
||||||
|
$view->addHandler( |
||||||
|
$display_id, 'field', 'fixity_check', 'periodic', |
||||||
|
['relationship' => 'reverse_file_fixity_check'], |
||||||
|
'periodic' |
||||||
|
); |
||||||
|
} |
||||||
|
return $view; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function check(File $file, bool $force = FALSE) { |
||||||
|
/** @var \Drupal\dgi_fixity\Entity\FixityCheckInterface[] $existing_checks */ |
||||||
|
$existing_checks = $this->entityTypeManager->getStorage('fixity_check')->loadByProperties(['file' => $file->id()]); |
||||||
|
if (empty($existing_checks)) { |
||||||
|
$check = FixityCheck::create()->setFile($file); |
||||||
|
} |
||||||
|
else { |
||||||
|
// Should only ever be at most one due to the UniqueFieldEntityReference |
||||||
|
// constraint on the file field. |
||||||
|
$check = reset($existing_checks); |
||||||
|
// Do not perform if the threshold for time since the last check has not |
||||||
|
// been exceeded. |
||||||
|
if (!$force) { |
||||||
|
if ($check->getPerformed() > $this->threshold()) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
} |
||||||
|
// Trigger a new revision (clears the performed / state fields). |
||||||
|
// If the check has never been performed before do not modify the |
||||||
|
// existing version. |
||||||
|
if ($check->wasPerformed()) { |
||||||
|
$check->setNewRevision(); |
||||||
|
} |
||||||
|
} |
||||||
|
$uri = $file->getFileUri(); |
||||||
|
// Assume success until proven untrue. |
||||||
|
$state = FixityCheck::STATE_MATCHES; |
||||||
|
// If column is set, only generate that hash. |
||||||
|
foreach ($this->filehash->algos() as $column => $algo) { |
||||||
|
// Nothing to do if the previous checksum value is not known. |
||||||
|
if (!isset($file->{$column})) { |
||||||
|
$state = FixityCheck::STATE_NO_CHECKSUM; |
||||||
|
break; |
||||||
|
} |
||||||
|
// Nothing to do if file URI is empty. |
||||||
|
if (NULL === $uri || '' === $uri || !file_exists($uri)) { |
||||||
|
$state = FixityCheck::STATE_MISSING; |
||||||
|
break; |
||||||
|
} |
||||||
|
// Unreadable files will have NULL hash values. |
||||||
|
elseif (preg_match('/^blake2b_([0-9]{3})$/', $algo, $matches)) { |
||||||
|
$hash = $this->filehash->blake2b($uri, $matches[1] / 8) ?: NULL; |
||||||
|
} |
||||||
|
else { |
||||||
|
$hash = hash_file($algo, $uri) ?: NULL; |
||||||
|
} |
||||||
|
if ($hash === NULL) { |
||||||
|
$state = FixityCheck::STATE_GENERATION_FAILED; |
||||||
|
break; |
||||||
|
} |
||||||
|
if ($file->{$column}->value !== $hash) { |
||||||
|
$state = FixityCheck::STATE_MISMATCHES; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$check->setState($state); |
||||||
|
$check->setPerformed($this->time->getRequestTime()); |
||||||
|
$check->setQueued(0); |
||||||
|
$check->save(); |
||||||
|
|
||||||
|
// Log results. |
||||||
|
$message = '@entity-type %label: %state'; |
||||||
|
$args = [ |
||||||
|
'@entity-type' => $check->getEntityType()->getSingularLabel(), |
||||||
|
'%label' => $check->label(), |
||||||
|
'%state' => $check->getStateProperty($check->getState(), 'label'), |
||||||
|
'link' => Link::createFromRoute( |
||||||
|
$this->t('View'), |
||||||
|
'entity.fixity_check.revision', |
||||||
|
[ |
||||||
|
'fixity_check' => $check->id(), |
||||||
|
'fixity_check_revision' => $check->getRevisionId(), |
||||||
|
], |
||||||
|
)->toString(), |
||||||
|
]; |
||||||
|
if ($check->passed()) { |
||||||
|
$this->logger->info($message, $args); |
||||||
|
} |
||||||
|
else { |
||||||
|
$this->logger->error($message, $args); |
||||||
|
} |
||||||
|
|
||||||
|
return $check; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function stats(): array { |
||||||
|
$storage = $this->entityTypeManager->getStorage('fixity_check'); |
||||||
|
|
||||||
|
// Group all current checks by their state. |
||||||
|
// Ignore those that have not been performed yet. |
||||||
|
$results = $storage->getAggregateQuery('AND') |
||||||
|
->condition('performed', 0, '!=') |
||||||
|
->groupBy('state') |
||||||
|
->aggregate('id', 'COUNT') |
||||||
|
->execute(); |
||||||
|
|
||||||
|
$failed = 0; |
||||||
|
$states = []; |
||||||
|
foreach ($results as $result) { |
||||||
|
$state = $result['state']; |
||||||
|
$count = $result['id_count']; |
||||||
|
$states[$state] = $count; |
||||||
|
// If there are any checks which have not 'passed', the aggregate state |
||||||
|
// of all checks is failure. |
||||||
|
if (FixityCheck::getStateProperty($state, 'passed') === FALSE) { |
||||||
|
$failed += $count; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// All active checks. |
||||||
|
$periodic = (int) $storage->getQuery('AND') |
||||||
|
->count('id') |
||||||
|
->condition('periodic', TRUE) |
||||||
|
->execute(); |
||||||
|
|
||||||
|
// All checks performed ever. |
||||||
|
$revisions = (int) $storage->getQuery('AND') |
||||||
|
->allRevisions() |
||||||
|
->count('id') |
||||||
|
->execute(); |
||||||
|
|
||||||
|
// Checks which have exceeded the threshold and should be performed again. |
||||||
|
$threshold = $this->threshold(); |
||||||
|
$current = (int) $storage->getQuery('AND') |
||||||
|
->condition('periodic', TRUE) |
||||||
|
->condition('performed', $threshold, '>=') |
||||||
|
->count('id') |
||||||
|
->execute(); |
||||||
|
|
||||||
|
// Up to date checks. |
||||||
|
$expired = $periodic - $current; |
||||||
|
|
||||||
|
return [ |
||||||
|
'periodic' => [ |
||||||
|
'total' => $periodic, |
||||||
|
'current' => $current, |
||||||
|
'expired' => $expired, |
||||||
|
], |
||||||
|
'revisions' => $revisions, |
||||||
|
'states' => $states, |
||||||
|
'failed' => $failed, |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function summary(array $stats, array $options = []): array { |
||||||
|
$summary = []; |
||||||
|
$summary[] = $this->formatPlural( |
||||||
|
$stats['revisions'], |
||||||
|
'@count check has been performed since tracking started.', |
||||||
|
'@count checks have been performed since tracking started.', |
||||||
|
[], |
||||||
|
$options |
||||||
|
); |
||||||
|
$summary[] = $this->formatPlural( |
||||||
|
$stats['periodic']['total'], |
||||||
|
'@count file is set to be checked periodically.', |
||||||
|
'@count files are set to be checked periodically.', |
||||||
|
[], |
||||||
|
$options |
||||||
|
); |
||||||
|
$summary[] = $this->formatPlural( |
||||||
|
$stats['periodic']['current'], |
||||||
|
'@count periodic check is up to date.', |
||||||
|
'@count periodic checks are up to date.', |
||||||
|
[], |
||||||
|
$options |
||||||
|
); |
||||||
|
if ($stats['periodic']['expired'] > 0) { |
||||||
|
$summary[] = $this->formatPlural( |
||||||
|
$stats['periodic']['expired'], |
||||||
|
'@count periodic check is out to date.', |
||||||
|
'@count periodic checks are out to date.', |
||||||
|
[], |
||||||
|
$options |
||||||
|
); |
||||||
|
} |
||||||
|
if ($stats['failed'] > 0) { |
||||||
|
$summary[] = $this->formatPlural( |
||||||
|
$stats['failed'], |
||||||
|
'@count check has failed.', |
||||||
|
'@count checks have failed.', |
||||||
|
[], |
||||||
|
$options |
||||||
|
); |
||||||
|
foreach ($stats['states'] as $state => $count) { |
||||||
|
$summary[] = $this->formatPlural( |
||||||
|
$count, |
||||||
|
FixityCheck::getStateProperty($state, 'singular'), |
||||||
|
FixityCheck::getStateProperty($state, 'plural'), |
||||||
|
[], |
||||||
|
$options |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
return $summary; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,136 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity; |
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityInterface; |
||||||
|
use Drupal\file\Entity\File; |
||||||
|
use Drupal\media\MediaInterface; |
||||||
|
use Drupal\views\ViewExecutable; |
||||||
|
|
||||||
|
/** |
||||||
|
* Interface for FixityCheckService. |
||||||
|
*/ |
||||||
|
interface FixityCheckServiceInterface { |
||||||
|
|
||||||
|
/** |
||||||
|
* A list of entity types which be converted into a fixity_check entity. |
||||||
|
* |
||||||
|
* @return string[] |
||||||
|
* A list of entity types which be converted into a fixity_check entity. |
||||||
|
*/ |
||||||
|
public function fromEntityTypes(): array; |
||||||
|
|
||||||
|
/** |
||||||
|
* Fetches or creates a fixity_check entity from the given media entity. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Entity\EntityInterface $entity |
||||||
|
* A media entity. |
||||||
|
* |
||||||
|
* @return \Drupal\dgi_fixity\FixityCheckInterface |
||||||
|
* The fixity_check entity for the given entity if possible NULL otherwise. |
||||||
|
*/ |
||||||
|
public function fromEntity(EntityInterface $entity): ?FixityCheckInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Fetches or creates a fixity_check entity from the given file entity. |
||||||
|
* |
||||||
|
* @param \Drupal\file\FileInterface|int $file |
||||||
|
* A file entity or file entity identifier. |
||||||
|
* |
||||||
|
* @return \Drupal\dgi_fixity\FixityCheckInterface |
||||||
|
* The fixity_check entity for the given file. |
||||||
|
*/ |
||||||
|
public function fromFile($file): ?FixityCheckInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Fetches or creates a fixity_check entity from the given media entity. |
||||||
|
* |
||||||
|
* @param \Drupal\media\MediaInterface $media |
||||||
|
* A media entity. |
||||||
|
* |
||||||
|
* @return \Drupal\dgi_fixity\FixityCheckInterface |
||||||
|
* The fixity_check entity for the given media. |
||||||
|
*/ |
||||||
|
public function fromMedia(MediaInterface $media): ?FixityCheckInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the threshold for determining if checks should be performed. |
||||||
|
* |
||||||
|
* @return int |
||||||
|
* The timestamp for the threshold relative to the current request time. |
||||||
|
*/ |
||||||
|
public function threshold(): int; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets when the given check should be performed again. |
||||||
|
* |
||||||
|
* Only periodic checks can be scheduled. |
||||||
|
* |
||||||
|
* @return int|null |
||||||
|
* The timestamp when the check should be performed again if scheduled to, |
||||||
|
* NULL otherwise. |
||||||
|
*/ |
||||||
|
public function scheduled(FixityCheckInterface $check): ?int; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the view for the given source, filtered to non-periodic files only. |
||||||
|
* |
||||||
|
* The source must comply with checks performed by this modules settings form. |
||||||
|
* This function does not validate it. |
||||||
|
* |
||||||
|
* @param string $source |
||||||
|
* The view display identifier as selected in this modules settings form. |
||||||
|
* @param int $limit |
||||||
|
* The maximum results the view should return. |
||||||
|
* |
||||||
|
* @return \Drupal\views\ViewExecutable|null |
||||||
|
* The filtered view. |
||||||
|
*/ |
||||||
|
public function source(string $source, int $limit): ?ViewExecutable; |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates a fixity_check entity from the given file. |
||||||
|
* |
||||||
|
* Either adds a new revision or creates a new fixity_check. |
||||||
|
* |
||||||
|
* @param \Drupal\file\Entity\File $file |
||||||
|
* The file to perform the fixity check against. |
||||||
|
* @param bool $force |
||||||
|
* A flag to indicate if the check should be performed even if the time |
||||||
|
* elapsed since the last check has not exceed the required threshold. |
||||||
|
* |
||||||
|
* @return \Drupal\dgi_fixity\Entity\FixityCheckInterface|null |
||||||
|
* The resulting fixity_check if successful. |
||||||
|
* NULL if the check was not performed because the time elapsed since the |
||||||
|
* last check has not exceed the required threshold. |
||||||
|
*/ |
||||||
|
public function check(File $file, bool $force = FALSE); |
||||||
|
|
||||||
|
/** |
||||||
|
* Get an associative array of statistics relating to FixityChecks. |
||||||
|
* |
||||||
|
* @return array |
||||||
|
* An associative array with the following fields: |
||||||
|
* - total: The number of active fixity checks. |
||||||
|
* - revisions: The total number of fixity checks ever performed. |
||||||
|
* - states: An associative array of states and their active counts. |
||||||
|
* - current: The number of checks that are up to date. |
||||||
|
* - expired: The number of checks that are out of date. |
||||||
|
* - failed: The number of checks in a failed state. |
||||||
|
*/ |
||||||
|
public function stats(): array; |
||||||
|
|
||||||
|
/** |
||||||
|
* Given stats provided by this service generate a summary. |
||||||
|
* |
||||||
|
* @param array $stats |
||||||
|
* The stats as returned by this service. |
||||||
|
* @param array $options |
||||||
|
* An associative array of additional options for the TranslatableMarkup. |
||||||
|
* |
||||||
|
* @return \Drupal\Core\StringTranslation\TranslatableMarkup[] |
||||||
|
* A list of messages that describe the current state of the system. |
||||||
|
*/ |
||||||
|
public function summary(array $stats, array $options = []): array; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,139 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity; |
||||||
|
|
||||||
|
use Drupal\Core\Entity\Sql\SqlContentEntityStorage; |
||||||
|
|
||||||
|
/** |
||||||
|
* File storage for files. |
||||||
|
*/ |
||||||
|
class FixityCheckStorage extends SqlContentEntityStorage implements FixityCheckStorageInterface { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function countMissing(): int { |
||||||
|
/** @var \Drupal\file\FileStorage $storage */ |
||||||
|
$storage = $this->entityTypeManager->getStorage('file'); |
||||||
|
$query = $this->database->select($storage->getBaseTable(), 'file_managed'); |
||||||
|
$query->leftJoin($this->baseTable, 'fixity_check', '[file_managed].[fid] = [fixity_check].[file]'); |
||||||
|
return $query |
||||||
|
->isNull('fixity_check.file') |
||||||
|
->countQuery() |
||||||
|
->execute() |
||||||
|
->fetchField(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getMissing(int $offset, int $limit): array { |
||||||
|
/** @var \Drupal\file\FileStorage $storage */ |
||||||
|
$storage = $this->entityTypeManager->getStorage('file'); |
||||||
|
$query = $this->database->select($storage->getBaseTable(), 'file_managed'); |
||||||
|
$query->fields('file_managed', ['fid']); |
||||||
|
$query->leftJoin($this->baseTable, 'fixity_check', '[file_managed].[fid] = [fixity_check].[file]'); |
||||||
|
$ids = $query |
||||||
|
->isNull('fixity_check.file') |
||||||
|
->orderBy('file_managed.fid') |
||||||
|
->range($offset, $limit) |
||||||
|
->execute() |
||||||
|
->fetchCol(); |
||||||
|
return $storage->loadMultiple($ids); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function clearPeriodic() { |
||||||
|
$this->database |
||||||
|
->update($this->baseTable) |
||||||
|
->fields([ |
||||||
|
'periodic' => 0, |
||||||
|
]) |
||||||
|
->execute(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function countPeriodic(): int { |
||||||
|
return $this->database |
||||||
|
->select($this->baseTable, 'c') |
||||||
|
->condition('c.periodic', 1) |
||||||
|
->countQuery() |
||||||
|
->execute() |
||||||
|
->fetchField(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getPeriodic(int $offset, int $limit): array { |
||||||
|
/** @var \Drupal\file\FileStorage $storage */ |
||||||
|
$storage = $this->entityTypeManager->getStorage('file'); |
||||||
|
$ids = $this->database |
||||||
|
->select($this->baseTable, 'c') |
||||||
|
->condition('c.periodic', 1) |
||||||
|
->fields('c', ['file']) |
||||||
|
->range($offset, $limit) |
||||||
|
->orderBy('id') |
||||||
|
->execute() |
||||||
|
->fetchCol(); |
||||||
|
return $storage->loadMultiple($ids); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function queue(int $queued, int $threshold, int $limit) { |
||||||
|
// Do not over-saturate the queue. |
||||||
|
// 10x the limit is the max we allow to be queued at a time. |
||||||
|
$queue = \Drupal::queue('dgi_fixity.fixity_check'); |
||||||
|
if ($queue->numberOfItems() > (10 * $limit)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
$query = $this->database->select($this->baseTable, 'c'); |
||||||
|
// Either never performed or out of date. |
||||||
|
$performed = $query->orConditionGroup() |
||||||
|
->condition('c.performed', 0, '=') |
||||||
|
->condition('c.performed', $threshold, '<='); |
||||||
|
|
||||||
|
// Only those which have enabled periodic checking, and are not already |
||||||
|
// queued. |
||||||
|
$ids = $query |
||||||
|
->fields('c', ['id']) |
||||||
|
->condition('c.periodic', 1) |
||||||
|
->condition('c.queued', 0) |
||||||
|
->condition($performed) |
||||||
|
->range(0, $limit) |
||||||
|
->execute() |
||||||
|
->fetchCol(); |
||||||
|
|
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckInterface $check */ |
||||||
|
foreach ($this->doLoadMultiple($ids) as $check) { |
||||||
|
// Queue checks for processing. |
||||||
|
if ($queue->createItem($check)) { |
||||||
|
// Add timestamp to avoid queueing item more than once. |
||||||
|
$check->setQueued($queued); |
||||||
|
$check->save(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function dequeue(int $queued) { |
||||||
|
$this->database |
||||||
|
->update($this->baseTable) |
||||||
|
->fields([ |
||||||
|
'queued' => 0, |
||||||
|
]) |
||||||
|
->condition('queued', 0, '<>') |
||||||
|
->condition('queued', $queued, '<') |
||||||
|
->execute(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,79 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity; |
||||||
|
|
||||||
|
use Drupal\Core\Entity\ContentEntityStorageInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Defines an interface for fixity_check entity storage classes. |
||||||
|
*/ |
||||||
|
interface FixityCheckStorageInterface extends ContentEntityStorageInterface { |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the number of files that have no related fixity_check entity. |
||||||
|
* |
||||||
|
* @return int |
||||||
|
* The number of files that have no related fixity_check entity. |
||||||
|
*/ |
||||||
|
public function countMissing(): int; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets a list of files which have no related fixity_check entity. |
||||||
|
* |
||||||
|
* @param int $offset |
||||||
|
* The offset into the list of files. |
||||||
|
* @param int $limit |
||||||
|
* The maximum number of files to return. |
||||||
|
* |
||||||
|
* @return \Drupal\file\FileInterface[] |
||||||
|
* The files selected by the given parameters. |
||||||
|
*/ |
||||||
|
public function getMissing(int $offset, int $limit): array; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the number of files that have enabled periodic checking. |
||||||
|
* |
||||||
|
* @return int |
||||||
|
* The number of files that have enabled periodic checking. |
||||||
|
*/ |
||||||
|
public function countPeriodic(): int; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets a list of files which have enabled periodic checking. |
||||||
|
* |
||||||
|
* @param int $offset |
||||||
|
* The offset into the list files. |
||||||
|
* @param int $limit |
||||||
|
* The maximum number of files to return. |
||||||
|
* |
||||||
|
* @return \Drupal\file\FileInterface[] |
||||||
|
* The file selected by the given parameters. |
||||||
|
*/ |
||||||
|
public function getPeriodic(int $offset, int $limit): array; |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the periodic check flag on all files to FALSE. |
||||||
|
*/ |
||||||
|
public function clearPeriodic(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Queues checks to be performed during cron up to at most the given limit. |
||||||
|
* |
||||||
|
* @param int $queued |
||||||
|
* The timestamp newly queued items will be recorded under. |
||||||
|
* @param int $threshold |
||||||
|
* Only queue checks which were performed before the given threshold. |
||||||
|
* @param int $limit |
||||||
|
* The number of items to queue at most. |
||||||
|
*/ |
||||||
|
public function queue(int $queued, int $threshold, int $limit); |
||||||
|
|
||||||
|
/** |
||||||
|
* Dequeues checks which have not been performed before the given timestamp. |
||||||
|
* |
||||||
|
* @param int $queued |
||||||
|
* Items which were queued before this timestamp will be dequeued. |
||||||
|
*/ |
||||||
|
public function dequeue(int $queued); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity; |
||||||
|
|
||||||
|
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema; |
||||||
|
use Drupal\Core\Field\FieldStorageDefinitionInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Defines the file schema handler. |
||||||
|
*/ |
||||||
|
class FixityCheckStorageSchema extends SqlContentEntityStorageSchema { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) { |
||||||
|
$schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping); |
||||||
|
$field_name = $storage_definition->getName(); |
||||||
|
|
||||||
|
if ($table_name == $this->storage->getBaseTable()) { |
||||||
|
switch ($field_name) { |
||||||
|
case 'file': |
||||||
|
$this->addSharedTableFieldUniqueKey($storage_definition, $schema, TRUE); |
||||||
|
$this->addSharedTableFieldForeignKey($storage_definition, $schema, 'file_managed', 'fid'); |
||||||
|
break; |
||||||
|
|
||||||
|
case 'state': |
||||||
|
case 'performed': |
||||||
|
case 'periodic': |
||||||
|
case 'queued': |
||||||
|
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE); |
||||||
|
break; |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $schema; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity; |
||||||
|
|
||||||
|
use Drupal\views\EntityViewsData; |
||||||
|
|
||||||
|
/** |
||||||
|
* Provides the views data for the fixity_check entity type. |
||||||
|
*/ |
||||||
|
class FixityCheckViewsData extends EntityViewsData { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getViewsData() { |
||||||
|
$data = parent::getViewsData(); |
||||||
|
$data['fixity_check']['table']['wizard_id'] = 'fixity_check'; |
||||||
|
$data['fixity_check']['table']['group'] = $this->t('Fixity Check'); |
||||||
|
$data['fixity_check']['table']['join'] = [ |
||||||
|
'fixity_check_revision' => [ |
||||||
|
'left_field' => 'revision_id', |
||||||
|
'field' => 'revision_id', |
||||||
|
], |
||||||
|
'file_managed' => [ |
||||||
|
'left_field' => 'fid', |
||||||
|
'field' => 'file', |
||||||
|
], |
||||||
|
]; |
||||||
|
$data['fixity_check_revision']['table']['wizard_id'] = 'fixity_check_revision'; |
||||||
|
$data['fixity_check_revision']['table']['group'] = $this->t('Fixity Check revision'); |
||||||
|
$data['fixity_check_revision']['table']['join'] = [ |
||||||
|
'fixity_check' => [ |
||||||
|
'left_field' => 'revision_id', |
||||||
|
'field' => 'revision_id', |
||||||
|
], |
||||||
|
'file_managed' => [ |
||||||
|
'left_field' => 'fid', |
||||||
|
'field' => 'file', |
||||||
|
], |
||||||
|
]; |
||||||
|
return $data; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Form; |
||||||
|
|
||||||
|
use Drupal\Core\Form\FormBase; |
||||||
|
use Drupal\Core\Form\FormStateInterface; |
||||||
|
use Drupal\dgi_fixity\FixityCheckBatchCheck; |
||||||
|
|
||||||
|
/** |
||||||
|
* Trigger a batch check of the files selected by the modules configuration. |
||||||
|
* |
||||||
|
* @internal |
||||||
|
*/ |
||||||
|
class BatchForm extends FormBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getFormId() { |
||||||
|
return 'dgi_fixity_batch_form'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function buildForm(array $form, FormStateInterface $form_state) { |
||||||
|
$form['info'] = [ |
||||||
|
'#type' => 'markup', |
||||||
|
'#markup' => $this->t('Submitting this form will perform fixity checks against all files with periodic checks enabled.<br>This will automatically be done via cron, but it can be performed manually here.'), |
||||||
|
]; |
||||||
|
$form['force'] = [ |
||||||
|
'#type' => 'checkbox', |
||||||
|
'#title' => $this->t('Skip time elapsed check'), |
||||||
|
'#description' => $this->t('If enabled, all files will be checked without regard to the time elapsed since the previous check was performed on the selected file.'), |
||||||
|
'#default' => FALSE, |
||||||
|
]; |
||||||
|
$form['actions'] = [ |
||||||
|
'#type' => 'actions', |
||||||
|
'submit' => [ |
||||||
|
'#type' => 'submit', |
||||||
|
'#value' => $this->t('Check'), |
||||||
|
'#button_type' => 'primary', |
||||||
|
], |
||||||
|
]; |
||||||
|
return $form; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function submitForm(array &$form, FormStateInterface $form_state) { |
||||||
|
$force = boolval($form_state->getValue('force')); |
||||||
|
try { |
||||||
|
$batch = FixityCheckBatchCheck::build(NULL, $force); |
||||||
|
batch_set($batch); |
||||||
|
} |
||||||
|
catch (\InvalidArgumentException $e) { |
||||||
|
$this->messenger()->addError($e->getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,160 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Form; |
||||||
|
|
||||||
|
use Drupal\Component\Datetime\TimeInterface; |
||||||
|
use Drupal\Core\Entity\ContentEntityConfirmFormBase; |
||||||
|
use Drupal\Core\Entity\EntityRepositoryInterface; |
||||||
|
use Drupal\Core\Entity\EntityTypeBundleInfoInterface; |
||||||
|
use Drupal\Core\Form\FormStateInterface; |
||||||
|
use Drupal\Core\Routing\RouteMatchInterface; |
||||||
|
use Drupal\dgi_fixity\FixityCheckInterface; |
||||||
|
use Drupal\dgi_fixity\FixityCheckServiceInterface; |
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Perform a fixity check on the given fixity_check or related entity. |
||||||
|
* |
||||||
|
* @internal |
||||||
|
*/ |
||||||
|
class CheckForm extends ContentEntityConfirmFormBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* The fixity service. |
||||||
|
* |
||||||
|
* @var \Drupal\dgi_fixity\FixityCheckServiceInterface |
||||||
|
*/ |
||||||
|
protected $fixity; |
||||||
|
|
||||||
|
/** |
||||||
|
* The entity used to derive the fixity_check entity. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Entity\ContentEntityInterface |
||||||
|
*/ |
||||||
|
protected $sourceEntity; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs the form. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository |
||||||
|
* The entity repository service. |
||||||
|
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info |
||||||
|
* The entity type bundle service. |
||||||
|
* @param \Drupal\Component\Datetime\TimeInterface $time |
||||||
|
* The time service. |
||||||
|
* @param \Drupal\dgi_fixity\FixityCheckServiceInterface $fixity |
||||||
|
* The fixity service. |
||||||
|
*/ |
||||||
|
public function __construct(EntityRepositoryInterface $entity_repository, EntityTypeBundleInfoInterface $entity_type_bundle_info, TimeInterface $time, FixityCheckServiceInterface $fixity) { |
||||||
|
parent::__construct($entity_repository, $entity_type_bundle_info, $time); |
||||||
|
$this->fixity = $fixity; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public static function create(ContainerInterface $container) { |
||||||
|
return new static( |
||||||
|
$container->get('entity.repository'), |
||||||
|
$container->get('entity_type.bundle.info'), |
||||||
|
$container->get('datetime.time'), |
||||||
|
$container->get('dgi_fixity.fixity_check') |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) { |
||||||
|
// Support checking entity types from which we can determine the associated |
||||||
|
// fixity_check. |
||||||
|
$entity = parent::getEntityFromRouteMatch($route_match, $entity_type_id); |
||||||
|
// Allow the original to affect the redirect url. For example return to the |
||||||
|
// media's audit page rather than the fixity_check's audit page. |
||||||
|
$this->sourceEntity = $entity; |
||||||
|
return ($entity instanceof FixityCheckInterface) ? |
||||||
|
$entity : |
||||||
|
$this->fixity->fromEntity($entity); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getDescription() { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckInterface $entity */ |
||||||
|
$entity = $this->getEntity(); |
||||||
|
if ($entity->wasPerformed()) { |
||||||
|
$scheduled = $this->fixity->scheduled($entity); |
||||||
|
if ($scheduled) { |
||||||
|
return $this->t(' |
||||||
|
<strong>Latest Result:</strong> %state<br/> |
||||||
|
<strong>Last Performed:</strong> %performed<br/> |
||||||
|
<strong>Next Scheduled:</strong> %scheduled |
||||||
|
', [ |
||||||
|
'%state' => $entity->getStateLabel(), |
||||||
|
'%performed' => date(DATE_RFC7231, $entity->getPerformed()), |
||||||
|
'%scheduled' => date(DATE_RFC7231, $scheduled), |
||||||
|
]); |
||||||
|
} |
||||||
|
return $this->t(' |
||||||
|
<strong>Latest Result:</strong> %state<br/> |
||||||
|
<strong>Last Performed:</strong> %performed |
||||||
|
', [ |
||||||
|
'%state' => $entity->getStateLabel(), |
||||||
|
'%performed' => date(DATE_RFC7231, $entity->getPerformed()), |
||||||
|
'%scheduled' => date(DATE_RFC7231, $scheduled), |
||||||
|
]); |
||||||
|
} |
||||||
|
return $this->t('No prior check has been performed.'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getQuestion() { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckInterface $entity */ |
||||||
|
$entity = $this->getEntity(); |
||||||
|
return $this->t('Are you sure you want to perform a check on %label?', [ |
||||||
|
'%label' => $this->getEntity()->label(), |
||||||
|
'%performed' => date(DATE_RFC7231, $entity->getPerformed()), |
||||||
|
]); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getCancelUrl() { |
||||||
|
return $this->sourceEntity->toUrl('fixity-audit'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function submitForm(array &$form, FormStateInterface $form_state) { |
||||||
|
parent::submitForm($form, $form_state); |
||||||
|
|
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckInterface $entity */ |
||||||
|
$entity = $this->getEntity(); |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckInterface $check */ |
||||||
|
$check = $this->fixity->check($entity->getFile(), TRUE); |
||||||
|
$this->setEntity($check); |
||||||
|
unset($entity); |
||||||
|
|
||||||
|
$message = $this->t('The @entity-type %label: %state.', [ |
||||||
|
'@entity-type' => $check->getEntityType()->getSingularLabel(), |
||||||
|
'%label' => $check->toLink()->toString(), |
||||||
|
'%state' => $check->getStateLabel(), |
||||||
|
]); |
||||||
|
|
||||||
|
if ($check->passed()) { |
||||||
|
$this->messenger()->addStatus($message); |
||||||
|
} |
||||||
|
else { |
||||||
|
$this->messenger()->addError($message); |
||||||
|
} |
||||||
|
|
||||||
|
// If no destination set return to the fixity check audit page. |
||||||
|
$form_state->setRedirectUrl($this->getCancelUrl()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,85 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Form; |
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface; |
||||||
|
use Drupal\Core\Form\FormBase; |
||||||
|
use Drupal\Core\Form\FormStateInterface; |
||||||
|
use Drupal\dgi_fixity\FixityCheckBatchGenerate; |
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates a fixity check entity for all previously existing files. |
||||||
|
* |
||||||
|
* @internal |
||||||
|
*/ |
||||||
|
class GenerateForm extends FormBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* The entity type manager. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Entity\EntityTypeManagerInterface |
||||||
|
*/ |
||||||
|
protected $entityTypeManager; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs the form. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager |
||||||
|
* The entity type manager. |
||||||
|
*/ |
||||||
|
public function __construct(EntityTypeManagerInterface $entity_type_manager) { |
||||||
|
$this->entityTypeManager = $entity_type_manager; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public static function create(ContainerInterface $container) { |
||||||
|
return new static( |
||||||
|
$container->get('entity_type.manager'), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getFormId() { |
||||||
|
return 'dgi_fixity_generate_form'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function buildForm(array $form, FormStateInterface $form_state) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckStorageInterface $storage */ |
||||||
|
$storage = $this->entityTypeManager->getStorage('fixity_check'); |
||||||
|
$form['info'] = [ |
||||||
|
'#type' => 'markup', |
||||||
|
'#markup' => $this->t(' |
||||||
|
<p>Submitting this form generate fixity checks for @count files.</p> |
||||||
|
<p>Generally this should only be required when the module is first installed.</p> |
||||||
|
', |
||||||
|
['@count' => $storage->countMissing()] |
||||||
|
), |
||||||
|
]; |
||||||
|
$form['actions'] = [ |
||||||
|
'#type' => 'actions', |
||||||
|
'submit' => [ |
||||||
|
'#type' => 'submit', |
||||||
|
'#value' => $this->t('Generate'), |
||||||
|
'#button_type' => 'primary', |
||||||
|
], |
||||||
|
]; |
||||||
|
return $form; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function submitForm(array &$form, FormStateInterface $form_state) { |
||||||
|
$batch = FixityCheckBatchGenerate::build(); |
||||||
|
batch_set($batch); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,132 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Form; |
||||||
|
|
||||||
|
use Drupal\Core\Datetime\DateFormatterInterface; |
||||||
|
use Drupal\Core\Entity\RevisionableStorageInterface; |
||||||
|
use Drupal\Core\Form\ConfirmFormBase; |
||||||
|
use Drupal\Core\Form\FormStateInterface; |
||||||
|
use Drupal\Core\Url; |
||||||
|
use Drupal\dgi_fixity\FixityCheckInterface; |
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Delete a fixity_check revision. |
||||||
|
* |
||||||
|
* @internal |
||||||
|
*/ |
||||||
|
class RevisionDeleteForm extends ConfirmFormBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* The fixity_check revision to delete. |
||||||
|
* |
||||||
|
* @var \Drupal\dgi_fixity\FixityCheckInterface |
||||||
|
*/ |
||||||
|
protected $revision; |
||||||
|
|
||||||
|
/** |
||||||
|
* Entity revision storage. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Entity\RevisionableStorageInterface |
||||||
|
*/ |
||||||
|
protected $storage; |
||||||
|
|
||||||
|
/** |
||||||
|
* The date formatter service. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Datetime\DateFormatterInterface |
||||||
|
*/ |
||||||
|
protected $dateFormatter; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs the form. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Entity\RevisionableStorageInterface $storage |
||||||
|
* The revisionable storage. |
||||||
|
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter |
||||||
|
* The date formatter service. |
||||||
|
*/ |
||||||
|
public function __construct(RevisionableStorageInterface $storage, DateFormatterInterface $date_formatter) { |
||||||
|
$this->storage = $storage; |
||||||
|
$this->dateFormatter = $date_formatter; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public static function create(ContainerInterface $container) { |
||||||
|
return new static( |
||||||
|
$container->get('entity_type.manager')->getStorage('fixity_check'), |
||||||
|
$container->get('date.formatter') |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getFormId() { |
||||||
|
return 'dgi_fixity_revision_delete_confirm'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getQuestion() { |
||||||
|
return $this->t('Are you sure you want to delete the revision from %revision-date?', [ |
||||||
|
'%revision-date' => $this->dateFormatter->format( |
||||||
|
$this->revision->getPerformed() |
||||||
|
), |
||||||
|
]); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getCancelUrl() { |
||||||
|
return new Url('entity.fixity_check.fixity_audit', [ |
||||||
|
'fixity_check' => $this->revision->id(), |
||||||
|
]); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getConfirmText() { |
||||||
|
return $this->t('Delete'); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function buildForm(array $form, FormStateInterface $form_state, FixityCheckInterface $fixity_check_revision = NULL) { |
||||||
|
$this->revision = $fixity_check_revision; |
||||||
|
$form = parent::buildForm($form, $form_state); |
||||||
|
|
||||||
|
return $form; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function submitForm(array &$form, FormStateInterface $form_state) { |
||||||
|
$this->storage->deleteRevision($this->revision->getRevisionId()); |
||||||
|
|
||||||
|
$this->logger('content')->notice('Fixity Check: deleted %title revision %revision.', [ |
||||||
|
'%title' => $this->revision->label(), |
||||||
|
'%revision' => $this->revision->getRevisionId(), |
||||||
|
]); |
||||||
|
$this->messenger()->addStatus( |
||||||
|
$this->t('Revision from %revision-date of %title has been deleted.', |
||||||
|
[ |
||||||
|
'%revision-date' => $this->dateFormatter->format( |
||||||
|
$this->revision->getPerformed() |
||||||
|
), |
||||||
|
'%title' => $this->revision->label(), |
||||||
|
] |
||||||
|
)); |
||||||
|
$form_state->setRedirect('entity.fixity_check.fixity_audit', [ |
||||||
|
'fixity_check' => $this->revision->id(), |
||||||
|
]); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,305 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Form; |
||||||
|
|
||||||
|
use Drupal\Component\Utility\Tags; |
||||||
|
use Drupal\Core\Config\ConfigFactoryInterface; |
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface; |
||||||
|
use Drupal\Core\Form\ConfigFormBase; |
||||||
|
use Drupal\Core\Form\FormStateInterface; |
||||||
|
use Drupal\Core\State\StateInterface; |
||||||
|
use Drupal\views\Views; |
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Configure this module. |
||||||
|
* |
||||||
|
* @internal |
||||||
|
*/ |
||||||
|
class SettingsForm extends ConfigFormBase { |
||||||
|
|
||||||
|
const CONFIG_NAME = 'dgi_fixity.settings'; |
||||||
|
const SOURCES = 'sources'; |
||||||
|
const THRESHOLD = 'threshold'; |
||||||
|
const BATCH_SIZE = 'batch_size'; |
||||||
|
const NOTIFY_STATUS = 'notify_status'; |
||||||
|
const NOTIFY_USER = 'notify_user'; |
||||||
|
const NOTIFY_USER_THRESHOLD = 'notify_user_threshold'; |
||||||
|
|
||||||
|
const NOTIFY_STATUS_NEVER = 0; |
||||||
|
const NOTIFY_STATUS_ALWAYS = 1; |
||||||
|
const NOTIFY_STATUS_ERROR = 2; |
||||||
|
|
||||||
|
// This is not stored as a configuration value but as a state value |
||||||
|
// Though we allow the user to view it and reset it via this form. |
||||||
|
const STATE_LAST_NOTIFICATION = 'dgi_fixity.last_notification'; |
||||||
|
|
||||||
|
/** |
||||||
|
* The entity type manager. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Entity\EntityTypeManagerInterface |
||||||
|
*/ |
||||||
|
protected $entityTypeManager; |
||||||
|
|
||||||
|
/** |
||||||
|
* The state manager. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\State\StateInterface |
||||||
|
*/ |
||||||
|
protected $state; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs a \Drupal\system\ConfigFormBase object. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory |
||||||
|
* The factory for configuration objects. |
||||||
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager |
||||||
|
* Manages entity type plugin definitions. |
||||||
|
* @param \Drupal\Core\State\StateInterface $state |
||||||
|
* State manager. |
||||||
|
*/ |
||||||
|
public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManagerInterface $entity_type_manager, StateInterface $state) { |
||||||
|
parent::__construct($config_factory); |
||||||
|
$this->entityTypeManager = $entity_type_manager; |
||||||
|
$this->state = $state; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public static function create(ContainerInterface $container) { |
||||||
|
return new static( |
||||||
|
$container->get('config.factory'), |
||||||
|
$container->get('entity_type.manager'), |
||||||
|
$container->get('state'), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getFormId() { |
||||||
|
return 'dgi_fixity_settings_form'; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
protected function getEditableConfigNames() { |
||||||
|
return [ |
||||||
|
static::CONFIG_NAME, |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function buildForm(array $form, FormStateInterface $form_state) { |
||||||
|
$config = $this->config(static::CONFIG_NAME); |
||||||
|
$displays = Views::getApplicableViews('entity_reference_display'); |
||||||
|
$view_storage = $this->entityTypeManager->getStorage('view'); |
||||||
|
$sources = []; |
||||||
|
foreach ($displays as $data) { |
||||||
|
[$view_id, $display_id] = $data; |
||||||
|
/** @var \Drupal\views\Entity\View $view */ |
||||||
|
$view = $view_storage->load($view_id); |
||||||
|
$tag = $view->getExecutable()->storage->get('tag'); |
||||||
|
// Only list views tagged with 'fixity'. |
||||||
|
if (in_array('fixity', Tags::explode($tag))) { |
||||||
|
$entity_type = $view->getExecutable()->getBaseEntityType(); |
||||||
|
// Only use views that return 'file' entities, as the batch and workers |
||||||
|
// dynamically filter the view by relating it to 'fixity_checks' that |
||||||
|
// do not yet have their 'periodic' flag set to TRUE. |
||||||
|
if ($entity_type && $entity_type->id() == 'file') { |
||||||
|
$display = $view->get('display'); |
||||||
|
$set_name = $view_id . ':' . $display_id; |
||||||
|
$sources[$set_name] = $display[$display_id]['display_title'] . ' (' . $set_name . ')'; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$form['checks'] = [ |
||||||
|
'#type' => 'details', |
||||||
|
'#title' => $this->t('Fixity Checks'), |
||||||
|
'#open' => TRUE, |
||||||
|
static::SOURCES => [ |
||||||
|
'#title' => $this->t('File Selection'), |
||||||
|
'#description' => $this->t(' |
||||||
|
<p>Select one or more <strong>Views</strong>. Whose results are used to determine which files have periodic checks enabled according to the schedule below.</p> |
||||||
|
<p>Only <em>File Entity</em> <strong>Views</strong> are supported.</p> |
||||||
|
<p>Only <strong>entity_reference</strong> or <strong>entity_reference_revisions</strong> displays are supported.</p> |
||||||
|
<p>If selecting multiple Views ideally they should not overlap, if only for the sake of efficiency.</p> |
||||||
|
<p>Views must be have an administrative tag <strong>fixity</strong> to appear in this list.</p> |
||||||
|
'), |
||||||
|
'#type' => 'checkboxes', |
||||||
|
'#options' => $sources, |
||||||
|
'#default_value' => $config->get(static::SOURCES) ?: [], |
||||||
|
], |
||||||
|
static::THRESHOLD => [ |
||||||
|
'#type' => 'textfield', |
||||||
|
'#required' => TRUE, |
||||||
|
'#title' => $this->t('Time elapsed'), |
||||||
|
'#description' => $this->t(' |
||||||
|
<p>Time threshold is relative to "<em>now</em>". For example "<em>-1 month</em>" would prevent any checks that occurred less than a month ago.</p> |
||||||
|
<p>Check <a href="https://www.php.net/manual/en/datetime.formats.relative.php">Relative Formats</a> for acceptable values</p> |
||||||
|
'), |
||||||
|
'#default_value' => $config->get(static::THRESHOLD) ?: '-1 month', |
||||||
|
'#element_validate' => [ |
||||||
|
[$this, 'validateThreshold'], |
||||||
|
], |
||||||
|
], |
||||||
|
static::BATCH_SIZE => [ |
||||||
|
'#type' => 'number', |
||||||
|
'#required' => TRUE, |
||||||
|
'#title' => $this->t('Batch size'), |
||||||
|
'#description' => $this->t(' |
||||||
|
<p>Set how many files will be processed at once when performing a batch / cron job</p> |
||||||
|
'), |
||||||
|
'#default_value' => 100, |
||||||
|
], |
||||||
|
]; |
||||||
|
|
||||||
|
// Default to the admin user if not given. |
||||||
|
$user = $config->get(static::NOTIFY_USER); |
||||||
|
$user = is_int($user) ? |
||||||
|
$this->entityTypeManager->getStorage('user')->load($user) : |
||||||
|
$this->entityTypeManager->getStorage('user')->load(1); |
||||||
|
|
||||||
|
$notification_threshold = $config->get(static::NOTIFY_USER_THRESHOLD) ?: '-1 week'; |
||||||
|
$last_notification = $this->state->get(static::STATE_LAST_NOTIFICATION); |
||||||
|
if ($last_notification !== NULL) { |
||||||
|
$next_notification = $last_notification + (time() - strtotime($notification_threshold)); |
||||||
|
} |
||||||
|
|
||||||
|
$form['notify'] = [ |
||||||
|
'#type' => 'details', |
||||||
|
'#title' => $this->t('Notifications'), |
||||||
|
'#description' => $this->t('Notifications are sent by email to the selected user.'), |
||||||
|
'#open' => TRUE, |
||||||
|
static::NOTIFY_STATUS => [ |
||||||
|
'#type' => 'select', |
||||||
|
'#title' => $this->t('Notification Status'), |
||||||
|
'#description' => $this->t(' |
||||||
|
<p>Choose under what conditions should notifications be sent to the selected user.</p> |
||||||
|
'), |
||||||
|
'#options' => [ |
||||||
|
static::NOTIFY_STATUS_NEVER => $this->t('Never'), |
||||||
|
static::NOTIFY_STATUS_ALWAYS => $this->t('Always'), |
||||||
|
static::NOTIFY_STATUS_ERROR => $this->t('Only if error'), |
||||||
|
], |
||||||
|
'#default_value' => $config->get(static::NOTIFY_STATUS) ?? static::NOTIFY_STATUS_ERROR, |
||||||
|
], |
||||||
|
static::NOTIFY_USER => [ |
||||||
|
'#type' => 'entity_autocomplete', |
||||||
|
'#required' => TRUE, |
||||||
|
'#title' => $this->t('User'), |
||||||
|
'#description' => $this->t(' |
||||||
|
<p>The user to be notified should one or more fixity checks fail.</p> |
||||||
|
<p>The user will be notified by email.</p> |
||||||
|
'), |
||||||
|
'#target_type' => 'user', |
||||||
|
'#default_value' => $user, |
||||||
|
'#selection_settings' => [ |
||||||
|
'include_anonymous' => FALSE, |
||||||
|
], |
||||||
|
], |
||||||
|
static::NOTIFY_USER_THRESHOLD => [ |
||||||
|
'#type' => 'textfield', |
||||||
|
'#required' => TRUE, |
||||||
|
'#title' => $this->t('Time elapsed'), |
||||||
|
'#description' => $this->t(' |
||||||
|
<p>Time threshold is relative to "<em>now</em>". For example "<em>-1 week</em>" would mean a week must pass between notifications.</p> |
||||||
|
<p>Check <a href="https://www.php.net/manual/en/datetime.formats.relative.php">Relative Formats</a> for acceptable values</p> |
||||||
|
'), |
||||||
|
'#default_value' => $notification_threshold, |
||||||
|
'#element_validate' => [ |
||||||
|
[$this, 'validateThreshold'], |
||||||
|
], |
||||||
|
], |
||||||
|
'last' => $last_notification ? |
||||||
|
[ |
||||||
|
'#type' => 'details', |
||||||
|
'#title' => $this->t('Last notification'), |
||||||
|
'#description' => $this->t(' |
||||||
|
<p>The last notification was sent on %last.</p> |
||||||
|
<p>At earliest the next message can be sent %next.</p> |
||||||
|
', [ |
||||||
|
'%last' => date(DATE_RFC7231, $last_notification), |
||||||
|
'%next' => date(DATE_RFC7231, $next_notification), |
||||||
|
] |
||||||
|
), |
||||||
|
static::STATE_LAST_NOTIFICATION => [ |
||||||
|
'#type' => 'button', |
||||||
|
'#value' => $this->t('Reset'), |
||||||
|
'#limit_validation_errors' => [], |
||||||
|
'#executes_submit_callback' => TRUE, |
||||||
|
'#submit' => [ |
||||||
|
[$this, 'resetLastNotification'], |
||||||
|
], |
||||||
|
], |
||||||
|
] : NULL, |
||||||
|
]; |
||||||
|
|
||||||
|
return parent::buildForm($form, $form_state); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Element validate callback; validate the threshold is valid. |
||||||
|
*/ |
||||||
|
public function validateThreshold(array $element, FormStateInterface $form_state, array $form) { |
||||||
|
$value = $form_state->getValue($element['#parents']); |
||||||
|
if (strtotime($value) === FALSE) { |
||||||
|
$form_state->setError($element, $this->t('The given threshold is not valid.')); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function validateForm(array &$form, FormStateInterface $form_state) { |
||||||
|
$value = $form_state->getValue(static::NOTIFY_USER); |
||||||
|
/** @var \Drupal\user\Entity\User $user */ |
||||||
|
$user = is_numeric($value) ? |
||||||
|
$this->entityTypeManager->getStorage('user')->load($value) : |
||||||
|
NULL; |
||||||
|
if ($user === NULL) { |
||||||
|
// Just a precaution the default form element validation should |
||||||
|
// catch this case anyways. |
||||||
|
$form_state->setError($form['notify'][static::NOTIFY_USER], $this->t('The given user does not exist.')); |
||||||
|
} |
||||||
|
elseif ($user->getEmail() === NULL) { |
||||||
|
$form_state->setError($form['notify'][static::NOTIFY_USER], $this->t('The given user does <em>not</em> have an email address associated with their account.')); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Resets the stored last notification. |
||||||
|
* |
||||||
|
* @param array $form |
||||||
|
* An associative array containing the structure of the form. |
||||||
|
* @param \Drupal\Core\Form\FormStateInterface $form_state |
||||||
|
* The current state of the form. |
||||||
|
*/ |
||||||
|
public function resetLastNotification(array &$form, FormStateInterface $form_state) { |
||||||
|
$this->state->delete(static::STATE_LAST_NOTIFICATION); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function submitForm(array &$form, FormStateInterface $form_state) { |
||||||
|
$values = $form_state->getValues(); |
||||||
|
$config = $this->config(static::CONFIG_NAME); |
||||||
|
$sources = array_keys(array_filter($values[static::SOURCES])); |
||||||
|
$config |
||||||
|
->set(static::SOURCES, array_combine($sources, $sources)) |
||||||
|
->set(static::THRESHOLD, $values[static::THRESHOLD]) |
||||||
|
->set(static::BATCH_SIZE, $values[static::BATCH_SIZE]) |
||||||
|
->set(static::NOTIFY_STATUS, $values[static::NOTIFY_STATUS]) |
||||||
|
->set(static::NOTIFY_USER, $values[static::NOTIFY_USER]) |
||||||
|
->set(static::NOTIFY_USER_THRESHOLD, $values[static::NOTIFY_USER_THRESHOLD]) |
||||||
|
->save(); |
||||||
|
parent::submitForm($form, $form_state); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Plugin\Action; |
||||||
|
|
||||||
|
/** |
||||||
|
* Performs a fixity checks on the entity. |
||||||
|
* |
||||||
|
* @Action( |
||||||
|
* id = "dgi_fixity:check_action", |
||||||
|
* action_label = @Translation("Check"), |
||||||
|
* deriver = "Drupal\dgi_fixity\Plugin\Action\Derivative\FixityCheckActionDeriver", |
||||||
|
* ) |
||||||
|
*/ |
||||||
|
class CheckAction extends FixityCheckActionBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function execute($entity = NULL) { |
||||||
|
$check = $this->getCheck($entity); |
||||||
|
if ($check) { |
||||||
|
$this->fixity->check($check->getFile(), TRUE); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Plugin\Action\Derivative; |
||||||
|
|
||||||
|
use Drupal\Core\Action\Plugin\Action\Derivative\EntityActionDeriverBase; |
||||||
|
use Drupal\Core\Entity\EntityTypeInterface; |
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface; |
||||||
|
use Drupal\Core\StringTranslation\TranslationInterface; |
||||||
|
use Drupal\dgi_fixity\FixityCheckServiceInterface; |
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Provides an action deriver that finds publishable entity types. |
||||||
|
* |
||||||
|
* @see \Drupal\Core\Action\Plugin\Action\PublishAction |
||||||
|
* @see \Drupal\Core\Action\Plugin\Action\UnpublishAction |
||||||
|
*/ |
||||||
|
class FixityCheckActionDeriver extends EntityActionDeriverBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* The fixity check service. |
||||||
|
* |
||||||
|
* @var \Drupal\dgi_fixity\FixityCheckServiceInterface |
||||||
|
*/ |
||||||
|
protected $fixity; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs a new EntityActionDeriverBase object. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager |
||||||
|
* The entity type manager. |
||||||
|
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation |
||||||
|
* The string translation service. |
||||||
|
* @param \Drupal\dgi_fixity\FixityCheckServiceInterface $fixity |
||||||
|
* The fixity service. |
||||||
|
*/ |
||||||
|
public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation, FixityCheckServiceInterface $fixity) { |
||||||
|
$this->entityTypeManager = $entity_type_manager; |
||||||
|
$this->stringTranslation = $string_translation; |
||||||
|
$this->fixity = $fixity; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public static function create(ContainerInterface $container, $base_plugin_id) { |
||||||
|
return new static( |
||||||
|
$container->get('entity_type.manager'), |
||||||
|
$container->get('string_translation'), |
||||||
|
$container->get('dgi_fixity.fixity_check'), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
protected function isApplicable(EntityTypeInterface $entity_type) { |
||||||
|
$supported_entity_types = array_merge(['fixity_check'], $this->fixity->fromEntityTypes()); |
||||||
|
return in_array($entity_type->id(), $supported_entity_types); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,85 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Plugin\Action; |
||||||
|
|
||||||
|
use Drupal\Core\Access\AccessResult; |
||||||
|
use Drupal\Core\Action\Plugin\Action\EntityActionBase; |
||||||
|
use Drupal\Core\Entity\EntityInterface; |
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface; |
||||||
|
use Drupal\Core\Session\AccountInterface; |
||||||
|
use Drupal\dgi_fixity\FixityCheckInterface; |
||||||
|
use Drupal\dgi_fixity\FixityCheckServiceInterface; |
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Base class for fixity_check entity-based actions. |
||||||
|
*/ |
||||||
|
abstract class FixityCheckActionBase extends EntityActionBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* The fixity check service. |
||||||
|
* |
||||||
|
* @var \Drupal\dgi_fixity\FixityCheckServiceInterface |
||||||
|
*/ |
||||||
|
protected $fixity; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs a CheckAction object. |
||||||
|
* |
||||||
|
* @param array $configuration |
||||||
|
* A configuration array containing information about the plugin instance. |
||||||
|
* @param string $plugin_id |
||||||
|
* The plugin_id for the plugin instance. |
||||||
|
* @param mixed $plugin_definition |
||||||
|
* The plugin implementation definition. |
||||||
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager |
||||||
|
* The entity type manager. |
||||||
|
* @param \Drupal\dgi_fixity\FixityCheckServiceInterface $fixity |
||||||
|
* The fixity service. |
||||||
|
*/ |
||||||
|
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, FixityCheckServiceInterface $fixity) { |
||||||
|
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager); |
||||||
|
$this->fixity = $fixity; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { |
||||||
|
return new static( |
||||||
|
$configuration, |
||||||
|
$plugin_id, |
||||||
|
$plugin_definition, |
||||||
|
$container->get('entity_type.manager'), |
||||||
|
$container->get('dgi_fixity.fixity_check'), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the related fixity_check entity for the given entity if possible. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Entity\EntityInterface $entity |
||||||
|
* The related entity. |
||||||
|
* |
||||||
|
* @return \Drupal\dgi_fixity\FixityCheckInterface |
||||||
|
* The related fixity_check if found or NULL. |
||||||
|
*/ |
||||||
|
protected function getCheck(EntityInterface $entity): ?FixityCheckInterface { |
||||||
|
return $entity instanceof FixityCheckInterface ? |
||||||
|
$entity : |
||||||
|
$this->fixity->fromEntity($entity); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { |
||||||
|
// If it exists and the user has permission to administer fixity checks. |
||||||
|
$result = $this->getCheck($object) && $account->hasPermission('administer fixity checks') ? |
||||||
|
AccessResult::allowed() : |
||||||
|
AccessResult::forbidden(); |
||||||
|
|
||||||
|
return $return_as_object ? $result : $result->isAllowed(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Plugin\Action; |
||||||
|
|
||||||
|
/** |
||||||
|
* Disables periodic checks on the the entity. |
||||||
|
* |
||||||
|
* @Action( |
||||||
|
* id = "dgi_fixity:periodic_disable_action", |
||||||
|
* action_label = @Translation("Disable periodic checks"), |
||||||
|
* deriver = "Drupal\dgi_fixity\Plugin\Action\Derivative\FixityCheckActionDeriver", |
||||||
|
* ) |
||||||
|
*/ |
||||||
|
class PeriodicDisableAction extends FixityCheckActionBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function execute($entity = NULL) { |
||||||
|
$check = $this->getCheck($entity); |
||||||
|
if ($check) { |
||||||
|
$check->setPeriodic(FALSE); |
||||||
|
$check->save(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Plugin\Action; |
||||||
|
|
||||||
|
/** |
||||||
|
* Enable periodic checks on the the entity. |
||||||
|
* |
||||||
|
* @Action( |
||||||
|
* id = "dgi_fixity:periodic_enable_action", |
||||||
|
* action_label = @Translation("Enable periodic checks"), |
||||||
|
* deriver = "Drupal\dgi_fixity\Plugin\Action\Derivative\FixityCheckActionDeriver", |
||||||
|
* ) |
||||||
|
*/ |
||||||
|
class PeriodicEnableAction extends FixityCheckActionBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function execute($entity = NULL) { |
||||||
|
$check = $this->getCheck($entity); |
||||||
|
if ($check) { |
||||||
|
$check->setPeriodic(TRUE); |
||||||
|
$check->save(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Plugin\Derivative; |
||||||
|
|
||||||
|
use Drupal\Component\Plugin\Derivative\DeriverBase; |
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface; |
||||||
|
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; |
||||||
|
use Drupal\Core\StringTranslation\StringTranslationTrait; |
||||||
|
use Drupal\Core\StringTranslation\TranslationInterface; |
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates fixity related local tasks. |
||||||
|
*/ |
||||||
|
class FixityCheckLocalTasks extends DeriverBase implements ContainerDeriverInterface { |
||||||
|
|
||||||
|
use StringTranslationTrait; |
||||||
|
|
||||||
|
/** |
||||||
|
* The entity manager. |
||||||
|
* |
||||||
|
* @var \Drupal\Core\Entity\EntityTypeManagerInterface |
||||||
|
*/ |
||||||
|
protected $entityTypeManager; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a local tasks for fixity checks on supported entities. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation |
||||||
|
* The translation manager. |
||||||
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager |
||||||
|
* The entity manager. |
||||||
|
*/ |
||||||
|
public function __construct(TranslationInterface $string_translation, EntityTypeManagerInterface $entity_type_manager) { |
||||||
|
$this->stringTranslation = $string_translation; |
||||||
|
$this->entityTypeManager = $entity_type_manager; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public static function create(ContainerInterface $container, $base_plugin_id) { |
||||||
|
return new static( |
||||||
|
$container->get('string_translation'), |
||||||
|
$container->get('entity_type.manager'), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function getDerivativeDefinitions($base_plugin_definition) { |
||||||
|
foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) { |
||||||
|
if ($entity_type->hasLinkTemplate('fixity-audit')) { |
||||||
|
$this->derivatives["$entity_type_id.fixity"] = [ |
||||||
|
'route_name' => "entity.{$entity_type_id}.fixity_audit", |
||||||
|
'title' => $this->t('Fixity'), |
||||||
|
'base_route' => "entity.{$entity_type_id}.canonical", |
||||||
|
'weight' => 10, |
||||||
|
]; |
||||||
|
$this->derivatives["$entity_type_id.fixity.audit"] = [ |
||||||
|
'route_name' => "entity.$entity_type_id.fixity_audit", |
||||||
|
'title' => $this->t('Audit'), |
||||||
|
'parent_id' => "dgi_fixity.entities:$entity_type_id.fixity", |
||||||
|
'weight' => 10, |
||||||
|
]; |
||||||
|
if ($entity_type->hasLinkTemplate('fixity-check')) { |
||||||
|
$this->derivatives["$entity_type_id.fixity.check"] = [ |
||||||
|
'route_name' => "entity.$entity_type_id.fixity_check", |
||||||
|
'title' => $this->t('Check'), |
||||||
|
'parent_id' => "dgi_fixity.entities:$entity_type_id.fixity", |
||||||
|
'weight' => 10, |
||||||
|
]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return $this->derivatives; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,47 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Plugin\Field\FieldFormatter; |
||||||
|
|
||||||
|
use Drupal\Core\Field\FieldItemListInterface; |
||||||
|
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceFormatterBase; |
||||||
|
use Drupal\Core\Url; |
||||||
|
|
||||||
|
/** |
||||||
|
* Formats file reference as a link to the file. |
||||||
|
* |
||||||
|
* @FieldFormatter( |
||||||
|
* id = "dgi_fixity_file_reference", |
||||||
|
* label = @Translation("Fixity File Reference"), |
||||||
|
* description = @Translation("Display a link to the file being referenced by a Fixity Check."), |
||||||
|
* field_types = { |
||||||
|
* "entity_reference" |
||||||
|
* } |
||||||
|
* ) |
||||||
|
*/ |
||||||
|
class FileReferenceFormatter extends EntityReferenceFormatterBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function viewElements(FieldItemListInterface $items, $langcode) { |
||||||
|
$elements = []; |
||||||
|
/** @var \Drupal\file\Entity\File $entity */ |
||||||
|
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $entity) { |
||||||
|
$label = $entity->label(); |
||||||
|
$uri = $entity->createFileUrl(FALSE); |
||||||
|
if (isset($uri)) { |
||||||
|
$elements[$delta] = [ |
||||||
|
'#type' => 'link', |
||||||
|
'#title' => $label, |
||||||
|
'#url' => Url::fromUri($uri), |
||||||
|
]; |
||||||
|
} |
||||||
|
else { |
||||||
|
$elements[$delta] = ['#plain_text' => $label]; |
||||||
|
} |
||||||
|
$elements[$delta]['#cache']['tags'] = $entity->getCacheTags(); |
||||||
|
} |
||||||
|
return $elements; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Plugin\Field\FieldFormatter; |
||||||
|
|
||||||
|
use Drupal\Core\Field\FieldItemListInterface; |
||||||
|
use Drupal\Core\Field\FormatterBase; |
||||||
|
use Drupal\dgi_fixity\Entity\FixityCheck; |
||||||
|
|
||||||
|
/** |
||||||
|
* Displays a human readable label for the state. |
||||||
|
* |
||||||
|
* @FieldFormatter( |
||||||
|
* id = "dgi_fixity_state", |
||||||
|
* label = @Translation("State"), |
||||||
|
* field_types = { |
||||||
|
* "integer" |
||||||
|
* }, |
||||||
|
* ) |
||||||
|
*/ |
||||||
|
class StateFormatter extends FormatterBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function viewElements(FieldItemListInterface $items, $langcode) { |
||||||
|
$elements = []; |
||||||
|
foreach ($items as $delta => $item) { |
||||||
|
$elements[$delta] = ['#markup' => FixityCheck::getStateProperty($item->value, 'label')]; |
||||||
|
} |
||||||
|
return $elements; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,68 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Plugin\QueueWorker; |
||||||
|
|
||||||
|
use Drupal\Core\Plugin\ContainerFactoryPluginInterface; |
||||||
|
use Drupal\Core\Queue\QueueWorkerBase; |
||||||
|
use Drupal\dgi_fixity\FixityCheckServiceInterface; |
||||||
|
use Drupal\dgi_fixity\FixityCheckInterface; |
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Performs a fixity check. |
||||||
|
* |
||||||
|
* @QueueWorker( |
||||||
|
* id = "dgi_fixity.fixity_check", |
||||||
|
* title = @Translation("Fixity Checks"), |
||||||
|
* cron = {"time" = 15} |
||||||
|
* ) |
||||||
|
*/ |
||||||
|
class FixityCheckWorker extends QueueWorkerBase implements ContainerFactoryPluginInterface { |
||||||
|
|
||||||
|
/** |
||||||
|
* The fixity check service. |
||||||
|
* |
||||||
|
* @var \Drupal\dgi_fixity\FixityCheckServiceInterface |
||||||
|
*/ |
||||||
|
protected $fixity; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs a new FixityCheckWorker instance. |
||||||
|
* |
||||||
|
* @param array $configuration |
||||||
|
* A configuration array containing information about the plugin instance. |
||||||
|
* @param string $plugin_id |
||||||
|
* The plugin_id for the plugin instance. |
||||||
|
* @param mixed $plugin_definition |
||||||
|
* The plugin implementation definition. |
||||||
|
* @param \Drupal\dgi_fixity\FixityCheckServiceInterface $fixity |
||||||
|
* The fixity check service. |
||||||
|
*/ |
||||||
|
public function __construct(array $configuration, $plugin_id, $plugin_definition, FixityCheckServiceInterface $fixity) { |
||||||
|
parent::__construct($configuration, $plugin_id, $plugin_definition); |
||||||
|
$this->fixity = $fixity; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { |
||||||
|
return new static( |
||||||
|
$configuration, |
||||||
|
$plugin_id, |
||||||
|
$plugin_definition, |
||||||
|
$container->get('dgi_fixity.fixity_check'), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function processItem($data) { |
||||||
|
if ($data instanceof FixityCheckInterface) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckInterface $data */ |
||||||
|
$this->fixity->check($data->getFile()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,79 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Plugin\QueueWorker; |
||||||
|
|
||||||
|
use Drupal\Core\Plugin\ContainerFactoryPluginInterface; |
||||||
|
use Drupal\Core\Queue\QueueWorkerBase; |
||||||
|
use Drupal\Core\Queue\RequeueException; |
||||||
|
use Drupal\dgi_fixity\FixityCheckServiceInterface; |
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
||||||
|
|
||||||
|
/** |
||||||
|
* Processes configured sources to find new files to periodically check. |
||||||
|
* |
||||||
|
* @QueueWorker( |
||||||
|
* id = "dgi_fixity.process_source", |
||||||
|
* title = @Translation("Fixity Check Process Source"), |
||||||
|
* cron = {"time" = 15} |
||||||
|
* ) |
||||||
|
*/ |
||||||
|
class ProcessSourceWorker extends QueueWorkerBase implements ContainerFactoryPluginInterface { |
||||||
|
|
||||||
|
/** |
||||||
|
* The fixity check service. |
||||||
|
* |
||||||
|
* @var \Drupal\dgi_fixity\FixityCheckServiceInterface |
||||||
|
*/ |
||||||
|
protected $fixity; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructs a new FixityCheckWorker instance. |
||||||
|
* |
||||||
|
* @param array $configuration |
||||||
|
* A configuration array containing information about the plugin instance. |
||||||
|
* @param string $plugin_id |
||||||
|
* The plugin_id for the plugin instance. |
||||||
|
* @param mixed $plugin_definition |
||||||
|
* The plugin implementation definition. |
||||||
|
* @param \Drupal\dgi_fixity\FixityCheckServiceInterface $fixity |
||||||
|
* The fixity check service. |
||||||
|
*/ |
||||||
|
public function __construct(array $configuration, $plugin_id, $plugin_definition, FixityCheckServiceInterface $fixity) { |
||||||
|
parent::__construct($configuration, $plugin_id, $plugin_definition); |
||||||
|
$this->fixity = $fixity; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { |
||||||
|
return new static( |
||||||
|
$configuration, |
||||||
|
$plugin_id, |
||||||
|
$plugin_definition, |
||||||
|
$container->get('dgi_fixity.fixity_check'), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function processItem($data) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckServiceInterface $fixity */ |
||||||
|
$fixity = \Drupal::service('dgi_fixity.fixity_check'); |
||||||
|
$view = $fixity->source($data, 1000); |
||||||
|
$view->execute(); |
||||||
|
// Only processes those which have not already enabled periodic checks. |
||||||
|
foreach ($view->result as $row) { |
||||||
|
/** @var \Drupal\dgi_fixity\FixityCheckInterface $check */ |
||||||
|
$check = $view->field['periodic']->getEntity($row); |
||||||
|
$check->setPeriodic(TRUE); |
||||||
|
$check->save(); |
||||||
|
} |
||||||
|
// Not finished processing. |
||||||
|
if (count($view->result) !== 0) { |
||||||
|
throw new RequeueException(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Plugin\Validation\Constraint; |
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraint; |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if an entity field has a unique entity reference value. |
||||||
|
* |
||||||
|
* @Constraint( |
||||||
|
* id = "UniqueFieldEntityReference", |
||||||
|
* label = @Translation("Unique field entity reference constraint", context = "Validation"), |
||||||
|
* type = { "entity_reference" } |
||||||
|
* ) |
||||||
|
*/ |
||||||
|
class UniqueFieldEntityReferenceConstraint extends Constraint { |
||||||
|
/** |
||||||
|
* The message to display to the user on invalid condition. |
||||||
|
* |
||||||
|
* @var string |
||||||
|
*/ |
||||||
|
public $message = 'The @referenced_entity_type: %entity_referenced is already a referenced by @entity_type: %entity'; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Plugin\Validation\Constraint; |
||||||
|
|
||||||
|
use Symfony\Component\Validator\Constraint; |
||||||
|
use Symfony\Component\Validator\ConstraintValidator; |
||||||
|
|
||||||
|
/** |
||||||
|
* Validates the UniqueFieldEntityReferenceConstraint constraint. |
||||||
|
*/ |
||||||
|
class UniqueFieldEntityReferenceConstraintValidator extends ConstraintValidator { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function validate($items, Constraint $constraint) { |
||||||
|
/** @var \Drupal\Core\Field\EntityReferenceFieldItemList $items */ |
||||||
|
/** @var UniqueFieldEntityReferenceConstraint $constraint */ |
||||||
|
/** @var \Drupal\Core\Field\EntityReferenceFieldItem $item */ |
||||||
|
if (!$item = $items->first()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
$target_property = $item->mainPropertyName(); |
||||||
|
$field_name = $items->getFieldDefinition()->getName(); |
||||||
|
/** @var \Drupal\Core\Entity\EntityInterface $entity */ |
||||||
|
$entity = $items->getEntity(); |
||||||
|
$entity_type_id = $entity->getEntityTypeId(); |
||||||
|
$id_key = $entity->getEntityType()->getKey('id'); |
||||||
|
|
||||||
|
$query = \Drupal::entityQuery($entity_type_id); |
||||||
|
$query->accessCheck(FALSE); |
||||||
|
|
||||||
|
$entity_id = $entity->id(); |
||||||
|
// Using isset() instead of !empty() as 0 and '0' are valid ID values for |
||||||
|
// entity types using string IDs. |
||||||
|
if (isset($entity_id)) { |
||||||
|
$query->condition($id_key, $entity_id, '<>'); |
||||||
|
} |
||||||
|
$targets = []; |
||||||
|
foreach ($items as $item) { |
||||||
|
$targets[] = $item->{$target_property}; |
||||||
|
} |
||||||
|
|
||||||
|
$targets = array_filter($targets); |
||||||
|
$results = $query |
||||||
|
->condition($field_name, $targets, 'IN') |
||||||
|
->range(0, 1) |
||||||
|
->execute(); |
||||||
|
|
||||||
|
if (count($results) > 0) { |
||||||
|
$used_by_entity_id = reset($results); |
||||||
|
$used_by_entity = \Drupal::entityTypeManager()->getStorage($entity_type_id)->load($used_by_entity_id); |
||||||
|
foreach ($used_by_entity->{$field_name}->referencedEntities() as $reference) { |
||||||
|
if (in_array($reference->id(), $targets)) { |
||||||
|
$this->context->addViolation($constraint->message, [ |
||||||
|
'@referenced_entity_type' => $reference->getEntityTypeId(), |
||||||
|
'%entity_referenced' => $reference->id(), |
||||||
|
'@entity_type' => $entity_type_id, |
||||||
|
'%entity' => $used_by_entity_id, |
||||||
|
]); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Plugin\views\wizard; |
||||||
|
|
||||||
|
use Drupal\views\Plugin\views\wizard\WizardPluginBase; |
||||||
|
|
||||||
|
/** |
||||||
|
* Used for creating 'fixity_check' views with the wizard. |
||||||
|
* |
||||||
|
* @ViewsWizard( |
||||||
|
* id = "fixity_check", |
||||||
|
* base_table = "fixity_check", |
||||||
|
* title = @Translation("Fixity Check"), |
||||||
|
* ) |
||||||
|
*/ |
||||||
|
class FixityCheck extends WizardPluginBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
protected $createdColumn = 'fixity_check-performed'; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Plugin\views\wizard; |
||||||
|
|
||||||
|
use Drupal\views\Plugin\views\wizard\WizardPluginBase; |
||||||
|
|
||||||
|
/** |
||||||
|
* Used for creating 'fixity_check' views with the wizard. |
||||||
|
* |
||||||
|
* @ViewsWizard( |
||||||
|
* id = "fixity_check_revision", |
||||||
|
* base_table = "fixity_check_revision", |
||||||
|
* title = @Translation("Fixity Check Revision"), |
||||||
|
* ) |
||||||
|
*/ |
||||||
|
class FixityCheckRevision extends WizardPluginBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
protected $createdColumn = 'fixity_check_revision-performed'; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,65 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Routing; |
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityRepositoryInterface; |
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface; |
||||||
|
use Drupal\Core\ParamConverter\EntityConverter; |
||||||
|
use Drupal\dgi_fixity\FixityCheckServiceInterface; |
||||||
|
use Symfony\Component\Routing\Route; |
||||||
|
|
||||||
|
/** |
||||||
|
* Converts an entity identifier into fixity_check entity. |
||||||
|
*/ |
||||||
|
class FixityCheckConverter extends EntityConverter { |
||||||
|
|
||||||
|
/** |
||||||
|
* The fixity service. |
||||||
|
* |
||||||
|
* @var \Drupal\dgi_fixity\FixityCheckServiceInterface |
||||||
|
*/ |
||||||
|
protected $fixity; |
||||||
|
|
||||||
|
/** |
||||||
|
* Construct a new FixityCheckConverter. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager |
||||||
|
* The entity type manager. |
||||||
|
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository |
||||||
|
* The entity repository. |
||||||
|
* @param \Drupal\dgi_fixity\FixityCheckServiceInterface $fixity |
||||||
|
* The fixity service. |
||||||
|
*/ |
||||||
|
public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository, FixityCheckServiceInterface $fixity) { |
||||||
|
parent::__construct($entity_type_manager, $entity_repository); |
||||||
|
$this->fixity = $fixity; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function convert($value, $definition, $name, array $defaults) { |
||||||
|
/** @var \Drupal\Core\Entity\EntityInterface $entity */ |
||||||
|
$entity = parent::convert($value, $definition, $name, $defaults); |
||||||
|
return $this->fixity->fromEntity($entity); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
public function applies($definition, $name, Route $route) { |
||||||
|
$supported_entity_types = $this->fixity->fromEntityTypes(); |
||||||
|
if (!empty($definition['type']) && strpos($definition['type'], 'fixity:') === 0) { |
||||||
|
$entity_type_id = substr($definition['type'], strlen('fixity:')); |
||||||
|
if (strpos($definition['type'], '{') !== FALSE) { |
||||||
|
$entity_type_slug = substr($entity_type_id, 1, -1); |
||||||
|
if ($name != $entity_type_slug && in_array($entity_type_slug, $route->compile()->getVariables(), TRUE)) { |
||||||
|
return in_array($entity_type_slug, $supported_entity_types); |
||||||
|
} |
||||||
|
} |
||||||
|
return in_array($entity_type_id, $supported_entity_types); |
||||||
|
} |
||||||
|
return FALSE; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Routing; |
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityTypeInterface; |
||||||
|
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider; |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets defaults on HTML routes for the fixity_check entity. |
||||||
|
* |
||||||
|
* @see \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider. |
||||||
|
*/ |
||||||
|
class FixityCheckRouteProvider extends AdminHtmlRouteProvider { |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
protected function getCanonicalRoute(EntityTypeInterface $entity_type) { |
||||||
|
if ($route = parent::getCanonicalRoute($entity_type)) { |
||||||
|
$route->setOption('_admin_route', TRUE); |
||||||
|
return $route; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,113 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace Drupal\dgi_fixity\Routing; |
||||||
|
|
||||||
|
use Drupal\Core\Entity\EntityTypeInterface; |
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface; |
||||||
|
use Drupal\Core\Routing\RouteSubscriberBase; |
||||||
|
use Drupal\dgi_fixity\FixityCheckServiceInterface; |
||||||
|
use Symfony\Component\Routing\Route; |
||||||
|
use Symfony\Component\Routing\RouteCollection; |
||||||
|
|
||||||
|
/** |
||||||
|
* Listens to the dynamic route events. |
||||||
|
*/ |
||||||
|
class FixityCheckRouteSubscriber extends RouteSubscriberBase { |
||||||
|
|
||||||
|
/** |
||||||
|
* The fixity check service. |
||||||
|
* |
||||||
|
* @var \Drupal\dgi_fixity\FixityCheckServiceInterface |
||||||
|
*/ |
||||||
|
protected $fixity; |
||||||
|
|
||||||
|
/** |
||||||
|
* Subscriber for Fixity Check routes. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_manager |
||||||
|
* The entity type manager. |
||||||
|
* @param \Drupal\dgi_fixity\FixityCheckServiceInterface $fixity |
||||||
|
* The fixity check service. |
||||||
|
*/ |
||||||
|
public function __construct(EntityTypeManagerInterface $entity_manager, FixityCheckServiceInterface $fixity) { |
||||||
|
$this->entityTypeManager = $entity_manager; |
||||||
|
$this->fixity = $fixity; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritdoc} |
||||||
|
*/ |
||||||
|
protected function alterRoutes(RouteCollection $collection) { |
||||||
|
$supported_entity_types = array_merge(['fixity_check'], $this->fixity->fromEntityTypes()); |
||||||
|
$definitions = $this->entityTypeManager->getDefinitions(); |
||||||
|
foreach ($supported_entity_types as $entity_type_id) { |
||||||
|
$entity_type = $definitions[$entity_type_id]; |
||||||
|
if ($route = $this->getFixityAuditRoute($entity_type)) { |
||||||
|
$collection->add("entity.$entity_type_id.fixity_audit", $route); |
||||||
|
} |
||||||
|
if ($route = $this->getFixityCheckRoute($entity_type)) { |
||||||
|
$collection->add("entity.$entity_type_id.fixity_check", $route); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the fixity check 'Audit' route for the given entity type. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type |
||||||
|
* The entity type. |
||||||
|
* |
||||||
|
* @return \Symfony\Component\Routing\Route|null |
||||||
|
* The generated route, if available. |
||||||
|
*/ |
||||||
|
protected function getFixityAuditRoute(EntityTypeInterface $entity_type) { |
||||||
|
if ($fixity_audit = $entity_type->getLinkTemplate('fixity-audit')) { |
||||||
|
$entity_type_id = $entity_type->id(); |
||||||
|
$route = new Route($fixity_audit); |
||||||
|
$route |
||||||
|
->addDefaults([ |
||||||
|
'_controller' => '\Drupal\dgi_fixity\Controller\FixityCheckController::entityAudit', |
||||||
|
'_title' => 'Audit', |
||||||
|
]) |
||||||
|
->addRequirements([ |
||||||
|
'_permission' => 'view fixity checks', |
||||||
|
]) |
||||||
|
->setOption('_admin_route', TRUE) |
||||||
|
->setOption('_fixity_entity_type_id', $entity_type_id) |
||||||
|
->setOption('parameters', [ |
||||||
|
$entity_type_id => ['type' => 'entity:' . $entity_type_id], |
||||||
|
]); |
||||||
|
return $route; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the fixity check 'Check' route for the given entity type. |
||||||
|
* |
||||||
|
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type |
||||||
|
* The entity type. |
||||||
|
* |
||||||
|
* @return \Symfony\Component\Routing\Route|null |
||||||
|
* The generated route, if available. |
||||||
|
*/ |
||||||
|
protected function getFixityCheckRoute(EntityTypeInterface $entity_type) { |
||||||
|
if ($fixity_audit = $entity_type->getLinkTemplate('fixity-check')) { |
||||||
|
$entity_type_id = $entity_type->id(); |
||||||
|
$route = new Route($fixity_audit); |
||||||
|
$route |
||||||
|
->addDefaults([ |
||||||
|
'_entity_form' => "{$entity_type_id}.fixity-check", |
||||||
|
]) |
||||||
|
->addRequirements([ |
||||||
|
'_permission' => 'administer fixity checks', |
||||||
|
]) |
||||||
|
->setOption('_admin_route', TRUE) |
||||||
|
->setOption('_fixity_entity_type_id', $entity_type_id) |
||||||
|
->setOption('parameters', [ |
||||||
|
$entity_type_id => ['type' => 'entity:' . $entity_type_id], |
||||||
|
]); |
||||||
|
return $route; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue