Browse Source

Merge remote-tracking branch 'fits2/merge-into-islandora' into 2.x

pull/907/head
Rosie Le Faive 2 years ago
parent
commit
9a335481e9
  1. 74
      modules/islandora_fits/CONTRIBUTING.md
  2. 339
      modules/islandora_fits/LICENSE
  3. 60
      modules/islandora_fits/README.md
  4. 29
      modules/islandora_fits/config/install/context.context.technical_metadata_on_ingest.yml
  5. 58
      modules/islandora_fits/config/install/core.entity_view_display.media.fits_technical_metadata.default.yml
  6. 56
      modules/islandora_fits/config/install/core.entity_view_display.media.fits_technical_metadata.fits_technical_metadata.yml
  7. 12
      modules/islandora_fits/config/install/core.entity_view_mode.media.fits_technical_metadata.yml
  8. 25
      modules/islandora_fits/config/install/field.field.media.fits_technical_metadata.field_complete.yml
  9. 25
      modules/islandora_fits/config/install/field.field.media.fits_technical_metadata.field_file_size.yml
  10. 29
      modules/islandora_fits/config/install/field.field.media.fits_technical_metadata.field_media_file.yml
  11. 23
      modules/islandora_fits/config/install/field.field.media.fits_technical_metadata.field_media_of.yml
  12. 23
      modules/islandora_fits/config/install/field.field.media.fits_technical_metadata.field_media_use.yml
  13. 21
      modules/islandora_fits/config/install/field.field.media.fits_technical_metadata.field_mime_type.yml
  14. 21
      modules/islandora_fits/config/install/field.storage.media.field_complete.yml
  15. 18
      modules/islandora_fits/config/install/media.type.fits_technical_metadata.yml
  16. 22
      modules/islandora_fits/config/install/system.action.generate_a_technical_metadata_derivative.yml
  17. 20
      modules/islandora_fits/config/optional/field.field.media.fits_technical_metadata.fits_ois_file_information_md5che.yml
  18. 19
      modules/islandora_fits/config/optional/field.storage.media.fits_ois_file_information_md5che.yml
  19. 11
      modules/islandora_fits/css/islandora_fits.css
  20. 8
      modules/islandora_fits/islandora_fits.info.yml
  21. 69
      modules/islandora_fits/islandora_fits.install
  22. 6
      modules/islandora_fits/islandora_fits.libraries.yml
  23. 107
      modules/islandora_fits/islandora_fits.module
  24. 4
      modules/islandora_fits/islandora_fits.services.yml
  25. 25
      modules/islandora_fits/migrations/islandora_fits_tags.yml
  26. 58
      modules/islandora_fits/src/Plugin/Action/GenerateFitsDerivative.php
  27. 92
      modules/islandora_fits/src/Plugin/Field/FieldFormatter/FitsFormatter.php
  28. 429
      modules/islandora_fits/src/Services/XMLTransform.php
  29. 13
      modules/islandora_fits/templates/fits.html.twig
  30. 46
      modules/islandora_fits/tests/src/Functional/LoadTest.php

74
modules/islandora_fits/CONTRIBUTING.md

@ -0,0 +1,74 @@
# Welcome!
If you are reading this document then you are interested in contributing to Islandora 8. All contributions are welcome: use-cases, documentation, code, patches, bug reports, feature requests, etc. You do not need to be a programmer to speak up!
We also have an IRC channel -- #islandora -- on freenode.net. Feel free to hang out there, ask questions, and help others out if you can.
Please note that this project operates under the [Islandora Community Code of Conduct](http://islandora.ca/codeofconduct). By participating in this project you agree to abide by its terms.
## Workflows
The group meets each Wednesday at 1:00 PM Eastern. Meeting notes and announcements are posted to the [Islandora community list](https://groups.google.com/forum/#!forum/islandora) and the [Islandora developers list](https://groups.google.com/forum/#!forum/islandora-dev). You can view meeting agendas, notes, and call-in information [here](https://github.com/Islandora/documentation/wiki#islandora-8-tech-calls). Anybody is welcome to join the calls, and add items to the agenda.
### Use cases
If you would like to submit a use case to the Islandora 8 project, please submit an issue [here](https://github.com/Islandora/documentation/issues/new) using the [Use Case template](https://github.com/Islandora/documentation/wiki/Use-Case-template), prepending "Use Case:" to the title of the issue.
### Documentation
You can contribute documentation in two different ways. One way is to create an issue [here](https://github.com/Islandora/documentation/issues/new), prepending "Documentation:" to the title of the issue. Another way is by pull request, which is the same process as [Contribute Code](https://github.com/Islandora/documentation/blob/main/CONTRIBUTING.md#contribute-code). All documentation resides in [`docs`](https://github.com/Islandora/documentation/tree/main/docs).
### Request a new feature
To request a new feature you should [open an issue in the Islandora 8 repository](https://github.com/Islandora/documentation/issues/new) or create a use case (see the _Use cases_ section above), and summarize the desired functionality. Prepend "Enhancement:" if creating an issue on the project repo, and "Use Case:" if creating a use case.
### Report a bug
To report a bug you should [open an issue in the Islandora 8 repository](https://github.com/Islandora/documentation/issues/new) that summarizes the bug. Prepend the label "Bug:" to the title of the issue.
In order to help us understand and fix the bug it would be great if you could provide us with:
1. The steps to reproduce the bug. This includes information about e.g. the Islandora version you were using along with the versions of stack components.
2. The expected behavior.
3. The actual, incorrect behavior.
Feel free to search the issue queue for existing issues (aka tickets) that already describe the problem; if there is such a ticket please add your information as a comment.
**If you want to provide a pull along with your bug report:**
That is great! In this case please send us a pull request as described in the section _Create a pull request_ below.
### Contribute code
Before you set out to contribute code you will need to have completed a [Contributor License Agreement](http://islandora.ca/sites/default/files/islandora_cla.pdf) or be covered by a [Corporate Contributor License Agreement](http://islandora.ca/sites/default/files/islandora_ccla.pdf). The signed copy of the license agreement should be sent to <mailto:community@islandora.ca>
_If you are interested in contributing code to Islandora but do not know where to begin:_
In this case you should [browse open issues](https://github.com/Islandora/documentation/issues) and check out [use cases](https://github.com/Islandora/documentation/labels/use%20case).
If you are contributing Drupal code, it must adhere to [Drupal Coding Standards](https://www.drupal.org/coding-standards); Travis CI will check for this on pull requests.
Contributions to the Islandora codebase should be sent as GitHub pull requests. See section _Create a pull request_ below for details. If there is any problem with the pull request we can work through it using the commenting features of GitHub.
* For _small patches_, feel free to submit pull requests directly for those patches.
* For _larger code contributions_, please use the following process. The idea behind this process is to prevent any wasted work and catch design issues early on.
1. [Open an issue](https://github.com/Islandora/documentation/issues), prepending "Enhancement:" in the title if a similar issue does not exist already. If a similar issue does exist, then you may consider participating in the work on the existing issue.
2. Comment on the issue with your plan for implementing the issue. Explain what pieces of the codebase you are going to touch and how everything is going to fit together.
3. Islandora committers will work with you on the design to make sure you are on the right track.
4. Implement your issue, create a pull request (see below), and iterate from there.
### Create a pull request
Take a look at [Creating a pull request](https://help.github.com/articles/creating-a-pull-request). In a nutshell you need to:
1. [Fork](https://help.github.com/articles/fork-a-repo) this repository to your personal or institutional GitHub account (depending on the CLA you are working under). Be cautious of which branches you work from though (you'll want to base your work off main, or for Drupal modules use the most recent version branch). See [Fork a repo](https://help.github.com/articles/fork-a-repo) for detailed instructions.
2. Commit any changes to your fork.
3. Send a [pull request](https://help.github.com/articles/creating-a-pull-request) using the [pull request template](https://github.com/Islandora/documentation/blob/main/.github/PULL_REQUEST_TEMPLATE.md) to the Islandora GitHub repository that you forked in step 1. If your pull request is related to an existing issue -- for instance, because you reported a [bug/issue](https://github.com/Islandora/documentation/issues) earlier -- prefix the title of your pull request with the corresponding issue number (e.g. `issue-123: ...`). Please also include a reference to the issue in the description of the pull. This can be done by using '#' plus the issue number like so '#123', also try to pick an appropriate name for the branch in which you're issuing the pull request from.
You may want to read [Syncing a fork](https://help.github.com/articles/syncing-a-fork) for instructions on how to keep your fork up to date with the latest changes of the upstream (official) repository.
## License Agreements
The Islandora Foundation requires that contributors complete a [Contributor License Agreement](http://islandora.ca/sites/default/files/islandora_cla.pdf) or be covered by a [Corporate Contributor License Agreement](http://islandora.ca/sites/default/files/islandora_ccla.pdf). The signed copy of the license agreement should be sent to <a href="mailto:community@islandora.ca?Subject=Contributor%20License%20Agreement" target="_top">community@islandora.ca</a>. This license is for your protection as a contributor as well as the protection of the Foundation and its users; it does not change your rights to use your own contributions for any other purpose. A list of current CLAs is kept [here](https://github.com/Islandora/islandora/wiki/Contributor-License-Agreements).

339
modules/islandora_fits/LICENSE

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

60
modules/islandora_fits/README.md

@ -0,0 +1,60 @@
# Islandora FITS
Config module to make Islandora aware of FITS microservice
## Installation
#### Install this module
Install and enable this module in the usual way. On installation the module will
add a context causing the creation of A FITS media when an Original File media is ingested; however, this process is
predicated on the existence of an `islandora_media_use` term with an external URI of `https://projects.iq.harvard.
edu/fits`--the `islandora_fits_tags` migration might be executed to create such a term.
#### Install FITS Webservice
FITS XMLs are generated from an easily installed web service.
Get the latest fits.zip and fits.war from https://projects.iq.harvard.edu/fits/downloads
(on my box I had to install a missing zip library with
‘sudo apt-get install php7.1-zip’)
Install following their instructions.
Copy the `.war` file to your webapps directory and test.
Edit the `catalina.properties` file on the Drupal server by adding the
following two lines to the bottom of the file:
```properties
fits.home=/\<path-to-fits>/fits
shared.loader=/\<path-to-fits>/fits/lib/*.jar
```
Restart Tomcat and test with:
```bash
curl -k -F datafile="@/path/to/myfile.jpg" http://example.com:8080/fits/examine
```
(note: the ‘@’ is required.)
#### Installing Microservice
Get code from https://github.com/roblib/CrayFits and install. This code can live anywhere, including an external server,
but most installations will have it at `/var/www/html`.
The App runs by entering:
```bash
php bin/console server:start *:8050
```
in the App root folder.
The server is stopped with:
```bash
php bin/console server:stop
```
On a production machine you'd probably want to configure an additional
port in Apache.
Note: The location of the FITS webserver is stored in the `.env` file in the
root dir of the Symfony app. This will have to be reconfigured if the FITS
server is anywhere other than `localhost:8080/fits`
#### Adding FITs requests to the queue
Copy the file `assets/ca.islandora.alpaca.connector.ocr.blueprint.xml`
to `/opt/karak/deploy` on your server. There is no need to restart.
#### Adding Checksum to Display
A pseudo field with the computed checksum can be added to Repository Item
display. Navigate to `admin/structure/types/manage/islandora_object/display`
to enable or disable display of `File Checksum`.

29
modules/islandora_fits/config/install/context.context.technical_metadata_on_ingest.yml

@ -0,0 +1,29 @@
langcode: en
status: true
dependencies:
module:
- islandora
enforced:
module:
- islandora_fits
name: technical_metadata_on_ingest
label: 'Technical Metadata on Ingest'
group: Islandora
description: 'Defines FITS harvesting behavior'
requireAllConditions: false
disabled: false
conditions:
media_has_term:
id: media_has_term
negate: 0
uuid: b270cb14-b960-4494-9ccf-1c9433092837
uri: 'http://pcdm.org/use#OriginalFile'
context_mapping:
media: '@islandora.media_route_context_provider:media'
reactions:
derivative:
id: derivative
actions:
generate_a_technical_metadata_derivative: generate_a_technical_metadata_derivative
saved: false
weight: 0

58
modules/islandora_fits/config/install/core.entity_view_display.media.fits_technical_metadata.default.yml

@ -0,0 +1,58 @@
langcode: en
status: true
dependencies:
config:
- field.field.media.fits_technical_metadata.field_file_size
- field.field.media.fits_technical_metadata.field_media_file
- field.field.media.fits_technical_metadata.field_media_of
- field.field.media.fits_technical_metadata.field_media_use
- field.field.media.fits_technical_metadata.field_mime_type
- media.type.fits_technical_metadata
module:
- islandora_fits
enforced:
module:
- islandora_fits
id: media.fits_technical_metadata.default
targetEntityType: media
bundle: fits_technical_metadata
mode: default
content:
field_media_file:
weight: 0
label: hidden
settings: { }
third_party_settings: { }
type: fits_formatter
region: content
field_media_of:
weight: 1
label: above
settings:
link: true
third_party_settings: { }
type: entity_reference_label
region: content
field_media_use:
weight: 3
label: above
settings:
link: true
third_party_settings: { }
type: entity_reference_label
region: content
field_mime_type:
weight: 2
label: above
settings:
link_to_entity: false
third_party_settings: { }
type: string
region: content
hidden:
created: true
field_file_size: true
langcode: true
name: true
thumbnail: true
uid: true

56
modules/islandora_fits/config/install/core.entity_view_display.media.fits_technical_metadata.fits_technical_metadata.yml

@ -0,0 +1,56 @@
langcode: en
status: true
dependencies:
config:
- core.entity_view_mode.media.fits_technical_metadata
- field.field.media.fits_technical_metadata.field_media_file
- image.style.thumbnail
- media.type.fits_technical_metadata
module:
- image
- islandora_fits
- user
enforced:
module:
- islandora_fits
id: media.fits_technical_metadata.fits_technical_metadata
targetEntityType: media
bundle: fits_technical_metadata
mode: fits_technical_metadata
content:
created:
label: hidden
type: timestamp
weight: 0
region: content
settings:
date_format: medium
custom_date_format: ''
timezone: ''
third_party_settings: { }
field_media_file:
weight: 6
label: above
settings: { }
third_party_settings: { }
type: fits_formatter
region: content
thumbnail:
type: image
weight: 5
label: hidden
settings:
image_style: thumbnail
image_link: ''
region: content
third_party_settings: { }
uid:
label: hidden
type: author
weight: 0
region: content
settings: { }
third_party_settings: { }
hidden:
langcode: true
name: true

12
modules/islandora_fits/config/install/core.entity_view_mode.media.fits_technical_metadata.yml

@ -0,0 +1,12 @@
langcode: en
status: true
dependencies:
module:
- media
enforced:
module:
- islandora_fits
id: media.fits_technical_metadata
label: 'FITS Technical Metadata'
targetEntityType: media
cache: true

25
modules/islandora_fits/config/install/field.field.media.fits_technical_metadata.field_complete.yml

@ -0,0 +1,25 @@
langcode: en
status: true
dependencies:
config:
- field.storage.media.field_complete
- media.type.fits_technical_metadata
enforced:
module:
- islandora_fits
id: media.fits_technical_metadata.field_complete
field_name: field_complete
entity_type: media
bundle: fits_technical_metadata
label: Complete
description: ''
required: false
translatable: false
default_value:
-
value: 0
default_value_callback: ''
settings:
on_label: Complete
off_label: Incomplete
field_type: boolean

25
modules/islandora_fits/config/install/field.field.media.fits_technical_metadata.field_file_size.yml

@ -0,0 +1,25 @@
langcode: en
status: true
dependencies:
config:
- field.storage.media.field_file_size
- media.type.fits_technical_metadata
enforced:
module:
- islandora_fits
id: media.fits_technical_metadata.field_file_size
field_name: field_file_size
entity_type: media
bundle: fits_technical_metadata
label: 'File size'
description: ''
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings:
min: null
max: null
prefix: ''
suffix: ''
field_type: integer

29
modules/islandora_fits/config/install/field.field.media.fits_technical_metadata.field_media_file.yml

@ -0,0 +1,29 @@
langcode: en
status: true
dependencies:
config:
- field.storage.media.field_media_file
- media.type.fits_technical_metadata
module:
- file
enforced:
module:
- islandora_fits
id: media.fits_technical_metadata.field_media_file
field_name: field_media_file
entity_type: media
bundle: fits_technical_metadata
label: File
description: ''
required: true
translatable: true
default_value: { }
default_value_callback: ''
settings:
file_directory: '[date:custom:Y]-[date:custom:m]'
file_extensions: xml
max_filesize: ''
description_field: false
handler: 'default:file'
handler_settings: { }
field_type: file

23
modules/islandora_fits/config/install/field.field.media.fits_technical_metadata.field_media_of.yml

@ -0,0 +1,23 @@
langcode: en
status: true
dependencies:
config:
- field.storage.media.field_media_of
- media.type.fits_technical_metadata
enforced:
module:
- islandora_fits
id: media.fits_technical_metadata.field_media_of
field_name: field_media_of
entity_type: media
bundle: fits_technical_metadata
label: 'Media of'
description: ''
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings:
handler: 'default:node'
handler_settings: { }
field_type: entity_reference

23
modules/islandora_fits/config/install/field.field.media.fits_technical_metadata.field_media_use.yml

@ -0,0 +1,23 @@
langcode: en
status: true
dependencies:
config:
- field.storage.media.field_media_use
- media.type.fits_technical_metadata
enforced:
module:
- islandora_fits
id: media.fits_technical_metadata.field_media_use
field_name: field_media_use
entity_type: media
bundle: fits_technical_metadata
label: 'Media Use'
description: ''
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings:
handler: 'default:taxonomy_term'
handler_settings: { }
field_type: entity_reference

21
modules/islandora_fits/config/install/field.field.media.fits_technical_metadata.field_mime_type.yml

@ -0,0 +1,21 @@
langcode: en
status: true
dependencies:
config:
- field.storage.media.field_mime_type
- media.type.fits_technical_metadata
enforced:
module:
- islandora_fits
id: media.fits_technical_metadata.field_mime_type
field_name: field_mime_type
entity_type: media
bundle: fits_technical_metadata
label: 'MIME type'
description: ''
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings: { }
field_type: string

21
modules/islandora_fits/config/install/field.storage.media.field_complete.yml

@ -0,0 +1,21 @@
langcode: en
status: true
dependencies:
module:
- field_permissions
- media
enforced:
module:
- islandora_fits
id: media.field_complete
field_name: field_complete
entity_type: media
type: boolean
settings: { }
module: core
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

18
modules/islandora_fits/config/install/media.type.fits_technical_metadata.yml

@ -0,0 +1,18 @@
langcode: en
status: true
dependencies:
enforced:
module:
- islandora_fits
id: fits_technical_metadata
label: 'FITS Technical metadata'
description: "Display of transformed FITS xml"
source: file
queue_thumbnail_downloads: false
new_revision: false
source_configuration:
source_field: field_media_file
field_map:
name: name
mimetype: field_mime_type
filesize: field_file_size

22
modules/islandora_fits/config/install/system.action.generate_a_technical_metadata_derivative.yml

@ -0,0 +1,22 @@
langcode: en
status: true
dependencies:
module:
- islandora_fits
enforced:
module:
- islandora_fits
id: generate_a_technical_metadata_derivative
label: 'FITS - Generate a Technical metadata derivative'
type: node
plugin: generate_fits_derivative
configuration:
queue: islandora-connector-fits
event: 'Generate Derivative'
source_term_uri: 'http://pcdm.org/use#OriginalFile'
derivative_term_uri: 'https://projects.iq.harvard.edu/fits'
mimetype: application/xml
args: null
destination_media_type: fits_technical_metadata
scheme: public
path: '[date:custom:Y]-[date:custom:m]/[node:nid]-[term:name].xml'

20
modules/islandora_fits/config/optional/field.field.media.fits_technical_metadata.fits_ois_file_information_md5che.yml

@ -0,0 +1,20 @@
langcode: en
status: true
dependencies:
config:
- field.storage.media.fits_ois_file_information_md5che
- media.type.fits_technical_metadata
module:
- text
id: media.fits_technical_metadata.fits_ois_file_information_md5che
field_name: fits_ois_file_information_md5che
entity_type: media
bundle: fits_technical_metadata
label: OIS_File_Information_Md5checksum
description: ''
required: false
translatable: true
default_value: { }
default_value_callback: ''
settings: { }
field_type: text

19
modules/islandora_fits/config/optional/field.storage.media.fits_ois_file_information_md5che.yml

@ -0,0 +1,19 @@
langcode: en
status: true
dependencies:
module:
- media
- text
id: media.fits_ois_file_information_md5che
field_name: fits_ois_file_information_md5che
entity_type: media
type: text
settings:
max_length: 255
module: text
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false

11
modules/islandora_fits/css/islandora_fits.css

@ -0,0 +1,11 @@
.islandora_fits_table {
width: 50em;
}
.islandora_fits_table_labels {
width: 50%;
}
.islandora_fits_table_values {
width: 50%;
}

8
modules/islandora_fits/islandora_fits.info.yml

@ -0,0 +1,8 @@
name: 'Islandora Fits'
type: module
description: 'Enables Technical Metadata derivative generation'
core: 8.x
core_version_requirement: ^8 || ^9
package: 'Islandora'
dependencies:
- islandora

69
modules/islandora_fits/islandora_fits.install

@ -0,0 +1,69 @@
<?php
/**
* @file
* Install/update hook implementations.
*/
use Drupal\field\Entity\FieldConfig;
/**
* Implements hook_install().
*/
function islandora_fits_install($is_syncing) {
if (!_islandora_fits_term_exists()) {
$callable = $is_syncing ? [\Drupal::messenger(), 'addStatus'] : [\Drupal::messenger(), 'addWarning'];
$callable(t('A term in the taxonomy @vid with the URI @uri does not appear to exist. The @migration_id migration can be executed to create it.', [
'@vid' => 'islandora_media_use',
'@uri' => 'https://projects.iq.harvard.edu/fits',
'@migration_id' => 'islandora_fits_tags',
]));
}
}
/**
* Implements hook_requirements().
*/
function islandora_fits_requirements($phase) : array {
$requirements = [];
if ($phase == 'runtime') {
$term_exists = _islandora_fits_term_exists();
$requirements['islandora_fits_term_exists'] = [
'title' => t('FITS Term Exists'),
'value' => $term_exists ? t('Exists') : t('Does not exist'),
'description' => t('Whether or not a term with the URI targeted by default FITS derivative configuration exists. If derivative configurations were made to target another URI, this can probably be ignored.'),
'severity' => $term_exists ? REQUIREMENT_OK : REQUIREMENT_WARNING
];
}
return $requirements;
}
/**
* Helper; determine if a term with the target URI exists.
*
* @return bool
* TRUE if a term (at least one) with the target URI exists; otherwise, FALSE.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
function _islandora_fits_term_exists() {
$table_exists = \Drupal::database()->schema()->tableExists('taxonomy_term__field_external_uri');
if (!$table_exists) {
// XXX: If the table does not exist, then avoid attempting to make a query
// making use of the non-existent table.
return FALSE;
}
$query = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->getQuery()
->condition('vid', 'islandora_media_use')
->condition('field_external_uri.uri', 'https://projects.iq.harvard.edu/fits')
->count();
$count = $query->execute();
return $count > 0;
}

6
modules/islandora_fits/islandora_fits.libraries.yml

@ -0,0 +1,6 @@
islandora_fits:
version: 1.x
css:
theme:
css/islandora_fits.css: {}

107
modules/islandora_fits/islandora_fits.module

@ -0,0 +1,107 @@
<?php
/**
* @file
* Contains islandora_fits.module.
*/
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\media\MediaInterface;
use Drupal\file\Entity\File;
use Drupal\media\Entity\Media;
use Drupal\node\Entity\NodeType;
/**
* Implements hook_help().
*/
function islandora_fits_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help for the islandora_fits module.
case 'help.page.islandora_fits':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Enables Technical Metadata derivative generation') . '</p>';
return $output;
default:
}
}
/**
* Implements hook_theme().
*/
function islandora_fits_theme($existing, $type, $theme, $path) {
return [
'fits' => [
'variables' => [
'title' => 'FITS data',
'link' => NULL,
'output' => [],
],
],
];
}
/**
* Implements hook_entity_extra_field_info().
*/
function islandora_fits_entity_extra_field_info() {
$entityFieldManager = \Drupal::service('entity_field.manager');
$node_types = NodeType::loadMultiple();
$extra = [];
$types = [];
foreach ($node_types as $node_type) {
$types[] = $node_type->get('type');
}
foreach ($types as $bundle) {
$fields = $entityFieldManager->getFieldDefinitions('node', $bundle);
if (array_key_exists('field_model', $fields) && array_key_exists('field_member_of', $fields)) {
$extra['node'][$bundle]['display']['islandora_fits_checksum'] = [
'label' => t('File Checksum'),
'description' => t('Checksum as discovered by FITs webservice'),
'weight' => 100,
'visible' => TRUE,
];
}
}
return $extra;
}
/**
* Implements hook_entity_view().
*/
function islandora_fits_entity_view(array &$build, $entity, $display, $view_mode) {
$route_match_item = \Drupal::routeMatch()->getParameters()->all();
$current_entity = reset($route_match_item);
if ($entity === $current_entity) {
$medias = \Drupal::entityQuery(
'media')
->condition('field_media_of', $entity->id())
->condition('bundle', 'fits_technical_metadata')
->execute();
$mid = \reset($medias);
if ($mid) {
$fits = Media::load($mid);
$checksum = $fits->get('fits_ois_file_information_md5che')->value;
if ($checksum) {
$build['islandora_fits_checksum'] = [
'#type' => 'container',
'#attributes' => [
'id' => 'field-islandora-checksum',
],
'checksum' => [
'#type' => 'item',
'#title' => t('MD5 Checksum'),
'internal_uri' => [
'#type' => 'markup',
'#markup' => $checksum,
],
],
];
}
}
}
}

4
modules/islandora_fits/islandora_fits.services.yml

@ -0,0 +1,4 @@
services:
islandora_fits.transformxml:
class: Drupal\islandora_fits\Services\XMLTransform
arguments: ['@renderer','@entity_field.manager', '@messenger']

25
modules/islandora_fits/migrations/islandora_fits_tags.yml

@ -0,0 +1,25 @@
---
id: islandora_fits_tags
migration_tags:
- islandora
migration_group: islandora
label: "FITS Term(s)"
source:
plugin: embedded_data
data_rows:
- vid: islandora_media_use
name: FITS File
description: Technical Metadata associated with an original media file
uri: https://projects.iq.harvard.edu/fits
ids:
uri:
type: string
process:
vid: vid
name: name
description: description
field_external_uri/uri: uri
destination:
plugin: entity:taxonomy_term
migration_dependencies:
required: { }

58
modules/islandora_fits/src/Plugin/Action/GenerateFitsDerivative.php

@ -0,0 +1,58 @@
<?php
namespace Drupal\islandora_fits\Plugin\Action;
use Drupal\Core\Form\FormStateInterface;
use Drupal\islandora\Plugin\Action\AbstractGenerateDerivative;
/**
* Emits a Node for generating fits derivatives event.
*
* @Action(
* id = "generate_fits_derivative",
* label = @Translation("Generate a Technical metadata derivative"),
* type = "node"
* )
*/
class GenerateFitsDerivative extends AbstractGenerateDerivative {
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
$config = parent::defaultConfiguration();
$config['path'] = '[date:custom:Y]-[date:custom:m]/[node:nid]-[term:name].xml';
$config['mimetype'] = 'application/xml';
$config['queue'] = 'islandora-connector-fits';
$config['destination_media_type'] = 'file';
return $config;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['mimetype']['#description'] = t('Mimetype to convert to (e.g. application/xml, etc...)');
$form['mimetype']['#value'] = 'application/xml';
$form['mimetype']['#type'] = 'hidden';
unset($form['args']);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::validateConfigurationForm($form, $form_state);
$exploded_mime = explode('/', $form_state->getValue('mimetype'));
if ($exploded_mime[0] != 'application') {
$form_state->setErrorByName(
'mimetype',
t('Please enter file mimetype (e.g. application/xml.)')
);
}
}
}

92
modules/islandora_fits/src/Plugin/Field/FieldFormatter/FitsFormatter.php

@ -0,0 +1,92 @@
<?php
namespace Drupal\islandora_fits\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\file\Entity\File;
use Drupal\Core\Link;
use Drupal\Core\Url;
/**
* Plugin implementation of the 'fits_formatter' formatter.
*
* @FieldFormatter(
* id = "fits_formatter",
* label = @Translation("Fits formatter"),
* field_types = {
* "file"
* }
* )
*/
class FitsFormatter extends FormatterBase {
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
// Implement default settings.
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
return [
// Implement settings form.
] + parent::settingsForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [];
// Implement settings summary.
return $summary;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
foreach ($items as $delta => $item) {
$elements[$delta] = ['#markup' => $this->viewValue($item)];
}
return $elements;
}
/**
* Generate the output appropriate for one field item.
*
* @param \Drupal\Core\Field\FieldItemInterface $item
* One field item.
*
* @return string
* The textual output generated.
*/
protected function viewValue(FieldItemInterface $item) {
$transformer = \Drupal::getContainer()->get('islandora_fits.transformxml');
$fileItem = $item->getValue();
$file = File::load($fileItem['target_id']);
$url = Url::fromUri(file_create_url($file->getFileUri()));
$link = Link::fromTextAndUrl("Link to XML", $url);
$link = $link->toRenderable();
$contents = file_get_contents($file->getFileUri());
if (mb_detect_encoding($contents) != 'UTF-8') {
$contents = utf8_encode($contents);
}
$output = $transformer->transformFits($contents);
$output['#link'] = $link;
$output['#title'] = $this->t("FITS Metadata");
return \Drupal::service('renderer')->render($output);
}
}

429
modules/islandora_fits/src/Services/XMLTransform.php

@ -0,0 +1,429 @@
<?php
namespace Drupal\islandora_fits\Services;
use Drupal\Component\Utility\Xss;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Drupal\Core\Entity\EntityFieldManager;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
use Drupal\media\MediaInterface;
use DrupalCodeGenerator\Command\Drupal_8\Form\Simple;
/**
* Class XMLTransform.
*/
class XMLTransform extends ServiceProviderBase {
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
private $renderer;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
private $entityManager;
/**
* List of characters to switch.
*
* @var array
*/
private $forbidden;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
private $messenger;
/**
* XMLTransform constructor.
*
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
* @param \Drupal\Core\Entity\EntityManagerInterface $entityManager
* The entity manager.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
public function __construct(RendererInterface $renderer, EntityFieldManager $entityManager, MessengerInterface $messenger) {
$this->renderer = $renderer;
$this->entityManager = $entityManager;
$this->messenger = $messenger;
$this->forbidden = ['-', ' '];
}
/**
* Transforms FITS xml into renderable array.
*
* @param string $input_xml
* Xml to be transformed.
*
* @return array|null
* Array of parsed results.
*/
public function transformFits($input_xml) {
$utf8 = mb_detect_encoding($input_xml, 'UTF-8', TRUE);
if (!$utf8) {
$input_xml = utf8_encode($input_xml);
}
try {
$xml = new \SimpleXMLElement($input_xml);
}
catch (\Exception $e) {
$this->messenger->addWarning(t('File does not contain valid xml.'));
return;
}
$xml->registerXPathNamespace('fits', 'http://hul.harvard.edu/ois/xml/ns/fits/fits_output');
$fits_metadata = $this->islandoraFitsChildXpath($xml);
// Get the value of the 'externalIdentifier' element generated by Droid.
if ($xml->identification->identity->externalIdentifier['type'] == 'puid') {
$puid = $xml->identification->identity->externalIdentifier;
}
$headers = [
'label' => t('Field'),
'value' => t('Value'),
];
if (count($fits_metadata) == 0) {
$variables['islandora_fits_table']['empty'] = '';
$variables['islandora_fits_fieldsets']['empty'] = [
'#type' => 'markup',
'#markup' => t('No technical metadata found.'),
];
}
else {
if ($xml->identification->identity->externalIdentifier['type'] == 'puid') {
$fits_metadata['Droid'] = ['PUID' => $puid];
}
foreach ($fits_metadata as $tool_name => $vals_array) {
$variables['islandora_fits_data'][$tool_name] = [];
$rows = &$variables['islandora_fits_data'][$tool_name];
foreach ($vals_array as $field => $val_array) {
if (!array_key_exists($field, $rows) && $field != 'Filepath') {
$rows[$field] = [
['data' => Xss::filter($field), 'class' => 'islandora_fits_table_labels'],
];
foreach ($val_array as $value) {
if (!isset($rows[$field]['value'])) {
$rows[$field]['value'] = ['data' => Xss::filter($value), 'class' => 'islandora_fits_table_values'];
}
else {
$data = $rows[$field]['value']['data'] .= ' - ' . Xss::filter($value);
$rows[$field]['value'] = ['data' => $data, 'class' => 'islandora_fits_table_values'];
}
}
}
$table_attributes = ['class' => ['islandora_fits_table']];
$table = [
'header' => $headers,
'rows' => $rows,
'attributes' => $table_attributes,
];
$variables['islandora_fits_table'][$tool_name] = $table;
$variables['islandora_fits_fieldsets'][$tool_name] = [
'#theme' => 'table',
'#header' => $headers,
'#rows' => $rows,
'#attributes' => $table_attributes,
'#header_columns' => 4,
];
}
}
}
$fieldsets = $variables['islandora_fits_fieldsets'];
$output = [];
foreach ($fieldsets as $title => $fieldset) {
$output[] = [
'title' => $title,
'data' => $fieldset,
];
}
$renderable = [
'#theme' => 'fits',
'#output' => $output,
'#attached' => [
'library' => [
'islandora_fits/islandora_fits',
],
],
];
return $renderable;
}
/**
* Finds the the first set of children from the FITS xml.
*
* Once it has these it passes them off recursively.
*
* @param \SimpleXMLElement
* The SimpleXMLElement to parse.
*
* @return array
* An array containing key/value pairs of fields and data.
*/
private function islandoraFitsChildXpath(\SimpleXMLElement $xml) {
$results = $xml->xpath('/*|/*/fits:metadata');
$output = [];
foreach ($results as $result) {
$this->islandoraFitsChildren($result, $output);
}
return $output;
}
/**
* Finds children for fits module.
*
* Recursive function that searches continuously until
* we grab the node's text value and add to
* the output array.
*
* @param \SimpleXMLElement $child
* The current child that we are searching through.
* @param array $output
* An array containing key/value pairs of fields and data.
*/
private function islandoraFitsChildren(\SimpleXMLElement $child, array &$output) {
$grandchildren = $child->xpath('*/*');
if (count($grandchildren) > 0) {
foreach ($grandchildren as $grandchild) {
$this->islandoraFitsChildren($grandchild, $output);
}
}
else {
$text_results = $child->xpath('text()');
$tool_name = FALSE;
if ($text_results) {
foreach ($text_results as $text) {
foreach ($text->attributes() as $key => $value) {
if ($key === 'toolname') {
$tool_name = trim((string) $value);
}
}
$output_text = trim((string) $text);
if (!empty($output_text)) {
$fits_out = $this->islandoraFitsContructOutput($child->getName(), $tool_name);
$tool_label = $fits_out['tool'];
$field_label = $fits_out['name'];
// Need to check if the label already exists in our output
// such that we do not duplicate entries.
if ($tool_label) {
if (isset($output[$tool_label])) {
if (!array_key_exists($field_label, $output[$tool_label])) {
$output[$tool_label][$field_label][] = $output_text;
}
else {
if (!in_array($output_text, $output[$tool_label][$field_label])) {
$output[$tool_label][$field_label][] = $output_text;
}
}
}
else {
$output[$tool_label][$field_label][] = $output_text;
}
}
// No tool attribute.
else {
if (isset($output['Unknown'][$field_label])) {
if (!in_array($output_text, $output['Unknown'][$field_label])) {
$output['Unknown'][$field_label][] = $output_text;
}
}
else {
$output['Unknown'][$field_label][] = $output_text;
}
}
}
}
}
}
}
/**
* Builds display by parsing strings.
*
* @param string $node_name
* Name of the current node that we will display.
* @param string $tool_name
* Name of the tool used to generate the metadata.
*
* @return array
* Constructed node name for output.
*/
private function islandoraFitsContructOutput(string $node_name, string $tool_name) {
// Construct an arbitrary string with all capitals in it.
$capitals = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$name_array = str_split($node_name);
$space_position = [];
// Check to see which characters are capitals so we can split
// them up for cleaner display.
foreach ($name_array as $key => $value) {
if (strpos($capitals, $value) !== FALSE && $key !== 0) {
$space_position[] = $key;
}
}
if (count($space_position)) {
// Needed in event we add multiple spaces so need to keep track.
$pos_offset = 0;
foreach ($space_position as $pos) {
$node_name = substr_replace($node_name, ' ', $pos + $pos_offset, 0);
$pos_offset++;
}
}
$node_name = ucwords($node_name);
return ['name' => $node_name, 'tool' => ucwords($tool_name)];
}
/**
* Adds fields to content type.
*
* @param string $input_xml
* Input to be transformed.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*
* @return bool
* Fields to be formatted.
*/
public function addMediaFields(string $input_xml) {
$fields_added = FALSE;
$data = $this->transformFits($input_xml);
$all_fields = [];
foreach ($data['#output'] as $datum) {
$all_fields = array_merge($all_fields, $this->harvestValues($datum));
}
$to_process = $this->normalizeNames($all_fields);
foreach ($to_process as $field) {
$exists = FieldStorageConfig::loadByName('media', $field['field_name']);
if (!$exists) {
$field_storage = FieldStorageConfig::create([
'entity_type' => 'media',
'field_name' => $field['field_name'],
'type' => 'text',
]);
$field_storage->save();
}
$bundle_fields = $this->entityManager->getFieldDefinitions('media', 'fits_technical_metadata');
$bundle_keys = array_keys($bundle_fields);
if (!in_array($field['field_name'], $bundle_keys)) {
$field_storage = FieldStorageConfig::loadByName('media', $field['field_name']);
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'fits_technical_metadata',
'label' => $field['field_label'],
])->save();
$fields_added = TRUE;
}
}
return $fields_added;
}
/**
* Populates media.
*
* @param string $input_xml
* String to be transformed.
* @param \Drupal\media\MediaInterface $media
* Media to which transformed xml is to be added.
*/
public function populateMedia(string $input_xml, MediaInterface &$media) {
$data = $this->transformFits($input_xml);
$all_fields = [];
foreach ($data['#output'] as $datum) {
$all_fields = array_merge($all_fields, $this->harvestValues($datum));
}
$to_add = [];
foreach ($all_fields as $label => $field_value) {
$lower = strtolower($label);
$normalized = str_replace($this->forbidden, '_', $lower);
$field_name = substr("fits_$normalized", 0, 32);
$to_add[$field_name] = $field_value;
}
foreach ($to_add as $field_name => $field_value) {
$media->set($field_name, $field_value);
}
}
/**
* Extracts and labels content.
*
* @param array $input
* Values to be harvested and prepped.
*
* @return array
* Fields prepped for display.
*/
private function harvestValues(array $input) {
$fields = [];
$label = str_replace(' ', '_', $input['title']);
$rows = $input['data']['#rows'];
foreach ($rows as $key => $value) {
$fields["{$label}_{$key}"] = $value['value']['data'];
}
return $fields;
}
/**
* Create standardized machine name fields.
*
* @param array $names
* Names to be normalized.
*
* @return array
* Normalized names.
*/
private function normalizeNames(array $names) {
$normalized_names = [];
foreach ($names as $label => $field_value) {
$lower = strtolower($label);
$normalized = str_replace($this->forbidden, '_', $lower);
$field_name = substr("fits_$normalized", 0, 32);
$normalized_names[] = [
'field_label' => $label,
'field_name' => $field_name,
'field_value' => $field_value,
];
}
return $normalized_names;
}
/**
* Checks to see if new fields are required.
*
* @param string $input_xml
* Xml to be checked.
*
* @return bool
* Whether fields havbe been added.
*/
public function checkNew(string $input_xml) {
$fields_added = FALSE;
$data = $this->transformFits($input_xml);
$all_fields = [];
foreach ($data['#output'] as $datum) {
$all_fields = array_merge($all_fields, $this->harvestValues($datum));
}
$to_process = $this->normalizeNames($all_fields);
foreach ($to_process as $field) {
$bundle_fields = $this->entityManager->getFieldDefinitions('media', 'fits_technical_metadata');
$bundle_keys = array_keys($bundle_fields);
if (!in_array($field['field_name'], $bundle_keys)) {
$fields_added = TRUE;
}
}
return $fields_added;
}
}

13
modules/islandora_fits/templates/fits.html.twig

@ -0,0 +1,13 @@
{{ attach_library('css/islandora_fits') }}
<h1>{{ title }}</h1>
<p>{{ link }}</p>
<div>
{% for entry in output %}
<h2> {{ entry.title }} </h2>
<div>{{ entry.data }}</div>
{% endfor %}
</div>

46
modules/islandora_fits/tests/src/Functional/LoadTest.php

@ -0,0 +1,46 @@
<?php
namespace Drupal\Tests\islandora_fits\Functional;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
/**
* Simple test to ensure that main page loads with module enabled.
*
* @group islandora_fits
*/
class LoadTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['islandora_fits'];
/**
* A user with permission to administer site configuration.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->user = $this->drupalCreateUser(['administer site configuration']);
$this->drupalLogin($this->user);
}
/**
* Tests that the home page loads with a 200 response.
*/
public function testLoad() {
$this->drupalGet(Url::fromRoute('<front>'));
$this->assertSession()->statusCodeEquals(200);
}
}
Loading…
Cancel
Save