From e436b56e9e5da37ea305818962f77ef608125d1b Mon Sep 17 00:00:00 2001 From: Alexander O'Neill Date: Tue, 23 Nov 2010 17:35:47 -0400 Subject: [PATCH] Migration to GitHub --- CollectionClass.inc | 400 + CollectionPolicy.inc | 602 + ConnectionHelper.inc | 79 + ContentModel.inc | 2016 ++ LICENSE.txt | 674 + MimeClass.inc | 336 + ObjectHelper.inc | 1025 + SearchClass.inc | 601 + SecurityClass.inc | 203 + XMLDatastream.inc | 277 + api/dublin_core.inc | 124 + api/fedora_collection.inc | 133 + api/fedora_export.inc | 179 + api/fedora_item.inc | 780 + api/fedora_utils.inc | 88 + api/rels-ext.inc | 43 + api/tagging.inc | 69 + .../COLLECTION-COLLECTION POLICY.xml | 21 + collection_policies/FLV-COLLECTION POLICY.xml | 67 + .../Image-COLLECTION POLICY.xml | 70 + collection_policies/JPG-COLLECTION POLICY.xml | 70 + collection_policies/PDF-COLLECTION POLICY.xml | 1 + .../PERSONAL-COLLECTION-POLICY.xml | 79 + collection_policies/README.txt | 7 + .../REFWORKS_COLLECTION_POLICY.xml | 11 + .../RIRI COLLECTION POLICY.xml | 67 + .../book_collection_policy.xml | 69 + .../large_image_collection_policy.xml | 84 + .../slide_collection_policy.xml | 76 + collection_policy.xsd | 47 + collection_views/COLLECTION_VIEW.xml | 264 + collection_views/Collection_QUERY.txt | 7 + .../Coverflow_Collection_View.xsl | 79 + .../Coverflow_PRE_Collection_View.xsl | 83 + collection_views/FLV-COLLECTION VIEW(2).xml | 212 + collection_views/README.txt | 11 + collection_views/REFWORKS-COLLECTION_VIEW.xml | 215 + collection_views/SIMPLE-COLLECTION_VIEW.xml | 215 + .../SmileyStuff-COLLECTION_VIEW.xml | 67 + collection_views/default-sparqltoHtml.xsl | 124 + collection_views/simple_list_view.xml | 28 + collection_views/yui_coverflow/css/test.css | 18 + .../yui_coverflow/js/CoverFlow.js | 936 + collection_views/yui_coverflow/js/test.js | 37 + content_modeller/.buildpath | 5 + content_modeller/.project | 28 + content_modeller/.settings/.jsdtscope | 12 + .../.settings/org.eclipse.php.core.prefs | 3 + ...rg.eclipse.wst.jsdt.ui.superType.container | 1 + .../org.eclipse.wst.jsdt.ui.superType.name | 1 + content_modeller/css/content_modeller.css | 33 + content_modeller/css/jquery.jnotify.css | 16 + content_modeller/css/jquery.treeview.css | 68 + .../images/ui-bg_flat_0_aaaaaa_40x100.png | Bin 0 -> 180 bytes .../images/ui-bg_flat_75_ffffff_40x100.png | Bin 0 -> 178 bytes .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin 0 -> 120 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 105 bytes .../images/ui-bg_glass_75_dadada_1x400.png | Bin 0 -> 111 bytes .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin 0 -> 110 bytes .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin 0 -> 119 bytes .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin 0 -> 101 bytes .../images/ui-icons_222222_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_2e83ff_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_454545_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_888888_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_cd0a0a_256x240.png | Bin 0 -> 4369 bytes .../css/smoothness/jquery-ui-1.8.1.custom.css | 486 + content_modeller/images/add.png | Bin 0 -> 386 bytes content_modeller/images/ajax-loader.gif | Bin 0 -> 673 bytes content_modeller/images/blue_down.png | Bin 0 -> 28401 bytes content_modeller/images/blue_up.png | Bin 0 -> 27615 bytes content_modeller/images/edit.gif | Bin 0 -> 364 bytes content_modeller/images/file.gif | Bin 0 -> 110 bytes content_modeller/images/folder-closed.gif | Bin 0 -> 105 bytes content_modeller/images/folder.gif | Bin 0 -> 106 bytes content_modeller/images/minus.gif | Bin 0 -> 837 bytes content_modeller/images/plus.gif | Bin 0 -> 841 bytes content_modeller/images/purge.gif | Bin 0 -> 622 bytes content_modeller/images/red_info.png | Bin 0 -> 29159 bytes content_modeller/images/remove.png | Bin 0 -> 745 bytes .../images/treeview-black-line.gif | Bin 0 -> 1877 bytes content_modeller/images/treeview-black.gif | Bin 0 -> 1216 bytes .../images/treeview-default-line.gif | Bin 0 -> 1993 bytes content_modeller/images/treeview-default.gif | Bin 0 -> 1222 bytes .../images/treeview-famfamfam-line.gif | Bin 0 -> 807 bytes .../images/treeview-famfamfam.gif | Bin 0 -> 1280 bytes .../images/treeview-gray-line.gif | Bin 0 -> 1877 bytes content_modeller/images/treeview-gray.gif | Bin 0 -> 1230 bytes content_modeller/images/treeview-red-line.gif | Bin 0 -> 1877 bytes content_modeller/images/treeview-red.gif | Bin 0 -> 1230 bytes content_modeller/images/view.gif | Bin 0 -> 362 bytes .../islandora_content_modeller.info | 7 + .../islandora_content_modeller.module | 4376 ++++ content_modeller/js/content_modeller.js | 271 + content_modeller/js/jquery.cookie.js | 92 + content_modeller/js/jquery.jnotify.js | 143 + content_modeller/js/jquery.treeview.min.js | 254 + content_modeller/treeview.inc | 56 + content_modeller/treeview/changelog.txt | 29 + content_modeller/treeview/images/add.png | Bin 0 -> 386 bytes content_modeller/treeview/images/file.gif | Bin 0 -> 110 bytes .../treeview/images/folder-closed.gif | Bin 0 -> 105 bytes content_modeller/treeview/images/folder.gif | Bin 0 -> 106 bytes content_modeller/treeview/images/minus.gif | Bin 0 -> 837 bytes content_modeller/treeview/images/plus.gif | Bin 0 -> 841 bytes content_modeller/treeview/images/purge.gif | Bin 0 -> 622 bytes content_modeller/treeview/images/remove.png | Bin 0 -> 745 bytes .../treeview/images/treeview-black-line.gif | Bin 0 -> 1877 bytes .../treeview/images/treeview-black.gif | Bin 0 -> 1216 bytes .../treeview/images/treeview-default-line.gif | Bin 0 -> 1993 bytes .../treeview/images/treeview-default.gif | Bin 0 -> 1222 bytes .../images/treeview-famfamfam-line.gif | Bin 0 -> 807 bytes .../treeview/images/treeview-famfamfam.gif | Bin 0 -> 1280 bytes .../treeview/images/treeview-gray-line.gif | Bin 0 -> 1877 bytes .../treeview/images/treeview-gray.gif | Bin 0 -> 1230 bytes .../treeview/images/treeview-red-line.gif | Bin 0 -> 1877 bytes .../treeview/images/treeview-red.gif | Bin 0 -> 1230 bytes content_modeller/treeview/images/view.gif | Bin 0 -> 362 bytes .../treeview/jquery.treeview.async.js | 72 + content_modeller/treeview/jquery.treeview.css | 68 + content_modeller/treeview/jquery.treeview.js | 251 + .../treeview/jquery.treeview.min.js | 15 + .../treeview/jquery.treeview.pack.js | 16 + content_modeller/treeview/jquery.treeview.zip | Bin 0 -> 71199 bytes .../treeview/lib/jquery.cookie.js | 92 + content_modeller/treeview/lib/jquery.js | 3363 +++ content_modeller/treeview/todo | 8 + content_models/COLLECTIONCM.xml | 1 + content_models/CRITTERS.xml | 105 + content_models/FAS_slideCModel.xml | 166 + content_models/README.txt | 3 + content_models/REFWORKSCM.xml | 76 + content_models/REFWORKSCM_1.xml | 76 + content_models/STANDARD JPG.xml | 198 + content_models/STANDARD PDF.xml | 236 + content_models/STANDARD_FLVCM.xml | 218 + content_models/STANDARD_IMAGECM.xml | 271 + content_models/STANDARD_JPG.xml | 198 + content_models/STRICT_PDFCM.xml | 107 + content_models/ilives_bookCModel.xml | 524 + content_models/ilives_jp2Sdef.xml | 73 + content_models/ilives_jp2Sdep-pageCModel.xml | 179 + content_models/ilives_tei2htmlSdef.xml | 72 + .../ilives_tei2htmlSdep-pageCModel.xml | 234 + content_models/ilives_viewerSdef.xml | 72 + .../ilives_viewerSdep-bookCModel.xml | 157 + content_models/islandora_collectionCModel.xml | 580 + content_models/islandora_herbCModel.xml | 1851 ++ .../islandora_jp2Sdep-slideCModel.xml | 277 + content_models/islandora_largeimages.xml | 298 + content_models/islandora_mapCModel.xml | 366 + content_models/islandora_mods2htmlSdef.xml | 110 + content_models/islandora_mods2htmlSdep.xml | 464 + content_models/islandora_slideCModel.xml | 1466 ++ .../islandora_viewerSdep-slideCModel.xml | 311 + example_collection_views/MHL-sparqltoHtml.xsl | 196 + example_collection_views/critter.xsl | 33 + example_collection_views/flv-sparqltoHtml.xsl | 212 + example_collection_views/mlp-sparqltoHtml.xsl | 193 + example_collection_views/refworks.xsl | 92 + .../riri-sparqltoHtml.xsl | 217 + example_collection_views/sparqltoHtml.xsl | 125 + fedora_repository.info | 8 + fedora_repository.install | 14 + fedora_repository.module | 1716 ++ fesl/fedora_fesl.info | 7 + fesl/fedora_fesl.module | 110 + fesl/fesl_block.inc | 95 + fesl/sample_policies/access-public.xml | 71 + .../sample_policies/collection-feslpolicy.xml | 44 + flash/flvplayer.swf | Bin 0 -> 33478 bytes formClass.inc | 857 + form_elements/css/copyright.css | 7 + form_elements/css/filechooser.css | 93 + form_elements/css/list.css | 25 + form_elements/css/people.css | 137 + .../ui-bg_diagonals-thick_18_b81900_40x40.png | Bin 0 -> 260 bytes .../ui-bg_diagonals-thick_20_666666_40x40.png | Bin 0 -> 251 bytes .../images/ui-bg_flat_10_000000_40x100.png | Bin 0 -> 178 bytes .../images/ui-bg_glass_100_f6f6f6_1x400.png | Bin 0 -> 104 bytes .../images/ui-bg_glass_100_fdf5ce_1x400.png | Bin 0 -> 125 bytes .../images/ui-bg_glass_65_ffffff_1x400.png | Bin 0 -> 105 bytes .../ui-bg_gloss-wave_35_f6a828_500x100.png | Bin 0 -> 3762 bytes .../ui-bg_highlight-soft_100_eeeeee_1x100.png | Bin 0 -> 90 bytes .../ui-bg_highlight-soft_75_ffe45c_1x100.png | Bin 0 -> 129 bytes .../images/ui-icons_222222_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_228ef1_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_ef8c08_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_ffd27a_256x240.png | Bin 0 -> 4369 bytes .../images/ui-icons_ffffff_256x240.png | Bin 0 -> 4369 bytes .../ui-lightness/jquery-ui-1.8.4.custom.css | 572 + form_elements/images/add_group.png | Bin 0 -> 2575 bytes form_elements/images/add_user.png | Bin 0 -> 2261 bytes form_elements/images/ajax-loader.gif | Bin 0 -> 673 bytes form_elements/images/application.png | Bin 0 -> 9068 bytes form_elements/images/date.png | Bin 0 -> 3716 bytes form_elements/images/editdelete.png | Bin 0 -> 2975 bytes form_elements/images/groupevent.png | Bin 0 -> 2409 bytes form_elements/images/image.png | Bin 0 -> 18873 bytes form_elements/images/lgpl-3.0.txt | 168 + form_elements/images/minus_small.png | Bin 0 -> 560 bytes form_elements/images/sound.png | Bin 0 -> 9689 bytes form_elements/images/txt.png | Bin 0 -> 5765 bytes form_elements/images/video.png | Bin 0 -> 6865 bytes form_elements/includes/autocomplete.inc | 224 + form_elements/includes/creative_commons.inc | 117 + form_elements/includes/filechooser.inc | 138 + form_elements/includes/people.inc | 278 + form_elements/islandora_form_elements.info | 7 + form_elements/islandora_form_elements.module | 913 + form_elements/js/copyright.js | 48 + form_elements/js/filechooser.js | 90 + form_elements/js/jcarousellite_1.0.1.js | 343 + form_elements/js/jquery.breakly-1.0.js | 58 + form_elements/js/jquery.easing.1.1.js | 105 + form_elements/js/jquery.loadImages.1.0.1.js | 33 + .../js/jquery.loadImages.1.0.1.min.js | 1 + form_elements/js/jquery.loadImages.1.0.1.zip | Bin 0 -> 906 bytes form_elements/js/jquery.mousewheel.min.js | 11 + form_elements/js/jquery.tag.editor-min.js | 8 + form_elements/js/jquery.tag.editor.js | 214 + form_elements/js/jquery.ui.core.js | 281 + form_elements/js/jquery.ui.datepicker.js | 1732 ++ form_elements/js/jquery.ui.widget.js | 237 + form_elements/js/otherselect.js | 30 + form_elements/js/people_ahah.js | 90 + form_elements/js/tageditor_1-4-1.zip | Bin 0 -> 6803 bytes form_elements/xml/gacs.xml | 5562 +++++ form_elements/xml/languages.xml | 19575 ++++++++++++++++ form_elements/xml/relators.rdf | 4297 ++++ ilives/book.inc | 250 + ilives/fedora_ilives.info | 7 + ilives/fedora_ilives.install | 65 + ilives/fedora_ilives.module | 508 + ilives/image_rotator_tagger_block.inc | 107 + ilives/searchTerms.xml | 67 + ilives/xml/ilives_CollectionModel.xml | 138 + ilives/xml/ilives_bookCModel.xml | 524 + ilives/xml/ilives_collection.xml | 56 + ilives/xml/ilives_figuresCModel.xml | 20 + ilives/xml/ilives_jp2Sdef.xml | 81 + ilives/xml/ilives_jp2Sdep-pageCModel.xml | 179 + ilives/xml/ilives_pageCModel.xml | 101 + ilives/xml/ilives_tei2htmlSdef.xml | 64 + ilives/xml/ilives_tei2htmlSdep-pageCModel.xml | 226 + ilives/xml/ilives_viewerSdef.xml | 64 + ilives/xml/ilives_viewerSdep-bookCModel.xml | 133 + ilives/xml/ilives_viewerSdep-pageCModel.xml | 133 + ilives/xsl/book_view.xsl | 65 + ilives/xsl/book_view.xsl.bak | 45 + ilives/xsl/book_view.xsl2009-05-26 | 44 + ilives/xsl/pageResults.xsl | 160 + ilives/xsl/results.xsl | 344 + images/Crystal_Clear_mimetype_pdf.png | Bin 0 -> 6427 bytes images/Gnome-emblem-photos.png | Bin 0 -> 16243 bytes images/Thumbs.db | Bin 0 -> 14336 bytes images/contentModel.jpg | Bin 0 -> 7833 bytes images/edit.gif | Bin 0 -> 364 bytes images/export.png | Bin 0 -> 2942 bytes images/export_big.png | Bin 0 -> 1943 bytes images/exported_big.png | Bin 0 -> 2208 bytes images/flashThumb.jpg | Bin 0 -> 760 bytes images/ingest.png | Bin 0 -> 1197 bytes images/list-add.png | Bin 0 -> 386 bytes images/noImage.jpg | Bin 0 -> 1427 bytes images/purge.gif | Bin 0 -> 622 bytes images/purge_big.png | Bin 0 -> 2340 bytes images/remove_icon.png | Bin 0 -> 745 bytes images/replace.png | Bin 0 -> 674 bytes images/smileytn.png | Bin 0 -> 17808 bytes images/view.gif | Bin 0 -> 362 bytes .../foxml/islandora-collectionCModel.xml | 216 + installer_files/foxml/islandora-demos.xml | 5 + .../foxml/islandora-pdfcollection.xml | 186 + .../foxml/islandora-strictpdfCModel.xml | 808 + installer_files/foxml/islandora-top.xml | 150 + islandoracm.xsd | 167 + js/iiv/slideviewer.js.bak | 497 + js/printer_tool.js | 14 + js/swfobject.js | 217 + plugins/CollectionFormBuilder.inc | 45 + plugins/CreateCollection.inc | 22 + plugins/DarwinCore.inc | 317 + plugins/DemoFormBuilder.inc | 77 + plugins/DocumentConverter.inc | 79 + plugins/Flv.inc | 271 + plugins/FlvFormBuilder.inc | 64 + plugins/FormBuilder.inc | 354 + plugins/ImageManipulation.inc | 200 + plugins/ModsFormBuilder.inc | 626 + plugins/Newspaper.inc | 98 + plugins/PersonalCollectionClass.inc | 204 + plugins/Refworks.inc | 449 + plugins/ShowDemoStreamsInFieldSets.inc | 25 + plugins/ShowStreamsInFieldSets.inc | 188 + plugins/fedora_attach/fedora_attach.admin.inc | 34 + plugins/fedora_attach/fedora_attach.info | 8 + plugins/fedora_attach/fedora_attach.install | 14 + plugins/fedora_attach/fedora_attach.module | 491 + plugins/fedora_imageapi.info | 8 + plugins/fedora_imageapi.module | 116 + plugins/herbarium.inc | 176 + plugins/map_viewer.inc | 55 + plugins/newspaper/newspapers_guardian.xml | 378 + plugins/newspaper/newspapers_issueCModel.xml | 161 + plugins/newspaper/newspapers_pageCModel.xml | 149 + .../newspapers_viewerSdep-issueCModel.xml | 241 + .../newspapers_viewerSdep-pageCModel.xml | 217 + plugins/nmlt/cck/scorm_fedora_type.txt | 223 + .../nmlt_collection_policy.xml | 22 + .../nmlt_collection_view.xslt | 23 + plugins/nmlt/collection_policies/query.txt | 5 + .../content_models/islandora_SCORMCModel.xml | 20 + plugins/nmlt/fedora_nmlt.info | 9 + plugins/nmlt/fedora_nmlt.install | 48 + plugins/nmlt/fedora_nmlt.module | 12 + plugins/nmlt/scorm.inc | 367 + plugins/pidfield/pidfield.info | 8 + plugins/pidfield/pidfield.install | 39 + plugins/pidfield/pidfield.module | 464 + plugins/slide_viewer.inc | 63 + plugins/tagging_form.inc | 122 + policies/noObjectEditPolicy.xml | 169 + policies/viewANDeditbyrole.xml | 80 + searchTerms.xml | 79 + solrSearchTerms.xml | 79 + tests/README_TESTING.txt | 42 + tests/fedora_repository.test | 153 + tests/test_files/CCITT_5.jp2 | 0 tests/test_files/CCITT_5.tiff | Bin 0 -> 32502 bytes tests/test_files/CCITT_5_uncompressed.jp2 | Bin 0 -> 40323 bytes tests/test_files/CCITT_5_uncompressed.tiff | Bin 0 -> 514102 bytes tests/test_files/lorem_ipsum.pdf | Bin 0 -> 14493 bytes .../islandora_workflow_client.info | 7 + .../islandora_workflow_client.module | 345 + workflow_client/plugins/create_jp2.inc | 83 + workflow_client/plugins/image_resize.inc | 69 + workflow_client/plugins/jhove.inc | 82 + workflow_client/plugins/mods_extend.inc | 123 + workflow_client/plugins/solr_index.inc | 109 + workflow_client/plugins/xslt.inc | 67 + workflow_client/process.inc | 31 + workflow_client/send.php | 83 + workflow_client/test.php | 61 + workflow_client/test.xml | 74 + workflow_client/test2.php | 68 + workflow_client/workflow.inc | 262 + workflow_client/workflow.xsd | 45 + workflow_client/xsl/jhove-mix.xsl | 14 + workflow_client/xsl/mods-solr.xsl | 790 + workflow_client/xsl/mods_to_dc.xsl | 419 + xsl/advanced_search_results.xsl | 322 + xsl/browseIndexToResultPage.xslt | 417 + xsl/convertQDC.xsl | 33 + xsl/refworks.xsl | 92 + xsl/results.xsl | 316 + xsl/romeo.xsl | 29 + xsl/sparql_to_html.xsl | 247 + xsl/specdwc.xsl | 43 + 359 files changed, 85399 insertions(+) create mode 100644 CollectionClass.inc create mode 100644 CollectionPolicy.inc create mode 100644 ConnectionHelper.inc create mode 100644 ContentModel.inc create mode 100644 LICENSE.txt create mode 100644 MimeClass.inc create mode 100644 ObjectHelper.inc create mode 100644 SearchClass.inc create mode 100644 SecurityClass.inc create mode 100644 XMLDatastream.inc create mode 100644 api/dublin_core.inc create mode 100644 api/fedora_collection.inc create mode 100644 api/fedora_export.inc create mode 100644 api/fedora_item.inc create mode 100644 api/fedora_utils.inc create mode 100644 api/rels-ext.inc create mode 100644 api/tagging.inc create mode 100644 collection_policies/COLLECTION-COLLECTION POLICY.xml create mode 100644 collection_policies/FLV-COLLECTION POLICY.xml create mode 100644 collection_policies/Image-COLLECTION POLICY.xml create mode 100644 collection_policies/JPG-COLLECTION POLICY.xml create mode 100644 collection_policies/PDF-COLLECTION POLICY.xml create mode 100644 collection_policies/PERSONAL-COLLECTION-POLICY.xml create mode 100644 collection_policies/README.txt create mode 100644 collection_policies/REFWORKS_COLLECTION_POLICY.xml create mode 100644 collection_policies/RIRI COLLECTION POLICY.xml create mode 100644 collection_policies/book_collection_policy.xml create mode 100644 collection_policies/large_image_collection_policy.xml create mode 100644 collection_policies/slide_collection_policy.xml create mode 100644 collection_policy.xsd create mode 100644 collection_views/COLLECTION_VIEW.xml create mode 100644 collection_views/Collection_QUERY.txt create mode 100644 collection_views/Coverflow_Collection_View.xsl create mode 100644 collection_views/Coverflow_PRE_Collection_View.xsl create mode 100644 collection_views/FLV-COLLECTION VIEW(2).xml create mode 100644 collection_views/README.txt create mode 100644 collection_views/REFWORKS-COLLECTION_VIEW.xml create mode 100644 collection_views/SIMPLE-COLLECTION_VIEW.xml create mode 100644 collection_views/SmileyStuff-COLLECTION_VIEW.xml create mode 100644 collection_views/default-sparqltoHtml.xsl create mode 100644 collection_views/simple_list_view.xml create mode 100644 collection_views/yui_coverflow/css/test.css create mode 100644 collection_views/yui_coverflow/js/CoverFlow.js create mode 100644 collection_views/yui_coverflow/js/test.js create mode 100644 content_modeller/.buildpath create mode 100644 content_modeller/.project create mode 100644 content_modeller/.settings/.jsdtscope create mode 100644 content_modeller/.settings/org.eclipse.php.core.prefs create mode 100644 content_modeller/.settings/org.eclipse.wst.jsdt.ui.superType.container create mode 100644 content_modeller/.settings/org.eclipse.wst.jsdt.ui.superType.name create mode 100644 content_modeller/css/content_modeller.css create mode 100644 content_modeller/css/jquery.jnotify.css create mode 100644 content_modeller/css/jquery.treeview.css create mode 100644 content_modeller/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png create mode 100644 content_modeller/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png create mode 100644 content_modeller/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png create mode 100644 content_modeller/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png create mode 100644 content_modeller/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png create mode 100644 content_modeller/css/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png create mode 100644 content_modeller/css/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png create mode 100644 content_modeller/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png create mode 100644 content_modeller/css/smoothness/images/ui-icons_222222_256x240.png create mode 100644 content_modeller/css/smoothness/images/ui-icons_2e83ff_256x240.png create mode 100644 content_modeller/css/smoothness/images/ui-icons_454545_256x240.png create mode 100644 content_modeller/css/smoothness/images/ui-icons_888888_256x240.png create mode 100644 content_modeller/css/smoothness/images/ui-icons_cd0a0a_256x240.png create mode 100644 content_modeller/css/smoothness/jquery-ui-1.8.1.custom.css create mode 100644 content_modeller/images/add.png create mode 100644 content_modeller/images/ajax-loader.gif create mode 100644 content_modeller/images/blue_down.png create mode 100644 content_modeller/images/blue_up.png create mode 100644 content_modeller/images/edit.gif create mode 100644 content_modeller/images/file.gif create mode 100644 content_modeller/images/folder-closed.gif create mode 100644 content_modeller/images/folder.gif create mode 100644 content_modeller/images/minus.gif create mode 100644 content_modeller/images/plus.gif create mode 100644 content_modeller/images/purge.gif create mode 100644 content_modeller/images/red_info.png create mode 100644 content_modeller/images/remove.png create mode 100644 content_modeller/images/treeview-black-line.gif create mode 100644 content_modeller/images/treeview-black.gif create mode 100644 content_modeller/images/treeview-default-line.gif create mode 100644 content_modeller/images/treeview-default.gif create mode 100644 content_modeller/images/treeview-famfamfam-line.gif create mode 100644 content_modeller/images/treeview-famfamfam.gif create mode 100644 content_modeller/images/treeview-gray-line.gif create mode 100644 content_modeller/images/treeview-gray.gif create mode 100644 content_modeller/images/treeview-red-line.gif create mode 100644 content_modeller/images/treeview-red.gif create mode 100644 content_modeller/images/view.gif create mode 100644 content_modeller/islandora_content_modeller.info create mode 100644 content_modeller/islandora_content_modeller.module create mode 100644 content_modeller/js/content_modeller.js create mode 100644 content_modeller/js/jquery.cookie.js create mode 100644 content_modeller/js/jquery.jnotify.js create mode 100644 content_modeller/js/jquery.treeview.min.js create mode 100644 content_modeller/treeview.inc create mode 100644 content_modeller/treeview/changelog.txt create mode 100644 content_modeller/treeview/images/add.png create mode 100644 content_modeller/treeview/images/file.gif create mode 100644 content_modeller/treeview/images/folder-closed.gif create mode 100644 content_modeller/treeview/images/folder.gif create mode 100644 content_modeller/treeview/images/minus.gif create mode 100644 content_modeller/treeview/images/plus.gif create mode 100644 content_modeller/treeview/images/purge.gif create mode 100644 content_modeller/treeview/images/remove.png create mode 100644 content_modeller/treeview/images/treeview-black-line.gif create mode 100644 content_modeller/treeview/images/treeview-black.gif create mode 100644 content_modeller/treeview/images/treeview-default-line.gif create mode 100644 content_modeller/treeview/images/treeview-default.gif create mode 100644 content_modeller/treeview/images/treeview-famfamfam-line.gif create mode 100644 content_modeller/treeview/images/treeview-famfamfam.gif create mode 100644 content_modeller/treeview/images/treeview-gray-line.gif create mode 100644 content_modeller/treeview/images/treeview-gray.gif create mode 100644 content_modeller/treeview/images/treeview-red-line.gif create mode 100644 content_modeller/treeview/images/treeview-red.gif create mode 100644 content_modeller/treeview/images/view.gif create mode 100644 content_modeller/treeview/jquery.treeview.async.js create mode 100644 content_modeller/treeview/jquery.treeview.css create mode 100644 content_modeller/treeview/jquery.treeview.js create mode 100644 content_modeller/treeview/jquery.treeview.min.js create mode 100644 content_modeller/treeview/jquery.treeview.pack.js create mode 100644 content_modeller/treeview/jquery.treeview.zip create mode 100644 content_modeller/treeview/lib/jquery.cookie.js create mode 100644 content_modeller/treeview/lib/jquery.js create mode 100644 content_modeller/treeview/todo create mode 100644 content_models/COLLECTIONCM.xml create mode 100644 content_models/CRITTERS.xml create mode 100644 content_models/FAS_slideCModel.xml create mode 100644 content_models/README.txt create mode 100644 content_models/REFWORKSCM.xml create mode 100644 content_models/REFWORKSCM_1.xml create mode 100644 content_models/STANDARD JPG.xml create mode 100644 content_models/STANDARD PDF.xml create mode 100644 content_models/STANDARD_FLVCM.xml create mode 100644 content_models/STANDARD_IMAGECM.xml create mode 100644 content_models/STANDARD_JPG.xml create mode 100644 content_models/STRICT_PDFCM.xml create mode 100644 content_models/ilives_bookCModel.xml create mode 100644 content_models/ilives_jp2Sdef.xml create mode 100644 content_models/ilives_jp2Sdep-pageCModel.xml create mode 100644 content_models/ilives_tei2htmlSdef.xml create mode 100644 content_models/ilives_tei2htmlSdep-pageCModel.xml create mode 100644 content_models/ilives_viewerSdef.xml create mode 100644 content_models/ilives_viewerSdep-bookCModel.xml create mode 100644 content_models/islandora_collectionCModel.xml create mode 100644 content_models/islandora_herbCModel.xml create mode 100644 content_models/islandora_jp2Sdep-slideCModel.xml create mode 100644 content_models/islandora_largeimages.xml create mode 100644 content_models/islandora_mapCModel.xml create mode 100644 content_models/islandora_mods2htmlSdef.xml create mode 100644 content_models/islandora_mods2htmlSdep.xml create mode 100644 content_models/islandora_slideCModel.xml create mode 100644 content_models/islandora_viewerSdep-slideCModel.xml create mode 100644 example_collection_views/MHL-sparqltoHtml.xsl create mode 100644 example_collection_views/critter.xsl create mode 100644 example_collection_views/flv-sparqltoHtml.xsl create mode 100644 example_collection_views/mlp-sparqltoHtml.xsl create mode 100644 example_collection_views/refworks.xsl create mode 100644 example_collection_views/riri-sparqltoHtml.xsl create mode 100644 example_collection_views/sparqltoHtml.xsl create mode 100644 fedora_repository.info create mode 100644 fedora_repository.install create mode 100644 fedora_repository.module create mode 100644 fesl/fedora_fesl.info create mode 100644 fesl/fedora_fesl.module create mode 100644 fesl/fesl_block.inc create mode 100644 fesl/sample_policies/access-public.xml create mode 100644 fesl/sample_policies/collection-feslpolicy.xml create mode 100644 flash/flvplayer.swf create mode 100644 formClass.inc create mode 100644 form_elements/css/copyright.css create mode 100644 form_elements/css/filechooser.css create mode 100644 form_elements/css/list.css create mode 100644 form_elements/css/people.css create mode 100644 form_elements/css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png create mode 100644 form_elements/css/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png create mode 100644 form_elements/css/ui-lightness/images/ui-bg_flat_10_000000_40x100.png create mode 100644 form_elements/css/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png create mode 100644 form_elements/css/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png create mode 100644 form_elements/css/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png create mode 100644 form_elements/css/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png create mode 100644 form_elements/css/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png create mode 100644 form_elements/css/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png create mode 100644 form_elements/css/ui-lightness/images/ui-icons_222222_256x240.png create mode 100644 form_elements/css/ui-lightness/images/ui-icons_228ef1_256x240.png create mode 100644 form_elements/css/ui-lightness/images/ui-icons_ef8c08_256x240.png create mode 100644 form_elements/css/ui-lightness/images/ui-icons_ffd27a_256x240.png create mode 100644 form_elements/css/ui-lightness/images/ui-icons_ffffff_256x240.png create mode 100644 form_elements/css/ui-lightness/jquery-ui-1.8.4.custom.css create mode 100755 form_elements/images/add_group.png create mode 100755 form_elements/images/add_user.png create mode 100644 form_elements/images/ajax-loader.gif create mode 100644 form_elements/images/application.png create mode 100755 form_elements/images/date.png create mode 100755 form_elements/images/editdelete.png create mode 100755 form_elements/images/groupevent.png create mode 100644 form_elements/images/image.png create mode 100644 form_elements/images/lgpl-3.0.txt create mode 100644 form_elements/images/minus_small.png create mode 100644 form_elements/images/sound.png create mode 100644 form_elements/images/txt.png create mode 100644 form_elements/images/video.png create mode 100644 form_elements/includes/autocomplete.inc create mode 100644 form_elements/includes/creative_commons.inc create mode 100644 form_elements/includes/filechooser.inc create mode 100644 form_elements/includes/people.inc create mode 100644 form_elements/islandora_form_elements.info create mode 100644 form_elements/islandora_form_elements.module create mode 100644 form_elements/js/copyright.js create mode 100644 form_elements/js/filechooser.js create mode 100644 form_elements/js/jcarousellite_1.0.1.js create mode 100644 form_elements/js/jquery.breakly-1.0.js create mode 100644 form_elements/js/jquery.easing.1.1.js create mode 100644 form_elements/js/jquery.loadImages.1.0.1.js create mode 100644 form_elements/js/jquery.loadImages.1.0.1.min.js create mode 100644 form_elements/js/jquery.loadImages.1.0.1.zip create mode 100644 form_elements/js/jquery.mousewheel.min.js create mode 100644 form_elements/js/jquery.tag.editor-min.js create mode 100644 form_elements/js/jquery.tag.editor.js create mode 100644 form_elements/js/jquery.ui.core.js create mode 100644 form_elements/js/jquery.ui.datepicker.js create mode 100644 form_elements/js/jquery.ui.widget.js create mode 100644 form_elements/js/otherselect.js create mode 100755 form_elements/js/people_ahah.js create mode 100644 form_elements/js/tageditor_1-4-1.zip create mode 100644 form_elements/xml/gacs.xml create mode 100644 form_elements/xml/languages.xml create mode 100644 form_elements/xml/relators.rdf create mode 100644 ilives/book.inc create mode 100644 ilives/fedora_ilives.info create mode 100644 ilives/fedora_ilives.install create mode 100644 ilives/fedora_ilives.module create mode 100755 ilives/image_rotator_tagger_block.inc create mode 100644 ilives/searchTerms.xml create mode 100644 ilives/xml/ilives_CollectionModel.xml create mode 100644 ilives/xml/ilives_bookCModel.xml create mode 100644 ilives/xml/ilives_collection.xml create mode 100644 ilives/xml/ilives_figuresCModel.xml create mode 100644 ilives/xml/ilives_jp2Sdef.xml create mode 100644 ilives/xml/ilives_jp2Sdep-pageCModel.xml create mode 100644 ilives/xml/ilives_pageCModel.xml create mode 100644 ilives/xml/ilives_tei2htmlSdef.xml create mode 100644 ilives/xml/ilives_tei2htmlSdep-pageCModel.xml create mode 100644 ilives/xml/ilives_viewerSdef.xml create mode 100644 ilives/xml/ilives_viewerSdep-bookCModel.xml create mode 100644 ilives/xml/ilives_viewerSdep-pageCModel.xml create mode 100644 ilives/xsl/book_view.xsl create mode 100644 ilives/xsl/book_view.xsl.bak create mode 100644 ilives/xsl/book_view.xsl2009-05-26 create mode 100644 ilives/xsl/pageResults.xsl create mode 100644 ilives/xsl/results.xsl create mode 100644 images/Crystal_Clear_mimetype_pdf.png create mode 100644 images/Gnome-emblem-photos.png create mode 100644 images/Thumbs.db create mode 100644 images/contentModel.jpg create mode 100644 images/edit.gif create mode 100644 images/export.png create mode 100644 images/export_big.png create mode 100644 images/exported_big.png create mode 100644 images/flashThumb.jpg create mode 100644 images/ingest.png create mode 100644 images/list-add.png create mode 100644 images/noImage.jpg create mode 100644 images/purge.gif create mode 100644 images/purge_big.png create mode 100644 images/remove_icon.png create mode 100644 images/replace.png create mode 100644 images/smileytn.png create mode 100644 images/view.gif create mode 100644 installer_files/foxml/islandora-collectionCModel.xml create mode 100644 installer_files/foxml/islandora-demos.xml create mode 100644 installer_files/foxml/islandora-pdfcollection.xml create mode 100644 installer_files/foxml/islandora-strictpdfCModel.xml create mode 100644 installer_files/foxml/islandora-top.xml create mode 100644 islandoracm.xsd create mode 100644 js/iiv/slideviewer.js.bak create mode 100644 js/printer_tool.js create mode 100644 js/swfobject.js create mode 100644 plugins/CollectionFormBuilder.inc create mode 100644 plugins/CreateCollection.inc create mode 100644 plugins/DarwinCore.inc create mode 100644 plugins/DemoFormBuilder.inc create mode 100644 plugins/DocumentConverter.inc create mode 100644 plugins/Flv.inc create mode 100644 plugins/FlvFormBuilder.inc create mode 100644 plugins/FormBuilder.inc create mode 100644 plugins/ImageManipulation.inc create mode 100644 plugins/ModsFormBuilder.inc create mode 100644 plugins/Newspaper.inc create mode 100644 plugins/PersonalCollectionClass.inc create mode 100644 plugins/Refworks.inc create mode 100644 plugins/ShowDemoStreamsInFieldSets.inc create mode 100644 plugins/ShowStreamsInFieldSets.inc create mode 100644 plugins/fedora_attach/fedora_attach.admin.inc create mode 100644 plugins/fedora_attach/fedora_attach.info create mode 100644 plugins/fedora_attach/fedora_attach.install create mode 100644 plugins/fedora_attach/fedora_attach.module create mode 100644 plugins/fedora_imageapi.info create mode 100644 plugins/fedora_imageapi.module create mode 100644 plugins/herbarium.inc create mode 100644 plugins/map_viewer.inc create mode 100644 plugins/newspaper/newspapers_guardian.xml create mode 100644 plugins/newspaper/newspapers_issueCModel.xml create mode 100644 plugins/newspaper/newspapers_pageCModel.xml create mode 100644 plugins/newspaper/newspapers_viewerSdep-issueCModel.xml create mode 100644 plugins/newspaper/newspapers_viewerSdep-pageCModel.xml create mode 100644 plugins/nmlt/cck/scorm_fedora_type.txt create mode 100644 plugins/nmlt/collection_policies/nmlt_collection_policy.xml create mode 100644 plugins/nmlt/collection_policies/nmlt_collection_view.xslt create mode 100644 plugins/nmlt/collection_policies/query.txt create mode 100644 plugins/nmlt/content_models/islandora_SCORMCModel.xml create mode 100644 plugins/nmlt/fedora_nmlt.info create mode 100644 plugins/nmlt/fedora_nmlt.install create mode 100644 plugins/nmlt/fedora_nmlt.module create mode 100644 plugins/nmlt/scorm.inc create mode 100644 plugins/pidfield/pidfield.info create mode 100644 plugins/pidfield/pidfield.install create mode 100644 plugins/pidfield/pidfield.module create mode 100644 plugins/slide_viewer.inc create mode 100644 plugins/tagging_form.inc create mode 100644 policies/noObjectEditPolicy.xml create mode 100644 policies/viewANDeditbyrole.xml create mode 100644 searchTerms.xml create mode 100644 solrSearchTerms.xml create mode 100644 tests/README_TESTING.txt create mode 100644 tests/fedora_repository.test create mode 100644 tests/test_files/CCITT_5.jp2 create mode 100644 tests/test_files/CCITT_5.tiff create mode 100644 tests/test_files/CCITT_5_uncompressed.jp2 create mode 100644 tests/test_files/CCITT_5_uncompressed.tiff create mode 100644 tests/test_files/lorem_ipsum.pdf create mode 100644 workflow_client/islandora_workflow_client.info create mode 100644 workflow_client/islandora_workflow_client.module create mode 100644 workflow_client/plugins/create_jp2.inc create mode 100644 workflow_client/plugins/image_resize.inc create mode 100644 workflow_client/plugins/jhove.inc create mode 100644 workflow_client/plugins/mods_extend.inc create mode 100644 workflow_client/plugins/solr_index.inc create mode 100644 workflow_client/plugins/xslt.inc create mode 100644 workflow_client/process.inc create mode 100644 workflow_client/send.php create mode 100644 workflow_client/test.php create mode 100644 workflow_client/test.xml create mode 100644 workflow_client/test2.php create mode 100644 workflow_client/workflow.inc create mode 100644 workflow_client/workflow.xsd create mode 100644 workflow_client/xsl/jhove-mix.xsl create mode 100644 workflow_client/xsl/mods-solr.xsl create mode 100644 workflow_client/xsl/mods_to_dc.xsl create mode 100644 xsl/advanced_search_results.xsl create mode 100644 xsl/browseIndexToResultPage.xslt create mode 100644 xsl/convertQDC.xsl create mode 100644 xsl/refworks.xsl create mode 100644 xsl/results.xsl create mode 100644 xsl/romeo.xsl create mode 100644 xsl/sparql_to_html.xsl create mode 100644 xsl/specdwc.xsl diff --git a/CollectionClass.inc b/CollectionClass.inc new file mode 100644 index 00000000..553634a4 --- /dev/null +++ b/CollectionClass.inc @@ -0,0 +1,400 @@ +collectionObject = new ObjectHelper($pid); + } + } + + /* gets objects related to this object. must include offset and limit + * calls getRelatedItems but enforces limit and offset + */ + + function getRelatedObjects($pid, $limit, $offset, $itqlquery=NULL) { + module_load_include('inc', 'fedora_repository', 'ObjectHelper'); + $objectHelper = new ObjectHelper(); + if (!isset($itqlquery)) { + // $query_string = $objectHelper->getStream($pid, 'QUERY', 0); + $itqlquery = $objectHelper->getStream($pid, 'QUERY', 0); + } + return $this->getRelatedItems($pid, $itqlquery, $limit, $offset); + } + + /** + * Gets objects related to this item. It will query the object for a Query stream and use that as a itql query + * or if there is no query stream it will use the default. If you pass a query to this method it will use the passed in query no matter what + */ + function getRelatedItems($pid, $itqlquery = NULL, $limit = NULL, $offset = NULL) { + module_load_include('inc', 'fedora_repository', 'ObjectHelper'); + module_load_include('inc', 'fedora_repository', 'api/fedora_utils'); + if (!isset($limit)) { + $limit = 1000; + } + if (!isset($offset)) { + $offset = 0; + } + global $user; + if (!fedora_repository_access(OBJECTHELPER :: $OBJECT_HELPER_VIEW_FEDORA, $pid, $user)) { + drupal_set_message(t("You do not have access to Fedora objects within the attempted namespace or access to Fedora denied."), 'error'); + return ' '; + } + $objectHelper = new ObjectHelper(); + $query_string = $itqlquery; + if (!isset($query_string)) { + $query_string = $objectHelper->getStream($pid, 'QUERY', 0); + if ($query_string == NULL) { + $query_string = 'select $object $title $content from <#ri> + where ($object $title + and $object $content + and ($object + or $object ) + and $object ) + minus $content + order by $title'; + } + } + + $query_string = htmlentities(urlencode($query_string)); + + $content = ''; + $url = variable_get('fedora_repository_url', 'http://localhost:8080/fedora/risearch'); + $url .= "?type=tuples&flush=TRUE&format=Sparql&limit=$limit&offset=$offset&lang=itql&stream=on&query=" . $query_string; + $content .= do_curl($url); + + return $content; + } + + function getCollectionPolicyStream($collection_pid) { + if ($this->collectionPolicyStream != NULL) { + return $this->collectionPolicyStream; + } + $this->collectionPolicyStream = $this->getStream($collection_pid, CollectionClass :: $COLLECTION_CLASS_COLLECTION_POLICY_STREAM, 0); + return $this->collectionPolicyStream; + } + + function getRelationshipElement($collection_pid) { + $stream = $this->getCollectionPolicyStream($collection_pid); + try { + $xml = new SimpleXMLElement($stream); + } catch (Exception $e) { + drupal_set_message(t('Error getting relationship element from policy stream !e', array('!e' => $e->getMessage())), 'error'); + return; + } + $relationship = $xml->relationship[0]; + return $relationship; + } + + function getCollectionViewStream($collection_pid) { + $this->collectionViewStream = $this->getStream($collection_pid, CollectionClass :: $COLLECTION_CLASS_COLLECTION_VIEW_STREAM, 0); + return $this->collectionViewStream; + } + + function getStream($pid, $dsid, $showError = 1) { + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + $item = new fedora_item($pid); + return isset($item->datastreams[$dsid]) ? $item->get_datastream_dissemination($dsid) : NULL; + } + + function getPidNameSpace($pid, $dsid) { + $stream = $this->getCollectionPolicyStream($pid); + try { + $xml = new SimpleXMLElement($stream); + } catch (Exception $e) { + drupal_set_message(t('Error getting PID namespace !e', array('!e' => $e->getMessage())), 'error'); + return; + } + foreach ($xml->contentmodels->contentmodel as $contentModel) { + // $contentModelName=strip_tags($contentModel['name']); + $contentdsid = strip_tags($contentModel->dsid); + if (strtolower($contentdsid) == strtolower($dsid)) { + return strip_tags($contentModel->pid_namespace->asXML()); + } + } + drupal_set_message(t('Error getting PID namespace! using collection pid of !pid and content model of !dsid', array('!pid' => $pid, '!dsid' => $dsid)), 'error'); + return NULL; + } + + /** + * gets a list of content models from a collection policy + */ + function getContentModels($collection_pid, $showError = TRUE) { + module_load_include('inc', 'Fedora_Repository', 'ContentModel'); + $collection_stream = $this->getCollectionPolicyStream($collection_pid); + try { + $xml = new SimpleXMLElement($collection_stream); + } catch (Exception $e) { + if ($showError) { + drupal_set_message(t("!e", array('!e' => $e->getMessage())), 'error'); + } + return NULL; + } + foreach ($xml->contentmodels->contentmodel as $content_model) { + $contentModel = new ContentModel(); + $contentModel->contentModelDsid = $content_model->dsid; + $contentModel->contentModelPid = $content_model->pid; + $contentModel->pidNamespace = $content_model->pidNamespace; + $contentModel->contentModelName = $content_model['name']; + $models[] = $contentModel; + } + return $models; + } + + /** + * using the collection policies pid namespace get a new pid by calling fedora' get next pid and appending it to the namespace + * $pid is the $pid of the content model + * $dsid is the datastream id of the content model. + */ + function getNextPid($pid, $dsid) { + module_load_include('inc', 'Fedora_Repository', 'ConnectionHelper'); + $pidNameSpace = $this->getPidNameSpace($pid, $dsid); + $pname = substr($pidNameSpace, 0, strpos($pidNameSpace, ":")); + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + return Fedora_Item::get_next_pid_in_namespace($pname); + } + + /** + * gets the form handler file, class and method and returns them in an array + * + * @param string $pid The content model PID + * @param string $dsid The content model DSID + * @return array The file, class and method name to handle the ingest form. + */ + function getFormHandler($pid, $dsid) { + $stream = $this->getContentModelStream($pid, $dsid); + try { + $xml = new SimpleXMLElement($stream); + } catch (Exception $e) { + drupal_set_message(t('Error Getting FormHandler: !e', array('!e' => $e->getMessage())), 'error'); + return NULL; + } + $formHandler = $xml->ingest_form; + if ($formHandler != NULL) { + $handlerDsid = strip_tags($formHandler['dsid']); + $handlerMethod = strip_tags($formHandler->form_builder_method->form_handler->asXML()); + $handlerFile = strip_tags($formHandler->form_builder_method->file->asXML()); + $handlerClass = strip_tags($formHandler->form_builder_method->class_name->asXML()); + $handlerModule = strip_tags($formHandler->form_builder_method->module->asXML()); + $returnArray = array(); + $returnArray['module'] = $handlerModule; + $returnArray['method'] = $handlerMethod; + $returnArray['class'] = $handlerClass; + $returnArray['file'] = $handlerFile; + return $returnArray; + } + + drupal_set_message(t("Error getting form handler. No handler found for !pid - !dsid", array('!pid' => $pid, '!dsid' => $dsid)), 'error'); + return NULL; + } + + function getAllowedMimeTypes($contentModelPid, $contentModel_dsid) { + $stream = $this->getContentModelStream($contentModelPid, $contentModel_dsid); + try { + $xml = new SimpleXMLElement($stream); + } catch (Exception $e) { + drupal_set_message(t('Error getting content model stream for mime types !e', array('!e' => $e->getMessage())), 'error'); + return; + } + foreach ($xml->mimetypes->type as $type) { + $types[] = $type; + } + return $types; + } + + /** + * Grabs the rules from the content model stream + * file the file that has been uploaded + */ + function getAndDoRules($file, $mimetype, $pid, $dsid) { + if (!user_access('ingest new fedora objects')) { + drupal_set_message(t('You do not have permission to ingest objects.')); + return FALSE; + } + + $stream = $this->getContentModelStream($pid, $dsid); + + try { + $xml = new SimpleXMLElement($stream); + } catch (Exception $e) { + drupal_set_message(t('Error getting content model stream !e', array('!e' => $e->getMessage())), 'error'); + return FALSE; + } + foreach ($xml->ingest_rules->rule as $rule) { + foreach ($rule->applies_to as $type) { + if (!strcmp(trim($type), trim($mimetype))) { + $methods = $rule->methods->method; + return $this->callMethods($file, $methods); + } + } + } + } + + /** + * calls the methods defined in the content model rules .xml file stored in a Fedora object + */ + function callMethods($file, $methods) { + foreach ($methods as $method) { + $phpFile = strip_tags($method->file->asXML()); + $phpClass = strip_tags($method->class_name->asXML()); + $phpMethod = strip_tags($method->method_name->asXML()); + $file_ext = strip_tags($method->modified_files_ext->asXML()); + $parameters = $method->parameters->parameter; + $dsid = strip_tags($method->datastream_id); + $parametersArray = array(); + if (isset($parameters)) { + foreach ($parameters as $parameter) { + $name = strip_tags($parameter['name']); + $value = strip_tags($parameter->asXML()); + $parametersArray["$name"] = $value; + } + } + // module_load_include( $phpFile, 'Fedora_Repository', ' '); + require_once(drupal_get_path('module', 'fedora_repository') . '/' . $phpFile); + $thisClass = new $phpClass (); + $returnValue = $thisClass->$phpMethod($parametersArray, $dsid, $file, $file_ext); + if (!$returnValue) { + drupal_set_message('Error! Failed running content model method !m !rv', array('!m' => $phpMethod, '!rv' => $returnValue)); + return FALSE; + } + } + return TRUE; + } + + /** + * grabs a xml form definition from a content model and builds + * the form using drupals forms api + */ + function build_ingest_form(&$form, &$form_state, $contentModelPid, $contentModelDsid) { + $stream = $this->getContentModelStream($contentModelPid, $contentModelDsid); + try { + $xml = new SimpleXMLElement($stream); + } catch (Exception $e) { + drupal_set_message(t('Error getting ingest form stream !e', array('!e' => $e->getMessage())), 'error'); + return FALSE; + } + $docRoot = $_SERVER['DOCUMENT_ROOT']; + + $file = (isset($form_state['values']['ingest-file-location']) ? $form_state['values']['ingest-file-location'] : ''); + // $fullPath = $docRoot . '' . $file; + $fullpath = $file; + // $form = array(); + $form['step'] = array( + '#type' => 'hidden', + '#value' => (isset($form_state['values']['step']) ? $form_state['values']['step'] : 0) + 1, + ); + $form['ingest-file-location'] = array( + '#type' => 'hidden', + '#value' => $file, + ); + + $form['content_model_name'] = array( + '#type' => 'hidden', + '#value' => $contentModelDsid + ); + $form['models'] = array(//content models available + '#type' => 'hidden', + '#value' => $form_state['values']['models'], + ); + $form['fullpath'] = array( + '#type' => 'hidden', + '#value' => $fullpath, + ); + + $form['#attributes']['enctype'] = 'multipart/form-data'; + $form['indicator']['ingest-file-location'] = array( + '#type' => 'file', + '#title' => t('Upload Document'), + '#size' => 48, + '#description' => t('Full text'), + ); + if ($xml->ingest_form->hide_file_chooser == 'TRUE') { + $form['indicator']['ingest-file-location']['#type'] = 'hidden'; + } + $ingest_form = $xml->ingest_form; //should only be one + $drupal_module = strip_tags($ingest_form->form_builder_method->module->asXML()); + if (empty($drupal_module)) { + $drupal_module = 'fedora_repository'; + } + $phpFile = strip_tags($ingest_form->form_builder_method->file->asXML()); + $phpClass = strip_tags($ingest_form->form_builder_method->class_name->asXML()); + $phpMethod = strip_tags($ingest_form->form_builder_method->method_name->asXML()); + $dsid = strip_tags($ingest_form['dsid']); + // module_load_include('php', 'Fedora_Repository', $phpFile); + require_once(drupal_get_path('module', $drupal_module) . '/' . $phpFile); + $thisClass = new $phpClass (); + + return $thisClass->$phpMethod($form, $ingest_form, $form_state['values'], $form_state); + } + + //this will also create a personal collection for an existing user if they don't have one + //not using this function currently + function createUserCollection(& $user) { + if (isset($user->fedora_personal_pid)) { + return; + } + $username = array( + 'name' => variable_get('fedora_admin_user', 'fedoraAdmin') + ); + $admin_user = user_load($username); + module_load_include('inc', 'fedora_repository', 'ConnectionHelper'); + $connectionHelper = new ConnectionHelper(); + try { + $soapClient = $connectionHelper->getSoapClient(variable_get('fedora_soap_manage_url', 'http://localhost:8080/fedora/services/management?wsdl')); + $pidNameSpace = variable_get('fedora_repository_pid', 'vre:'); + $pidNameSpace = substr($pidNameSpace, 0, strpos($pidNameSpace, ":")); + $params = array( + 'numPIDs' => '', + 'pidNamespace' => $pidNameSpace + ); + $object = $soapClient->__soapCall('getNextPID', array( + $params + )); + } catch (exception $e) { + drupal_set_message(t('Error getting Next PID: !e', array('!e' => $e->getMessage())), 'error'); + return FALSE; + } + $pid = implode(get_object_vars($object)); + $pidNumber = strstr($pid, ":"); + $pid = $pidNameSpace . ':' . 'USER-' . $user->name . '-' . substr($pidNumber, 1); + $personal_collection_pid = array( + 'fedora_personal_pid' => $pid + ); + module_load_include('inc', 'fedora_repository', 'plugins/PersonalCollectionClass'); + $personalCollectionClass = new PersonalCollectionClass(); + if (!$personalCollectionClass->createCollection($user, $pid, $soapClient)) { + drupal_set_message("Did not create a personal collection object for !u", array('!u' => $user->name)); + return FALSE; //creation failed don't save the collection pid in drupal db + } + user_save($user, $personal_collection_pid); + return TRUE; + } + +} + diff --git a/CollectionPolicy.inc b/CollectionPolicy.inc new file mode 100644 index 00000000..cb769085 --- /dev/null +++ b/CollectionPolicy.inc @@ -0,0 +1,602 @@ +get_datastream_dissemination($dsid); + } else { + $ds=null; + } + + } + + if (!empty($ds) || !$preFetch) { + $ret=new CollectionPolicy($ds, $pid, $dsid); + } + } + catch (SOAPException $e) { + + $ret = FALSE; + } + return $ret; + } + + /** + * Ingests a new Collection Policy datastream to the specified + * PID with the DSID specified. The file should be a valid collection + * policy XML. Returns false on failure. + * + * @param string $pid + * @param string $name + * @param string $cpDsid + * @param string $file + * @return CollectionPolicy $ret + */ + public static function ingestFromFile($pid, $name, $cpDsid, $file) { + $ret = FALSE; + + if (($cp = self::loadFromCollection($pid, $cpDsid)) === FALSE && file_exists($file)) { + $cp = new ContentModel(file_get_contents($file), $pid, $cpDsid); + $rootEl = $cp->xml->getElementsByTagName('collection_policy')->item(0); + $rootEl->setAttribute('name', $name); + + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + $fedoraItem = new Fedora_Item($pid); + $fedoraItem->add_datastream_from_string($cp->dumpXml(), $cpDsid, $name, 'text/xml', 'X'); + $ret = $cp; + } + + return $ret; + } + + /** + * Ingests a new Collection Policy datastream to the specified + * PID with the DSID specified. Clones the collection policy from the + * source collection pid. Returns false on failure. + * + * @param string $pid + * @param string $name + * @param string $cpDsid + * @param string $copy_collection_pid + * @return CollectionPolicy $ret + */ + public static function ingestFromCollection($pid, $name, $cpDsid, $copy_collection_pid) { + $ret = FALSE; + + if (($cp = self::loadFromCollection($pid, $cpDsid)) === FALSE && ($copy_cp = self::loadFromCollection($copy_collection_pid)) !== FALSE && $copy_cp->validate()) { + $newDom = $copy_cp->xml; + $rootEl = $newDom->getElementsByTagName('collection_policy')->item(0); + $rootEl->setAttribute('name', $name); + + $cp = new CollectionPolicy($newDom, $pid, $cpDsid); + + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + $fedoraItem = new Fedora_Item($pid); + $fedoraItem->add_datastream_from_string($cp->dumpXml(), $cpDsid, $name, 'text/xml', 'X'); + $ret = $cp; + } + + return $ret; + } + + /** + * Ingests a new minimum Collection Policy datastream to the specified + * PID with the DSID specified. Initializes the policy with the specified values. + * Returns false on failure + * + * @param string $pid + * @param string $name + * @param string $cpDsid + * @param string $model_pid + * @param string $model_namespace + * @param string $relationshiop + * @param string $searchField + * @param string $searchValue + * @return CollectionPolicy $ret + */ + public static function ingestBlankPolicy($pid, $name, $policyDsid, $model_pid, $model_namespace, $relationship, $searchField, $searchValue) { + $ret = FALSE; + if (($cp = self::loadFromCollection($pid, $modelDsid)) === FALSE) { + module_load_include('inc', 'fedora_repository', 'ContentModel'); + if (($cm = ContentModel::loadFromModel($model_pid)) !== FALSE && $cm->validate()) { + $newDom = new DOMDocument('1.0', 'utf-8'); + $newDom->formatOutput = TRUE; + $rootEl = $newDom->createElement('collection_policy'); + $rootEl->setAttribute('name', $name); + $rootEl->setAttribute('xmlns', self::$XMLNS); + $rootEl->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $rootEl->setAttribute('xsi:schemaLocation', self::$XMLNS .' '. self::$SCHEMA_URI); + + $modelsEl = $newDom->createElement('content_models'); + + $cmEl = $newDom->createElement('content_model'); + $cmEl->setAttribute('name', $cm->getName()); + $cmEl->setAttribute('dsid', $cm->dsid); + $cmEl->setAttribute('namespace', $model_namespace); + $cmEl->setAttribute('pid', $cm->pid); + + $modelsEl->appendChild($cmEl); + $rootEl->appendChild($modelsEl); + + $relEl = $newDom->createElement('relationship', $relationship); + $rootEl->appendChild($relEl); + + $searchTermsEl = $newDom->createElement('search_terms'); + $newTermEl = $newDom->createElement('term', $searchValue); + $newTermEl->setAttribute('field', $searchField); + $searchTermsEl->appendChild($newTermEl); + $rootEl->appendChild($searchTermsEl); + + $newDom->appendChild($rootEl); + + $cp = new CollectionPolicy($newDom, $pid, $policyDsid); + + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + + + $fedoraItem = new Fedora_Item($pid); + $fedoraItem->add_datastream_from_string($cp->dumpXml(), $policyDsid, $name, 'text/xml', 'X'); + $ret = $cp; + } + + } + + return $ret; + } + + /** + * Attempts to convert from the old XML schema to the new by + * traversing the XML DOM and building a new DOM. When done + * $this->xml is replaced by the newly created DOM.. + * + * @return void + */ + protected function convertFromOldSchema() { + if ($this->xml == NULL) { + $this->fetchXml(); + } + $sXml = simplexml_load_string($this->xml->saveXML()); + $newDom = new DOMDocument('1.0', 'utf-8'); + $newDom->formatOutput = TRUE; + + $rootEl = $newDom->createElement('collection_policy'); + $rootEl->setAttribute('name', $sXml['name']); + $rootEl->setAttribute('xmlns', self::$XMLNS); + $rootEl->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $rootEl->setAttribute('xsi:schemaLocation', self::$XMLNS .' '. self::$SCHEMA_URI); + + $content_modelsEl = $newDom->createElement('content_models'); + foreach ($sXml->contentmodels->contentmodel as $contentmodel) { + $content_modelEl = $newDom->createElement('content_model'); + $content_modelEl->setAttribute('name', $contentmodel['name']); + $content_modelEl->setAttribute('dsid', $contentmodel->dsid); + $content_modelEl->setAttribute('namespace', $contentmodel->pid_namespace); + $content_modelEl->setAttribute('pid', $contentmodel->pid); + $content_modelsEl->appendChild($content_modelEl); + } + $rootEl->appendChild($content_modelsEl); + + $search_termsEl = $newDom->createElement('search_terms'); + foreach ($sXml->search_terms->term as $term) { + $termEl = $newDom->createElement('term', $term->value); + $termEl->setAttribute('field', $term->field); + if (strval($sXml->search_terms->default) == $term->field) { + $termEl->setAttribute('default', 'true'); + } + $search_termsEl->appendChild($termEl); + } + $rootEl->appendChild($search_termsEl); + + $relationshipEl = $newDom->createElement('relationship', $sXml->relationship); + $rootEl->appendChild($relationshipEl); + + $newDom->appendChild($rootEl); + + $this->xml = DOMDocument::loadXML($newDom->saveXml()); + } + + + /** + * Gets the name of the relationship to use + * for members of this collection. + * Returns FALSE on failure. + * + * @return string $relationship + */ + public function getRelationship() { + $ret=FALSE; + if ($this->validate()) { + $ret=trim($this->xml->getElementsByTagName('relationship')->item(0)->nodeValue); + } + return $ret; + } + + /** + * Sets the name of the relationship to use + * for members of this collection. + * Returns FALSE on failure. + * + * @param string $relationship + * @return boolean $ret + */ + public function setRelationship($relationship) { + $ret=FALSE; + if ($this->validate()) { + $relationshipEl=$this->xml->getElementsByTagName('relationship')->item(0); + $relationshipEl->nodeValue=trim($relationship); + $ret = TRUE; + } + return $ret; + } + + /** + * Gets the path to the staging area to use for this + * collection. By default recurses to the parent collection + * if the staging area is undefined + * + * @param BOOLEAN $recurse + * @return string $path + */ + public function getStagingArea($recurse=TRUE) { + $ret=FALSE; + if ($this->validate()) { + if ($this->staging_area === NULL) { + $stagingEl=$this->xml->getElementsByTagName('staging_area'); + + if ($stagingEl->length > 0) { + $this->staging_area = trim($stagingEl->item(0)->nodeValue); + } elseif ($recurse) { + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + $item=new Fedora_Item($this->pid); + $rels=$item->get_relationships(); + if (count($rels) > 0) { + foreach ($rels as $rel) { + $cp = CollectionPolicy::loadFromCollection($rel['object']); + if ($cp !== FALSE) { + $this->staging_area = $cp->getStagingArea(); + break; + } + } + } + } + } + + $ret = $this->staging_area; + + } + return $ret; + } + + /** + * Sets the path to the staging area to use for this + * collection. If specified path is blank (or false) it will + * remove the staging are path element from the collection policy. + * + * @param string $path + * + * @return string $relationship + */ + public function setStagingArea($path) { + $ret=FALSE; + if ($this->validate()) { + $rootEl=$this->xml->getElementsByTagName('collection_policy')->item(0); + $stagingEl=$this->xml->getElementsByTagName('staging_area'); + if ($stagingEl->length > 0) { + $stagingEl=$stagingEl->item(0); + if (trim($path) == '') { + $rootEl->removeChild($stagingEl); + } + else { + $stagingEl->nodeValue=trim($path); + } + } + elseif (trim($path) != '') { + $stagingEl=$this->xml->createElement('staging_area', trim($path)); + $rootEl->appendChild($stagingEl); + } + + $ret = TRUE; + } + return $ret; + } + + + /** + * Gets the next available PID for the + * content model specified by the DSID + * parameter. + * + * @param string $dsid + * @return string $nextPid + */ + public function getNextPid($dsid) { + $ret = FALSE; + if (self::validDsid($dsid) && $this->validate()) { + $content_models = $this->xml->getElementsByTagName('content_models')->item(0)->getElementsByTagName('content_model'); + $namespace = FALSE; + for ($i=0; $namespace === FALSE && $i<$content_models->length;$i++) { + if (strtolower($content_models->item($i)->getAttribute('dsid')) == strtolower($dsid)) { + $namespace = $content_models->item($i)->getAttribute('namespace'); + } + } + + $pname = substr($namespace, 0, strpos($namespace, ":")); + + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + $ret = Fedora_Item::get_next_pid_in_namespace($pname); + } + return $ret; + } + + /** + * Gets a list of ContentModel objects supported by this collection. + * + * @return ContentModel[] $models + */ + function getContentModels() { + $ret=FALSE; + if ($this->validate()) { + module_load_include('inc', 'Fedora_Repository', 'ContentModel'); + $ret=array(); + $content_models = $this->xml->getElementsByTagName('content_models')->item(0)->getElementsByTagName('content_model'); + for ($i=0;$i<$content_models->length;$i++) { + $cm=ContentModel::loadFromModel($content_models->item($i)->getAttribute('pid'), + $content_models->item($i)->getAttribute('dsid'), + $content_models->item($i)->getAttribute('namespace'), + $content_models->item($i)->getAttribute('name')); + if ($cm !== FALSE) { + $ret[]=$cm; + } + + } + } + return $ret; + } + + + + /** + * Gets a list of search terms from the Collection Policy. If asArray is set + * it will return an associative array with the value, field name, and the default value. + * If not set, an array of just the values will be returned. If $recurse is TRUE, it will + * recurseively return the parents search terms if it has none until it returns a set of terms or reaches + * the top level collection. If $cache is TRUE, it will return a cached version (if available). + * + * @param boolean $asArray + * @param boolean $recurse + * @param boolean $cache + * @return string[] $ret + */ + function getSearchTerms($asArray = FALSE, $recurse = FALSE, $cache = FALSE) { + $ret = FALSE; + + if ($cache == TRUE && ($cache = cache_get('collection_policy_search_terms-'.$this->pid)) !== 0 ) { + $ret=$cache->data; + } else { + + if ($this->xml == NULL) { + $fedoraItem = new Fedora_Item($this->pid); + $ds = $fedoraItem->get_datastream_dissemination($this->dsid); + $this->xml = DOMDocument::loadXML($ds); + } + + + if ($this->validate()) { + $ret=array(); + $terms= $this->xml->getElementsByTagName('search_terms')->item(0)->getElementsByTagName('term'); + for ($i=0;$i<$terms->length;$i++) { + $default = $terms->item($i)->attributes->getNamedItem('default'); + $default = ($default !== NULL) ? (strtolower($default->nodeValue) == 'true') : FALSE; + $ret[] = ($asArray)?array( 'value' => $terms->item($i)->nodeValue, + 'field' => $terms->item($i)->getAttribute('field'), + 'default' => $default): $terms->item($i)->nodeValue; + } + + + if ($recurse && count($ret) == 0) { + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + $item=new Fedora_Item($this->pid); + $rels=$item->get_relationships(); + if (count($rels) > 0) { + foreach ($rels as $rel) { + $cp = CollectionPolicy::loadFromCollection($rel['object']); + if ($cp !== FALSE) { + $ret = $cp->getSearchTerms($asArray, $recurse); + break; + } + } + } + } + cache_set('collection_policy_search_terms-'.$this->pid, $ret, 'cache', time()+3600); + + } + } + return $ret; + } + + + /** + * Adds a search term to the collection policy. + * Returns fase on failure. + * + * @param string $field + * @param string $value + * @return boolean $success + */ + function addTerm($field, $value) { + $ret=FALSE; + if ($this->validate()) { + $search_termsEl = $this->xml->getElementsByTagName('search_terms')->item(0); + $terms= $search_termsEl->getElementsByTagName('term'); + $found=FALSE; + for ($i=0;!$found && $i<$terms->length;$i++) { + if ($terms->item($i)->getAttribute('field') == $field) { + $found = TRUE; + } + } + + if (!$found) { + $newTermEl = $this->xml->createElement('term', $value); + $newTermEl->setAttribute('field', $field); + $search_termsEl->appendChild($newTermEl); + $ret = TRUE; + } + } + return $ret; + } + + /** + * Removes the search term specified by the field parameter from the collection policy. + * + * @param string $field + * @return boolean $success + */ + function removeTerm($field) { + $ret=FALSE; + if ($this->validate()) { + $search_termsEl = $this->xml->getElementsByTagName('search_terms')->item(0); + $terms = $search_termsEl->getElementsByTagName('term'); + $found = FALSE; + for ($i=0; !$found && $i < $terms->length; $i++) { + if ($terms->item($i)->getAttribute('field') == $field) { + $found = $terms->item($i); + } + } + + if ($found !== FALSE) { + $search_termsEl->removeChild($found); + $ret = TRUE; + } + } + return $ret; + } + + + function setDefaultTerm($field) { + $ret = FALSE; + if ($this->validate()) { + $search_termsEl = $this->xml->getElementsByTagName('search_terms')->item(0); + $terms= $search_termsEl->getElementsByTagName('term'); + $found=FALSE; + for ($i=0;!$found && $i<$terms->length;$i++) { + if ($terms->item($i)->getAttribute('field') == $field) { + $found = $terms->item($i); + } + } + + if ($found !== FALSE) { + for ($i=0;$i<$terms->length;$i++) + if ($terms->item($i)->attributes->getNamedItem('default') !== NULL) { + $terms->item($i)->removeAttribute('default'); + } + $found->setAttribute('default', 'true'); + $ret = TRUE; + } + } + return $ret; + } + + + /** + * Removes the specified content model from the collection policy. This will only + * prevent future ingests of the removed model to the collection. $cm should be + * a valid ContentModel object. Returns false on failure or when the CM was not found in + * the collection policy. + * + * @param ContentModel $cm + * @return boolean $valid + */ + function removeModel($cm) { + $ret=FALSE; + if ($this->validate() && $cm->validate()) { + $contentmodelsEl = $this->xml->getElementsByTagName('content_models'); + $models = $contentmodelsEl->item(0)->getElementsByTagName('content_model'); + $found = FALSE; + for ($i=0; $found === FALSE && $i < $models->length; $i++) { + if ($models->item($i)->getAttribute('pid') == $cm->pid) { + $found=$models->item($i); + } + } + + if ($found !== FALSE && $models->length > 1) { + $contentmodelsEl->item(0)->removeChild($found); + $ret = TRUE; + } + } + return $ret; + } + + + + + function addModel($cm, $namespace) { + $ret = FALSE; + if (self::validPid($namespace) && $this->validate() && $cm->validate()) { + $contentmodelsEl = $this->xml->getElementsByTagName('content_models'); + $models = $contentmodelsEl->item(0)->getElementsByTagName('content_model'); + $found = FALSE; + for ($i=0;!$found && $i<$models->length;$i++) { + if ($models->item($i)->getAttribute('pid') == $cm->pid) + $found = TRUE; + } + + if (!$found) { + $cmEl = $this->xml->createElement('content_model'); + $cmEl->setAttribute('name', $cm->getName()); + $cmEl->setAttribute('dsid', $cm->dsid); + $cmEl->setAttribute('namespace', $namespace); + $cmEl->setAttribute('pid', $cm->pid); + $contentmodelsEl->item(0)->appendChild($cmEl); + } + + $ret = !$found; + } + return $ret; + } + + function getName() { + $ret=FALSE; + if ($this->validate()) { + $ret=$this->xml->getElementsByTagName('collection_policy')->item(0)->getAttribute('name'); + } + return $ret; + } + +} diff --git a/ConnectionHelper.inc b/ConnectionHelper.inc new file mode 100644 index 00000000..11aa868f --- /dev/null +++ b/ConnectionHelper.inc @@ -0,0 +1,79 @@ + $url))); + return NULL; + } + + return $new_url; + } + + function getSoapClient($url = NULL) { + if ($url == NULL) { + $url=variable_get('fedora_soap_url', 'http://localhost:8080/fedora/services/access?wsdl'); + } + global $user; + if ($user->uid == 0) { + //anonymous user. We will need an entry in the fedora users.xml file + //with the appropriate entry for a username of anonymous password of anonymous + try { + $client = new SoapClient($this->_fixURL($url, 'anonymous', 'anonymous'), array( + 'login' => 'anonymous', + 'password' => 'anonymous', + 'exceptions' => TRUE, + )); + } + catch (SoapFault $e) { + drupal_set_message(t("!e", array('!e' => $e->getMessage()))); + return NULL; + } + } + else { + try { + $client = new SoapClient($this->_fixURL($url, $user->name, $user->pass), array( + 'login' => $user->name, + 'password' => $user->pass, + 'exceptions' => TRUE, + )); + } + catch (SoapFault $e) { + drupal_set_message(t("!e", array('!e' => $e->getMessage()))); + return NULL; + } + } + return $client; + } +} + diff --git a/ContentModel.inc b/ContentModel.inc new file mode 100644 index 00000000..b952e2c7 --- /dev/null +++ b/ContentModel.inc @@ -0,0 +1,2016 @@ +get_content_models_list($pid); + foreach ($content_models as $content_model) { + if ($content_model != 'fedora-system:FedoraObject-3.0') { + $ret = self::loadFromModel($content_model); + break; + } + } + } + return $ret; + } + + /** + * Ingests a Content Model from a file to the specified pid/dsid . + * Returns false on failure. + * + * @param string $pid + * @param string $name + * @param string $modelDsid + * @param string $file + * @return ContentModel $cm + */ + public static function ingestFromFile($pid, $name, $modelDsid, $file) { + $ret = FALSE; + + if (($cm = self::loadFromModel($pid, $modelDsid)) === FALSE && file_exists($file)) { + $cm = new ContentModel(file_get_contents($file), $pid, $modelDsid); + $rootEl = $cm->xml->getElementsByTagName('content_model')->item(0); + $rootEl->setAttribute('name', $name); + + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + $fedoraItem = new Fedora_Item($pid); + $fedoraItem->add_datastream_from_string($cm->dumpXml(), $modelDsid, $name, 'text/xml', 'X'); + $ret= $cm; + } + + return $ret; + } + + + /** + * Ingests a Content Model from an existing model to the specified pid/dsid . + * Returns false on failure. + * + * @param string $pid + * @param string $name + * @param string $modelDsid + * @param string $copy_model_pid + * @return ContentModel $cm + */ + public static function ingestFromModel($pid, $name, $modelDsid, $copy_model_pid) { + $ret = FALSE; + + if (($cm = self::loadFromModel($pid, $modelDsid)) === FALSE && ($copy_cm = self::loadFromModel($copy_model_pid)) !== FALSE && $copy_cm->validate()) { + $newDom = $copy_cm->xml; + $rootEl = $newDom->getElementsByTagName('content_model')->item(0); + $rootEl->setAttribute('name', $name); + + $cm = new ContentModel($newDom, $pid, $modelDsid); + + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + $fedoraItem = new Fedora_Item($pid); + $fedoraItem->add_datastream_from_string($cm->dumpXml(), $modelDsid, $name, 'text/xml', 'X'); + $ret = $cm; +} + + return $ret; + } + + + /** + * Ingests a minimum Content Model to the specified pid/dsid. + * Returns false on failure. + * + * @param string $pid + * @param string $name + * @param string $modelDsid + * @param string $defaultMimetype + * @param string $ingestFromDsid + * @param integer $ingestFormPage + * @param boolean $ingestFormHideFileChooser + * @param string $ingestFormModule + * @param string $ingestFormModule + * @param string $ingestFormFile + * @param string $ingestFormClass + * @param string $ingestFormMethod + * @param string $ingestFormHandler + * + * @return ContentModel $cm + */ + public static function ingestBlankModel($pid, $name, $modelDsid, $defaultMimetype, $ingestFormDsid, $ingestFormPage, $ingestFormHideChooser, $ingestFormModule, $ingestFormModule, $ingestFormFile, $ingestFormClass, $ingestFormMethod, $ingestFormHandler) { + $ret = FALSE; + if (($cm = self::loadFromModel($pid, $modelDsid)) === FALSE) { + $newDom = new DOMDocument('1.0', 'utf-8'); + $newDom->formatOutput = TRUE; + $rootEl = $newDom->createElement('content_model'); + $rootEl->setAttribute('name', $name); + $rootEl->setAttribute('xmlns', self::$XMLNS); + $rootEl->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $rootEl->setAttribute('xsi:schemaLocation', self::$XMLNS .' '. self::$SCHEMA_URI); + + $mimeEl = $newDom->createElement('mimetypes'); + $typeEl=$newDom->createElement('type', $defaultMimetype); + $mimeEl->appendChild($typeEl); + $rootEl->appendChild($mimeEl); + + $ingestRulesEl = $newDom->createElement('ingest_rules'); + $rootEl->appendChild($ingestRulesEl); + + $ingestFormEl = $newDom->createElement('ingest_form'); + $ingestFormEl->setAttribute('dsid', $ingestFormDsid); + $ingestFormEl->setAttribute('page', $ingestFormPage); + if ($ingestFormHideChooser == 'true') { + $ingestFormEl->setAttribute('hide_file_chooser', 'true'); + } + + $builderEl= $newDom->createElement('form_builder_method'); + $builderEl->setAttribute('module', $ingestFormModule); + $builderEl->setAttribute('file', $ingestFormFile); + $builderEl->setAttribute('class', $ingestFormClass); + $builderEl->setAttribute('method', $ingestFormMethod); + $builderEl->setAttribute('handler', $ingestFormHandler); + $ingestFormEl->appendChild($builderEl); + + $elementsEl = $newDom->createElement('form_elements'); + $ingestFormEl->appendChild($elementsEl); + $rootEl->appendChild($ingestFormEl); + $newDom->appendChild($rootEl); + + $cm = new ContentModel($newDom, $pid, $modelDsid); + + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + $fedoraItem = new Fedora_Item($pid); + $fedoraItem->add_datastream_from_string($cm->dumpXml(), $modelDsid, $name, 'text/xml', 'X'); + $ret = $cm; + } + + return $ret; + } + + + /** + * Constructs a ContentModel object from the PID of the model in Fedora. + * If DSID is specified it will use that datastream as the model, otherwise it will + * use the default (usually ISLANDORACM). PID_NAMESPACE and name can also be initialized + * from the collection policy. + * Returns false on failure. + * + * NOTE: $name will be overwritten with the content model name found in the datastream + * when the model is first validated.\ + * + * @param string $pid + * @param string $dsid + * @param string $pid_namespace + * @param string $name + * @return ContentModel $cm + */ + public static function loadFromModel($pid, $dsid = NULL, $pid_namespace = NULL, $name = NULL) { + $ret = FALSE; + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + try { + if (self::validPid($pid)) { + $fedoraItem = new Fedora_Item($pid); + $dsid = ($dsid != NULL && self::validDsid($dsid)) ? $dsid : ContentModel::getDefaultDSID(); + $ds = $fedoraItem->get_datastream_dissemination($dsid); + if (!empty($ds)) { + $ret=new ContentModel($ds, $pid, $dsid, $pid_namespace, $name); + } + } + } + catch (SOAPException $e) { + $ret = FALSE; + } + return $ret; + } + + /** + * Constructor + * NOTE: Use the static constructor methods whenever possible. + * + * @param string $xmlStr + * @param string $pid + * @param string $dsid + * @param string $pid_namespace + * @param string $name + * @return XMLDatastream $cm + */ + public function __construct($xmlStr, $pid = NULL, $dsid = NULL, $pid_namespace = NULL, $name = NULL) { + parent::__construct($xmlStr, $pid, $dsid); + $this->pid_namespace = $pid_namespace; + + $this->name = $name; + } + + /** + * Attempts to convert from the old XML schema to the new by + * traversing the XML DOM and building a new DOM. When done + * $this->xml is replaced by the newly created DOM.. + * + * @return void + */ + protected function convertFromOldSchema() { + $sXml = simplexml_load_string($this->xml->saveXML()); + $newDom = new DOMDocument('1.0', 'utf-8'); + $newDom->formatOutput = TRUE; + $rootEl = $newDom->createElement('content_model'); + $rootEl->setAttribute('name', $sXml['name']); + $rootEl->setAttribute('xmlns', self::$XMLNS); + $rootEl->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $rootEl->setAttribute('xsi:schemaLocation', self::$XMLNS .' ' . self::$SCHEMA_URI); + + $mimeEl = $newDom->createElement('mimetypes'); + foreach ($sXml->mimetypes->type as $mime) { + $typeEl = $newDom->createElement('type', $mime); + $mimeEl->appendChild($typeEl); + } + $rootEl->appendChild($mimeEl); + + $ingestRulesEl = $newDom->createElement('ingest_rules'); + foreach ($sXml->ingest_rules->rule as $rule) { + $ruleEl = $newDom->createElement('rule'); + foreach ($rule->applies_to as $appTo) { + $applToEl = $newDom->createElement('applies_to', trim($appTo)); + $ruleEl->appendChild($applToEl); + } + if (isset($rule->methods->method) && count($rule->methods->method) > 0) { + $methodsEl = $newDom->createElement('ingest_methods'); + foreach ($rule->methods->method as $method) { + $methodEl = $newDom->createElement('ingest_method'); + $methodEl->setAttribute('module', $method->module); + $methodEl->setAttribute('file', $method->file); + $methodEl->setAttribute('class', $method->class_name); + $methodEl->setAttribute('method', $method->method_name); + $methodEl->setAttribute('dsid', $method->datastream_id); + $methodEl->setAttribute('modified_files_ext', $method->modified_files_ext); + + if (isset($method->parameters)) { + $paramsEl = $newDom->createElement('parameters'); + foreach ($method->parameters->parameter as $param) { + $paramEl = $newDom->createElement('parameter', $param); + $paramEl->setAttribute('name', $param['name']); + $paramsEl->appendChild($paramEl); + } + $methodEl->appendChild($paramsEl); + } + + + $methodsEl->appendChild($methodEl); + } + $ruleEl->appendChild($methodsEl); + $ingestRulesEl->appendChild($ruleEl); + } + + } + $rootEl->appendChild($ingestRulesEl); + + if (isset($sXml->display_in_fieldset) && count($sXml->display_in_fieldset->datastream) > 0) { + $datastreamsEl = $newDom->createElement('datastreams'); + + foreach ($sXml->display_in_fieldset->datastream as $ds) { + $dsEl = $newDom->createElement('datastream'); + $dsEl->setAttribute('dsid', $ds['id']); + + if (isset($ds->add_datastream_method)) { + $add_ds_methodEl = $newDom->createElement('add_datastream_method'); + $add_ds_methodEl->setAttribute('module', $ds->add_datastream_method->module); + $add_ds_methodEl->setAttribute('file', $ds->add_datastream_method->file); + $add_ds_methodEl->setAttribute('class', $ds->add_datastream_method->class_name); + $add_ds_methodEl->setAttribute('method', $ds->add_datastream_method->method_name); + $add_ds_methodEl->setAttribute('dsid', $ds->add_datastream_method->datastream_id); + $add_ds_methodEl->setAttribute('modified_files_ext', $ds->add_datastream_method->modified_files_ext); + + if (isset($ds->add_datastream_method->parameters)) { + $paramsEl = $newDom->createElement('parameters'); + foreach ($ds->add_datastream_method->parameters->parameter as $param) { + $paramEl = $newDom->createElement('parameter', $param); + $paramEl->setAttribute('name', $param['name']); + $paramsEl->appendChild($paramEl); + } + $add_ds_methodEl->appendChild($paramsEl); + } + + $dsEl->appendChild($add_ds_methodEl); + } + + foreach ($ds->method as $disp_meth) { + $disp_methEl = $newDom->createElement('display_method'); + $disp_methEl->setAttribute('module', $disp_meth->module); + $disp_methEl->setAttribute('file', $disp_meth->file); + $disp_methEl->setAttribute('class', $disp_meth->class_name); + $disp_methEl->setAttribute('method', $disp_meth->method_name); + $dsEl->appendChild($disp_methEl); + } + $datastreamsEl->appendChild($dsEl); + } + $rootEl->appendChild($datastreamsEl); + } + + $ingest_formEl = $newDom->createElement('ingest_form'); + $ingest_formEl->setAttribute('dsid', $sXml->ingest_form['dsid']); + $ingest_formEl->setAttribute('page', $sXml->ingest_form['page']); + if (isset($sXml->ingest_form['hide_file_chooser'])) { + $ingest_formEl->setAttribute('hide_file_chooser', (strtolower($sXml->ingest_form['hide_file_chooser']) == 'true') ? 'true' : 'false'); + } + + $form_builderEl = $newDom->createElement('form_builder_method'); + $form_builderEl->setAttribute('module', $sXml->ingest_form->form_builder_method->module); + $form_builderEl->setAttribute('file', $sXml->ingest_form->form_builder_method->file); + $form_builderEl->setAttribute('class', $sXml->ingest_form->form_builder_method->class_name); + $form_builderEl->setAttribute('method', $sXml->ingest_form->form_builder_method->method_name); + $form_builderEl->setAttribute('handler', $sXml->ingest_form->form_builder_method->form_handler); + $ingest_formEl->appendChild($form_builderEl); + + $form_elementsEl = $newDom->createElement('form_elements'); + foreach ($sXml->ingest_form->form_elements->element as $element) { + + //I found an XML where the label was HTML.. this code will attempt to + //walk the object setting the label to be the first string it finds. + if (count(get_object_vars($element->label)) > 0) { + $obj=$element->label; + while ($obj != NULL && !is_string($obj)) { + $keys = get_object_vars($obj); + $obj = array_shift($keys); + } + $element->label=($obj == NULL) ? '' : $obj; + } + + $elEl = $newDom->createElement('element'); + $elEl->setAttribute('label', $element->label); + $elEl->setAttribute('name', $element->name); + $elEl->setAttribute('type', $element->type); + if (strtolower($element->required) == 'true') { + $elEl->setAttribute('required', 'true'); + } + if (isset($element->description) && trim($element->description) != '') { + $descEl = $newDom->createElement('description', trim($element->description)); + $elEl->appendChild($descEl); + } + if (isset($element->authoritative_list)) { + $authListEl = $newDom->createElement('authoritative_list'); + foreach ($element->authoritative_list->item as $item) { + $itemEl = $newDom->createElement('item', trim($item->value)); + if (trim($item->value) != trim($item->field)) { + $itemEl->setAttribute('field', trim($item->field)); + } + $authListEl->appendChild($itemEl); + } + $elEl->appendChild($authListEl); + } + $form_elementsEl->appendChild($elEl); + } + $ingest_formEl->appendChild($form_builderEl); + $ingest_formEl->appendChild($form_elementsEl); + $rootEl->appendChild($ingest_formEl); + + if (isset($sXml->edit_metadata) && + trim($sXml->edit_metadata->build_form_method->module) != '' && + trim($sXml->edit_metadata->build_form_method->file) != '' && + trim($sXml->edit_metadata->build_form_method->class_name) != '' && + trim($sXml->edit_metadata->build_form_method->method_name) != '' && + trim($sXml->edit_metadata->submit_form_method->method_name) != '' && + trim($sXml->edit_metadata->build_form_method['dsid']) != '' + ) { + $edit_metadata_methodEl= $newDom->createElement('edit_metadata_method'); + $edit_metadata_methodEl->setAttribute('module', $sXml->edit_metadata->build_form_method->module); + $edit_metadata_methodEl->setAttribute('file', $sXml->edit_metadata->build_form_method->file); + $edit_metadata_methodEl->setAttribute('class', $sXml->edit_metadata->build_form_method->class_name); + $edit_metadata_methodEl->setAttribute('method', $sXml->edit_metadata->build_form_method->method_name); + $edit_metadata_methodEl->setAttribute('handler', $sXml->edit_metadata->submit_form_method->method_name); + $edit_metadata_methodEl->setAttribute('dsid', $sXml->edit_metadata->build_form_method['dsid']); + $rootEl->appendChild($edit_metadata_methodEl); + } + + $newDom->appendChild($rootEl); + + $this->xml = DOMDocument::loadXML($newDom->saveXml()); + + } + + /** + * Gets a list of service deployments that this model has. + * + * NOTE: Not currently being used. + * + * @return String[] $serviceDepPids + */ + public function getServices() { + $query = 'select $object $title from <#ri> + where ($object $title + and $object $deploymentOf + and $object + and $object pid .'> + and $object ) + order by $title'; + + module_load_include('inc', 'fedora_repository', 'CollectionClass'); + + $collectionHelper = new CollectionClass(); + $xml = simplexml_load_string($collectionHelper->getRelatedItems($this->pid, $query)); + + $results = array(); + foreach ($xml->results->result as $result) { + $pid = strval(($result->object['uri'])); + $pid = substr($pid, strpos($pid, "/") + 1, strlen($pid)); + $results[] = $pid; + } + + return $results; + } + + /** + * Gets the name of the ContentModel + * Returns false on failure. + * + * @return String $name + */ + public function getName() { + $ret = FALSE; + if ($this->name != NULL) { + $ret = $this->name; + } + elseif ($this->validate()) { + $rootEl = $this->xml->getElementsByTagName('content_model')->item(0); + $this->name = $rootEl->getAttribute('name'); + $ret = $this->name; + } + return $ret; + } + + + /** + * Gets the element corresponding to the datastream specified + * in the element of the schema. + * Returns FALSE on failure. + * + * @param $dsid + * @return DOMElement $datastream + */ + private function getDSModel($dsid) { + $ret = FALSE; + if (self::validDsid($dsid) && $this->validate()) { + $result=$this->xml->getElementsByTagName('datastreams'); + if ($result->length > 0) { + $result=$result->item(0)->getElementsByTagName('datastream'); + for ($i = 0; $ret == FALSE && $i < $result->length; $i++) { + if ($result->item($i)->getAttribute('dsid') == $dsid) + $ret=$result->item($i); + } + } + } + return $ret; + } + + /** + * Gets an array of form elements to use in the ingest form. The results of this array are passed + * to the specified ingest form builder. The form builder can optionally not use the elements as defined + * in the form builder if more complex forms or behaviour is required. + * Each element has the following keys: 'label', 'type', 'required', 'description', and if defined, 'authoritative_list' and/or 'parameters' + * + * @return string[] $elements + */ + public function getIngestFormElements() { + $ret = FALSE; + if ($this->validate()) { + $elements_array = array(); + $form_elements_wrapper = $this->xml->getElementsbyTagName('ingest_form')->item(0)->getElementsByTagName('form_elements'); + + if ($form_elements_wrapper->length == 0) { + return $ret; + } + $elements = $this->xml->getElementsbyTagName('ingest_form')->item(0)->getElementsByTagName('form_elements')->item(0)->getElementsByTagName('element'); + for ($i=0;$i<$elements->length;$i++) { + $desc = $elements->item($i)->getElementsByTagName('description'); + $desc = ($desc->length > 0)?$desc->item(0)->nodeValue:''; + + $label = $elements->item($i)->getAttribute('label'); + if ($label == NULL) { + $label=$elements->item($i)->getAttribute('name'); + } + + $element=array('name' => $elements->item($i)->getAttribute('name'), + 'label' => $label, + 'type' => $elements->item($i)->getAttribute('type'), + 'required' => ($elements->item($i)->getAttribute('required') == 'true') ? TRUE : FALSE, + 'description' => $desc + ); + + $auth_list = $elements->item($i)->getElementsByTagName('authoritative_list'); + if ($auth_list->length > 0) { + $list = array(); + $items = $auth_list->item(0)->getElementsByTagName('item'); + for ($j = 0; $j < $items->length; $j++) { + $field = $items->item($j)->attributes->getNamedItem('field'); + $list[ $items->item($j)->nodeValue ] = ($field !== NULL) ? $field->nodeValue : $items->item($j)->nodeValue; + } + $element['authoritative_list']=$list; + } + + + $params = $elements->item($i)->getElementsByTagName('parameters'); + $list = array(); + if ($params->length > 0) { + $items = $params->item(0)->getElementsByTagName('parameter'); + for ($j = 0; $j < $items->length; $j++) { + $value = $items-> item($j)->nodeValue; + $list[$items->item($j)->getAttribute('name')] = (strtolower($value) == 'true' ? TRUE : (strtolower($value) == 'false' ? FALSE : $value)); + } + } + $element['parameters'] = $list; + + $elements_array[] = $element; + } + $ret = $elements_array; + } + + return $ret; + } + + /** + * Decrements an ingest form element in the list of elements. + * Updates the "order". This method is simply an overload to the incIngestFormElement + * which has a direction parameter. + * + * TODO: Might be useful to move multiple places at once, or define + * a method to move to an absolute position. + * + * @param String $name + * @return boolean $success + */ + public function decIngestFormElement($name) { + return $this->incIngestFormElement($name, 'dec'); + } + + /** + * Increments (or decrements) ingest form element in the list of elements. + * Updates the "order". The $reorder parameter accepts 'inc' or 'dec' to + * specify the direction to move (defaults to increment.) + * + * TODO: Might be useful to move multiple places at once, or define + * a method to move to an absolute position. + * + * @param String $name + * @param String $reorder + * @return boolean $success + */ + public function incIngestFormElement($name, $reorder = 'inc') { + $ret = FALSE; + if ($this->validate()) { + $elementsEl = $this->xml->getElementsbyTagName('ingest_form')->item(0)->getElementsByTagName('form_elements')->item(0); + $elements = $elementsEl->getElementsByTagName('element'); + $found = FALSE; + $refEl = FALSE; + for ($i = 0; $found === FALSE && $i < $elements->length; $i++) { + if ($elements->item($i)->getAttribute('name') == trim($name)) { + if ($reorder == 'inc') { + $found=$elements->item($i); + $refEl = ($i > 0)?$elements->item($i-1):false; + } + else { + $found = ($i + 1 < $elements->length) ? $elements->item($i + 1) : FALSE; + $refEl = $elements->item($i); + } + } + } + + if ($found !== FALSE) { + $elementsEl->removeChild($found); + $elementsEl->insertBefore($found, $refEl); + $ret = TRUE; + } + } + return $ret; + } + + /** + * Removes an ingest form element from the list of ingest form elements. + * @param String $name + * @return boolean $success + */ + public function removeIngestFormElement($name) { + $ret = FALSE; + if ($this->validate()) { + $elementsEl = $this->xml->getElementsbyTagName('ingest_form')->item(0)->getElementsByTagName('form_elements')->item(0); + $elements = $elementsEl->getElementsByTagName('element'); + $found = FALSE; + for ($i = 0; $found === FALSE && $i < $elements->length; $i++) { + if ($elements->item($i)->getAttribute('name') == trim($name)) { + $found=$elements->item($i); + } + } + + if ($found !== FALSE) { + $elementsEl->removeChild($found); + $ret = TRUE; + } + } + return $ret; + } + + /** + * Sets a parameter of an ingest form element. If the value of the element is FALSE the parameter + * will be removed entirely (if you want to store false as a value, then send the String "false"). + * + * @param String $elementName + * @param String $paramName + * @param String $paramValue + * @return boolean success + */ + public function setIngestFormElementParam($name, $paramName, $paramValue) { + $ret = FALSE; + + if ($this->validate()) { + $elements = $this->xml->getElementsbyTagName('ingest_form')->item(0)->getElementsByTagName('form_elements')->item(0)->getElementsByTagName('element'); + $element = FALSE; + for ($i = 0; $element === FALSE && $i < $elements->length; $i++) { + if ($elements->item($i)->getAttribute('name') == $name) { + $element = $elements->item($i); + } + } + + if ($element !== FALSE) { + $paramsEl = $element->getElementsByTagName('parameters'); + if ($paramsEl->length == 0) { + if ($paramValue !== FALSE) { + $paramsEl = $this->xml->createElement('parameters'); + $element->appendChild($paramsEl); + } + else { + $ret = TRUE; + } + } + else { + $paramsEl=$paramsEl->item(0); + } + + if (!$ret) { + $params = $paramsEl->getElementsByTagName('parameter'); + $found = FALSE; + for ($i = 0; $found === FALSE && $i < $params->length; $i++) { + if ($params->item($i)->getAttribute('name') == $paramName) { + $found=$params->item($i); + } + } + + if ($paramValue === FALSE) { + if ($found !== FALSE) { + $paramsEl->removeChild($found); + if ($params->length == 0) { + $element->removeChild($paramsEl); + } + } + $ret = TRUE; + } + else { + if ($found !== FALSE) { + $found->nodeValue = $paramValue; + } + else { + $paramEl=$this->xml->createElement('parameter', $paramValue); + $paramEl->setAttribute('name', $paramName); + $paramsEl->appendChild($paramEl); + } + $ret = TRUE; + } + } + } + } + + return $ret; + } + + +/** + * Gets a list of all parameters that belong to the specified ingest form element. + * + * @param String $elementName + * @return boolean success + */ + public function getIngestFormElementParams($name) { + $ret = FALSE; + + if ($this->validate()) { + $elements = $this->xml->getElementsbyTagName('ingest_form')->item(0)->getElementsByTagName('form_elements')->item(0)->getElementsByTagName('element'); + $element = FALSE; + for ($i=0; $element === FALSE && $i < $elements->length; $i++) { + if ($elements->item($i)->getAttribute('name') == $name) { + $element = $elements->item($i); + } + } + + if ($element !== FALSE) { + $ret = array(); + $paramsEl = $element->getElementsByTagName('parameters'); + + if ($paramsEl->length > 0) { + $params = $paramsEl->item(0)->getElementsByTagName('parameter'); + for ($i = 0; $i < $params->length; $i++) { + $ret[$params->item($i)->getAttribute('name')]=$params->item($i)->nodeValue; + } + } + } + } + + return $ret; + } + + /** + * Edits the ingest form element specified. + * NOTE: The element name can not be changed. To update an elements name + * it must be deleted and added with the new name. + * + * @param String $name + * @param String $label + * @param String $type + * @param boolean $required + * @param String description + * @return boolean success + */ + public function editIngestFormElement($name, $label, $type, $required, $description) { + $ret = FALSE; + if ($this->validate()) { + $elements = $this->xml->getElementsbyTagName('ingest_form')->item(0)->getElementsByTagName('form_elements')->item(0)->getElementsByTagName('element'); + $found = FALSE; + for ($i = 0; $found === FALSE && $i < $elements->length; $i++) { + if ($elements->item($i)->getAttribute('name') == $name) { + $found = $elements->item($i); + } + } + + $found->setAttribute('name', $name); + $found->setAttribute('type', $type); + $found->setAttribute('required', $required ? 'true' : 'false'); + if (trim($label) != '' && trim($label) != trim($name)) { + $found->setAttribute('label', $label); + } + elseif ($found->getAttribute('label') != '') { + $found->removeAttribute('label'); + } + + $descEl=$found->getElementsByTagName('description'); + if (trim($description) != '') { + if ($descEl->length > 0) { + $descEl=$descEl->item(0); + $descEl->nodeValue = $description; + } + else { + $descEl = $this->xml->createElement('description', $description); + $found->appendChild($descEl); + } + } + elseif ($descEl->length > 0) { + $found->removeChild($descEl->item(0)); + } + + if ($found->getAttribute('type') != 'select' && $found->getAttribute('type') != 'radio') { + $authList = $found->getElementsByTagName('authoritative_list'); + if ($authList->length > 0) { + $found->removeChild($authList->item(0)); + } + } + + $ret = TRUE; + } + return $ret; + } + + + /** + * Add an ingest form element to the model. + * + * @param String $name + * @param String $label + * @param String $type + * @param boolean $required + * @param String $description + * @return boolean $success + */ + public function addIngestFormElement($name, $label, $type, $required, $description = '') { + $ret = FALSE; + if ($this->validate()) { + $elements = $this->xml->getElementsbyTagName('ingest_form')->item(0)->getElementsByTagName('form_elements')->item(0); + $elementEl = $this->xml->createElement('element'); + $elementEl->setAttribute('name', $name); + $elementEl->setAttribute('type', $type); + $elementEl->setAttribute('required', $requiredi ? 'true' : 'false'); + if (trim($label) != '' && trim($label) != trim($name)) { + $elementEl->setAttribute('label', $label); + } + if (trim($description) != '') { + $descEl=$this->xml->createElement('description', $description); + $elementEl->appendChild($descEl); + } + $elements->appendChild($elementEl); + + $ret = TRUE; + } + return $ret; + } + + /** + * Decrements an authority list item from a form element in the list of elements. + * Updates the "order". This method is simply an overload to the incAuthListItem + * which has a direction parameter. + + * + * @param String $elementName + * @param String $value + * @return boolean $success + */ + public function decAuthListItem($elementName, $value) { + return $this->incAuthListItem($elementName, $value, 'dec'); + } + + /** + * Increments (or decrements) an authority list item from a form element in the list of elements. + * Updates the "order". + * + * @param String $elementName + * @param String $value + * @param String $direction + * @return boolean $success + */ + public function incAuthListItem($elementName, $value, $reorder = 'inc') { + $ret = FALSE; + if ($this->validate()) { + $elements = $this->xml->getElementsbyTagName('ingest_form')->item(0)->getElementsByTagName('form_elements')->item(0)->getElementsByTagName('element'); + $found = FALSE; + for ($i = 0; $found === FALSE && $i < $elements->length; $i++) { + if ($elements->item($i)->getAttribute('name') == $elementName) { + $found = $elements->item($i); + } + } + + if ($found !== FALSE) { + + $authListEl = $found->getElementsByTagName('authoritative_list'); + if ($authListEl->length > 0) { + $authListEl = $authListEl->item(0); + + $items = $authListEl->getElementsByTagName('item'); + $found = FALSE; + $refEl = FALSE; + for ($i = 0; $found === FALSE && $i < $items->length; $i++) { + if ($items->item($i)->nodeValue == $value) { + if ($reorder == 'inc') { + $refEl = ($i > 0) ? $items->item($i - 1) : FALSE; + $found = $items->item($i); + } + else { + $refEl = $items->item($i); + $found = ($i + 1 < $items->length) ? $items->item($i + 1) : FALSE; + } + } + } + + if ($found !== FALSE && $refEl !== FALSE) { + $authListEl->removeChild($found); + $authListEl->insertBefore($found, $refEl); + $ret = TRUE; + } + } + } + } + return $ret; + } + + /** + * Removes an authority list item from a form element. + @param String $elementName + @param String $value + @return boolean $success + */ + public function removeAuthListItem($elementName, $value) { + $ret = FALSE; + if ($this->validate()) { + $elements = $this->xml->getElementsbyTagName('ingest_form')->item(0)->getElementsByTagName('form_elements')->item(0)->getElementsByTagName('element'); + $found = FALSE; + for ($i = 0; $found === FALSE && $i < $elements->length; $i++) { + if ($elements->item($i)->getAttribute('name') == $elementName) { + $found = $elements->item($i); + } + } + + if ($found !== FALSE) { + $authListEl = $found->getElementsByTagName('authoritative_list'); + if ($authListEl->length > 0) { + $authListEl = $authListEl->item(0); + $items = $authListEl->getElementsByTagName('item'); + $found = FALSE; + for ($i = 0; $found === FALSE && $i < $items->length; $i++) { + if ($items->item($i)->nodeValue == $value) { + $found=$items->item($i); + } + } + + if ($found !== FALSE) { + if ($items->length == 1) { + $found->removeChild($authListEl); + } + else { + $authListEl->removeChild($found); + } + + $ret = TRUE; + } + } + } + } + return $ret; + } + + /** + * Adds an authority list item to a form element. + @param String $elementName + @param String $value + @param String $label (optional) + @return boolean $success + */ + public function addAuthListItem($elementName, $value, $label = '') { + $ret = FALSE; + if ($this->validate()) { + $elements = $this->xml->getElementsbyTagName('ingest_form')->item(0)->getElementsByTagName('form_elements')->item(0)->getElementsByTagName('element'); + $found = FALSE; + for ($i = 0; $found === FALSE && $i < $elements->length; $i++) { + if ($elements->item($i)->getAttribute('name') == $elementName) { + $found = $elements->item($i); + } + } + + if ($found !== FALSE) { + $authListEl = $found->getElementsByTagName('authoritative_list'); + if ($authListEl->length == 0) { + $authListEl = $this->xml->createElement('authoritative_list'); + $found->appendChild($authListEl); + } + else { + $authListEl = $authListEl->item(0); + } + + $items = $authListEl->getElementsByTagName('item'); + $found = FALSE; + for ($i = 0; $found == FALSE && $i < $items->length; $i++) { + if ($items->item($i)->nodeValue == $value) { + $found = TRUE; + } + } + + if (!$found) { + $itemEl = $this->xml->createElement('item', trim($value)); + if (trim($label) != '' && trim($label) != trim($value)) { + $itemEl->setAttribute('field', trim($label)); + } + $authListEl->appendChild($itemEl); + $ret = TRUE; + } + } + + } + return $ret; + } + + /** + * Builds an ingest form using the method specified in element of + * Returns FALSE on failure. + * + * @param &$form + * @param &$form_state + * @return string identifier + */ + public function buildIngestForm(&$form, &$form_state) { + $ret = FALSE; + if ($this->validate()) { + $docRoot = $_SERVER['DOCUMENT_ROOT']; + $file = (isset($form_state['values']['ingest-file-location']) ? $form_state['values']['ingest-file-location'] : ''); + + $fullpath = $file; + + $form['step'] = array( + '#type' => 'hidden', + '#value' => (isset($form_state['values']['step']) ? $form_state['values']['step'] : 0) + 1, + ); + $form['ingest-file-location'] = array( + '#type' => 'hidden', + '#value' => $file, + ); + $form['content_model_name'] = array( + '#type' => 'hidden', + '#value' => 'ISLANDORACM', + ); + $form['models'] = array( //content models available + '#type' => 'hidden', + '#value' => $form_state['values']['models'], + ); + $form['fullpath'] = array( + '#type' => 'hidden', + '#value' => $fullpath, + ); + + $form['#attributes']['enctype'] = 'multipart/form-data'; + $form['indicator']['ingest-file-location'] = array( + '#type' => 'file', + '#title' => t('Upload Document'), + '#size' => 48, + '#description' => t('Full text'), + ); + + $ingest_form = $this->xml->getElementsByTagName('ingest_form')->item(0); + + if (!empty($ingest_form)) { + if (strtolower($ingest_form->getAttribute('hide_file_chooser')) == 'true') { + $form['indicator']['ingest-file-location']['#type'] = 'hidden'; + } + } + $dsid = $ingest_form->getAttribute('dsid'); + + $method=$ingest_form->getElementsByTagName('form_builder_method')->item(0); + $module = $method->getAttribute('module'); + $path=drupal_get_path('module', !empty($module) ? $module : 'fedora_repository') . '/' . $method->getAttribute('file'); + if (!file_exists($path)) { + self::$errors[]='Build Ingest Form: file \''. $path .'\' does not exist.'; + } + else { + @require_once($path); + $className=$method->getAttribute('class'); + $methodName=($method->getAttribute('method')); + if (!class_exists($className)) { + self::$errors[] = 'Build Ingest Form: class \''. $className .' does not exist.'; + } + else { + $class = new $className; + $elements_array=$this->getIngestFormElements(); + if (method_exists($class, $methodName)) { + $ret = $class->$methodName ($form, $elements_array, $form_state); + } + else { + self::$errors[] = 'Build Ingest Form: method \''. $className .'->'. $methodName .'\' does not exist.'; + } + } + } + } + return $ret; + } + + /** + * Builds an edit metadata form using the method specified in the element += * The DSID specified must match the DSID attribute of . Returns FALSE on failure. + * + * @param string $dsid + * @return $form + */ + public function buildEditMetadataForm($pid, $dsid) { + $ret = FALSE; + if (self::validDsid($dsid) && $this->validate()) { + $method = $this->xml->getElementsByTagName('edit_metadata_method'); + if ($method->length > 0 && $method->item(0)->getAttribute('dsid') == $dsid) { + $method = $method->item(0); + $module = $method->getAttribute('module'); + $path = drupal_get_path('module', !empty($module) ? $module : 'fedora_repository') . '/' . $method->getAttribute('file') ; + if (!file_exists($path)) { + self::$errors[] = 'Build Edit Metadata Form: file \''. $path .'\' does not exist.'; + } + else { + @require_once($path ); + $className=$method->getAttribute('class'); + $methodName=($method->getAttribute('method')); + if (!class_exists($className)) { + self::$errors[] = 'Build Edit Metadata Form: class \''. $className .'\' does not exist.'; + } + else { + $class = new $className($pid); + if (!method_exists($class, $methodName)) { + self::$errors[] = 'Build Edit Metadata Form: method \''. $className .'->'. $methodName .'\' does not exist.'; + } + else { + $ret = $class->$methodName(); + } + } + } + } + } + return $ret; + } + + /** + * Handles the edit metadata form using the handler specified in the element + * Returns FALSE on failure. + * + * @param &$form_id + * @param &$form_values + * @param &$soap_client + * @return $result + */ + public function handleEditMetadataForm(&$form_id, &$form_state, &$soap_client) { + global $base_url; + $ret = FALSE; + if ($this->validate()) { + $method = $this->xml->getElementsByTagName('edit_metadata_method'); + if ($method->length > 0) { + $method=$method->item(0); + $module = $method->getAttribute('module'); + $path = drupal_get_path('module', !empty($module) ? $module : 'fedora_repository') . '/' . $method->getAttribute('file'); + if (!file_exists($path)) { + self::$errors[] = 'Handle Edit Metadata Form: file \''. $path .'\' does not exist.'; + } + else { + @require_once($path); + $className = $method->getAttribute('class'); + $methodName = ($method->getAttribute('handler')); + if (!class_exists($className)) { + self::$errors[] = 'Handle Edit Metadata Form: class \''. $className .'\' does not exist.'; + } + else { + $class = new $className($form_state['values']['pid']); + if (!method_exists($class, $methodName)) { + self::$errors[] = 'Handle Edit Metadata Form: metho \''. $className .'->'. $methodName .'\' does not exist.'; + } + else { + $ret = $class->$methodName($form_id, $form_state['values'], $soap_client); + } + } + } + } + else { + // Assume DC form if none is specified. + module_load_include('inc', 'fedora_repository', 'formClass'); + $metaDataForm = new formClass(); + $ret = $metaDataForm->updateMetaData($form_state['values']['form_id'], $form_state['values'], $soap_client); + $form_state['redirect'] = $base_url . '/fedora/repository/' . $form_state['values']['pid']; + + } + } + return $ret; + } + + + /** + * Gets an associative array describing the edit metadata method. + * Array has the following keys: 'file', 'class', 'method', 'handler', 'dsid' + * @return String[] $method + */ + public function getEditMetadataMethod() { + $ret = FALSE; + if ($this->validate()) { + $method=$this->xml->getElementsByTagName('content_model')->item(0)->getElementsByTagName('edit_metadata_method'); + if ($method->length > 0) { + $method = $method->item(0); + $ret = array('module' => $method->getAttribute('module') == '' ? 'fedora_repository' : $method->getAttribute('module'), + 'file' => $method->getAttribute('file'), + 'class' => $method->getAttribute('class'), + 'method' => $method->getAttribute('method'), + 'handler' => $method->getAttribute('handler'), + 'dsid' => $method->getAttribute('dsid') + ); + } + } + return $ret; + } + + /** + * Removes the edit data method from the Content Model. + * @return boolean $success + */ + public function removeEditMetadataMethod() { + $ret = FALSE; + if ($this->validate()) { + $rootEl=$this->xml->getElementsByTagName('content_model')->item(0); + $method = $rootEl->getElementsByTagName('edit_metadata_method'); + if ($method->length > 0) { + $rootEl->removeChild($method->item(0)); + $ret = TRUE; + } + } + return $ret; + } + + /** + * Update the Edit Metadata Method defined in the Content Model + * @param String $module + * @param String $file + * @param String $class + * @param String $method + * @param String $handler + * @param String $dsid + * @return boolean $success + */ + public function updateEditMetadataMethod($module, $file, $class, $method, $handler, $dsid) { + $ret = FALSE; + if (self::validDsid($dsid) && $this->validate()) { + $methodEl = $this->xml->getElementsByTagName('content_model')->item(0)->getElementsByTagName('edit_metadata_method'); + if ($methodEl->length > 0) { + $methodEl=$methodEl->item(0); + } + else { + $methodEl = $this->xml->createElement('edit_metadata_method'); + $this->xml->getElementsByTagName('content_model')->item(0)->appendChild($methodEl); + } + $methodEl->setAttribute('module', $module); + $methodEl->setAttribute('file', $file); + $methodEl->setAttribute('class', $class); + $methodEl->setAttribute('method', $method); + $methodEl->setAttribute('handler', $handler); + $methodEl->setAttribute('dsid', $dsid); + $ret = TRUE; + } + return $ret; + } + + /** + * Executes the add datastream method for the specified datastream on the specified file. + * Returns FALSE on failure. + * + * @param string $dsid + * @param string $filen + * @return $result + */ + public function execAddDatastreamMethods($dsid, $file) { + $ret = FALSE; + if (self::validDsid($dsid) && $this->validate() && ($ds = $this->getDSModel($dsid)) !== FALSE) { + $addMethod = $ds->getElementsByTagName('add_datastream_method'); + if ($addMethod->length > 0) { + $addMethod=$addMethod->item(0); + $paramArray = array(); + $params= $addMethod->getElementsByTagName('parameters'); + if ($params->length > 0) { + $params=$params->item(0)->getElementsByTagName('parameter'); + for ($i = 0; $i < $params->length; $i++) { + $paramsArray[$params->item($i)->getAttribute('name')] = $params->item($i)->nodeValue; + } + } + $module = $addMethod->getAttribute('module'); + $path = drupal_get_path('module', !empty($module) ? $module : 'fedora_repository') . '/' . $addMethod->getAttribute('file') ; + if (!file_exists($path)) { + self::$errors[] = 'Execute Add Datastream Methods: file \''. $path .'\' does not exist.'; + } + else { + @require_once($path); + $className = $addMethod->getAttribute('class'); + $methodName = $addMethod->getAttribute('method'); + if (!class_exists($className)) { + self::$errors[] = 'Execute Add Datastream Methods: class \''. $className .'\' does not exist.'; + } + else { + $class = new $className; + if (!method_exists($class, $methodName)) { + self::$errors[] = 'Execute Add Datastream Methods: method \''. $className .'->'. $methodName .'\' does not exist.'; + } + else { + $ret = $class->$methodName($paramsArray, $addMethod->getAttribute('dsid'), $file, $addMethod->getAttribute('modified_files_ext')); + } + } + } + } + } + return $ret; + } + + /** + * Executes the ingest rules that apply to the specified file/mimetype. + * Returns FALSE on failure. + * + * If $preview is TRUE, then only execute rules with + * a parameter 'preview'. Used to generate previews for the file chooser. + * + * @param string $file + * @param string $mimetype + * @param boolean $preview + * @return $result + */ + public function execIngestRules($file, $mimetype, $preview = FALSE) { + $ret = FALSE; + if ($this->validate()) { + $ret = TRUE; + $rules = $this->xml->getElementsByTagName('ingest_rules')->item(0)->getElementsByTagName('rule'); + for ($i = 0; $i < $rules->length; $i++) { + $rule=$rules->item($i); + $types = $rule->getElementsbyTagName('applies_to'); + $valid_types = array(); + for ($j = 0; $j < $types->length; $j++) { + $valid_types[]=trim($types->item($j)->nodeValue); + } + + if (in_array($mimetype, $valid_types)) { + $methods = $rule->getElementsByTagName('ingest_methods'); + if ($methods->length > 0) { + $methods = $methods->item(0)->getElementsbyTagName('ingest_method'); + for ($j = 0; $j < $methods->length; $j++) { + $method=$methods->item($j); + $param_array=array(); + $params=$method->getElementsByTagName('parameters'); + $param_array['model_pid'] = $this->pid; + if ($params->length > 0) { + $params = $params->item(0)->getElementsByTagName('parameter'); + for ($k = 0; $k < $params->length; $k++) + $param_array[$params->item($k)->getAttribute('name')]=$params->item($k)->nodeValue; + } + + + if (!$preview || isset($param_array['preview'])) { + $module = $method->getAttribute('module'); + $path = drupal_get_path('module', !empty($module) ? $module : 'fedora_repository') . '/' . $method->getAttribute('file') ; + + if (!file_exists($path) || substr_compare($path, 'fedora_repository/', -strlen('fedora_repository/'), strlen('fedora_repository/')) === 0) { + self::$errors[] = 'Execute Ingest Rules: file \''. $path .'\' does not exist.'; + $ret = FALSE; + } + else { + require_once($path); + $className=$method->getAttribute('class'); + $methodName=($method->getAttribute('method')); + if (!class_exists($className)) { + self::$errors[] = 'Execute Ingest Rules: class \''. $className .'\' does not exist.'; + $ret = FALSE; + } + else $class = new $className; + if (!method_exists($class, $methodName)) { + self::$errors[] = 'Execute Ingest Rules: method \''. $className .'->' . $methodName .'\' does not exist.'; + $ret = FALSE; + } + else { + $status = $class->$methodName($param_array, $method->getAttribute('dsid'), $file, $method->getAttribute('modified_files_ext')); + if ($status !== TRUE) { + $ret = FALSE; + } + } + } + } + } + } + } + } + + } + return $ret; + } + + /** + * Executes the form handler from the element of . + * + * @param &$formData + * @return boolean $success + */ + public function execFormHandler(&$data) { + $ret = FALSE; + if ($this->validate()) { + $method =$this->xml->getElementsByTagName('ingest_form')->item(0)->getElementsByTagName('form_builder_method')->item(0); + $module = $method->getAttribute('module'); + $path=drupal_get_path('module', !empty($module) ? $module : 'fedora_repository') . '/' . $method->getAttribute('file') ; + if (!file_exists($path)) { + self::$errors[] = 'Execute Form Handler: file \''. $path .'\' does not exist.'; + } + else { + require_once($path); + $className=$method->getAttribute('class'); + $methodName=($method->getAttribute('handler')); + if (!class_exists($className)) { + self::$errors[] = 'Execute Form Handler: class \''. $className .'\' does not exist.'; + } + else { + $class = new $className; + if (!method_exists($class, $methodName)) { + self::$errors[] = 'Execute Form Handler: method \''. $className .'->'. $methodName .'\' does not exist.'; + } + else { + $class->$methodName($data); + $ret = TRUE; + } + } + } + } + + return $ret; + } + + /** + * Gets a list of valid mimetypes that can apply to this model. + * Returns FALSE on failure. + * + * @return string[] $mimetypes + */ + public function getMimetypes() { + //only proceed if the xml is valid. + if ($this->validate()) { + if ($this->mimes === NULL) { + $result=$this->xml->getElementsByTagName('mimetypes'); + $result=$result->item(0)->getElementsByTagName('type'); + $mimes = array(); + for ($i = 0; $i < $result->length; $i++) { + $mimes[] = trim($result->item($i)->nodeValue); + } + $this->mimes=$mimes; + } + return $this->mimes; + } + } + + /** + * Calls all defined display methods for the ContentModel. + * The PID specified is passed to the constructor of the display + * class(es) specified in the Content Model. + * + * @param string $pid + * @return string $output + */ + public function displayExtraFieldset($pid) { + $output = ''; + if ($this->validate()) { + $datastreams = $this->xml->getElementsByTagName('datastreams'); + if ($datastreams->length > 0) { + $datastreams = $datastreams->item(0)->getElementsByTagName('datastream'); + for ($i=0; $i < $datastreams->length; $i++) { + $ds = $datastreams->item($i); + if ($ds->attributes->getNamedItem('display_in_fieldset') == NULL || strtolower($ds->getAttribute('display_in_fieldset')) != 'false' ) { + $dispMethods = $ds->getElementsByTagName('display_method'); + for ($j = 0; $j < $dispMethods->length; $j++) { + $method = $dispMethods->item($j); + $module = $method->getAttribute('module'); + $path=drupal_get_path('module', !empty($module) ? $module : 'fedora_repository') . '/' . $method->getAttribute('file') ; + if (!file_exists($path)) { + self::$errors[] = 'Execute Form Handler: file \''. $path .'\' does not exist.'; + } + else { + require_once($path); + $className = $method->getAttribute('class'); + $methodName = ($method->getAttribute('method')); + if (!class_exists($className)) { + self::$errors[] = 'Execute Form Handler: class \''. $className .'\' does not exist.'; + } + else { + $class = new $className($pid); + if (!method_exists($class, $methodName)) { + self::$errors[] = 'Execute Form Handler: method \''. $className .'->'. $methodName .'\' does not exist.'; + } + else { + $output .= $class->$methodName(); + } + } + } + } + } + } + } + } + + return $output; + } + + /** + * Gets a list of datastreams from the ContentModel + * (not including the QDC ds if it is listed). + * Returns FALSE on failure. + * + * @return string[] $datastreams + */ + public function listDatastreams() { + $ds_array=array(); + if ($this->validate()) { + $datastreams = $this->xml->getElementsByTagName('datastreams'); + if ($datastreams->length > 0) { + $datastreams=$datastreams->item(0)->getElementsByTagName('datastream'); + for ($i=0;$i<$datastreams->length;$i++) { + $dsName=$datastreams->item($i)->getAttribute('dsid'); + // if ($dsName != 'QDC') + // { + $ds_array[]=$dsName; + // } + } + } + } + + return $ds_array; + } + + /** + * Adds an allowed mimetype to the model. + * + * @param String $type + * @return boolean $success + */ + public function addMimetype($type) { + $ret=FALSE; + if ($this->validate()) { + $mimetypesEl = $this->xml->getElementsByTagName('mimetypes')->item(0); + $types= $mimetypesEl->getElementsByTagName('type'); + + $found = FALSE; + for ($i=0;!$found && $i<$types->length;$i++) { + if ($types->item($i)->nodeValue == $type) + $found = TRUE; + } + + if (!$found) { + $newTypeEl = $this->xml->createElement('type', $type); + $mimetypesEl->appendChild($newTypeEl); + $ret=TRUE; + } + + } + return $ret; + } + + /** + * Removes an allowed mimetype from the model. + * + * @param String $type + * @return boolean $success + */ + public function removeMimetype($type) { + $ret=FALSE; + if ($this->validate()) { + $mimetypesEl = $this->xml->getElementsByTagName('mimetypes')->item(0); + $types= $mimetypesEl->getElementsByTagName('type'); + $found=FALSE; + for ($i=0;!$found && $i<$types->length;$i++) { + if ($types->item($i)->nodeValue == $type) + $found=$types->item($i); + } + + if ($found !== FALSE && $types->length > 1) { + $mimetypesEl->removeChild($found); + $ret = TRUE; + } + + } + return $ret; + } + + public function getDisplayMethods($ds) { + $ret=FALSE; + if (($ds = $this->getDSModel($ds))!==FALSE) { + $ret=array(); + $dispMethods= $ds->getElementsByTagName('display_method'); + for ($i=0;$i<$dispMethods->length;$i++) { + $ret[] = array( 'module' => $dispMethods->item($i)->getAttribute('module')==''?'fedora_repository':$dispMethods->item($i)->getAttribute('module'), + 'file' => $dispMethods->item($i)->getAttribute('file'), + 'class' => $dispMethods->item($i)->getAttribute('class'), + 'method' => $dispMethods->item($i)->getAttribute('method'), + 'default' => ($dispMethods->item($i)->attributes->getNamedItem('default') !== NULL ? strtolower($dispMethods->item($i)->getAttribute('default')) == 'true' : FALSE)); + } + } + return $ret; + } + + public function addDs($dsid, $display_in_fieldset = FALSE) { + $ret=FALSE; + + if (self::validDsid($dsid) && ($ds = $this->getDSModel($dsid)) === FALSE) { + $datastreamsEl = $this->xml->getElementsByTagName('datastreams'); + if ($datastreamsEl->length > 0) { + $datastreamsEl=$datastreamsEl->item(0); + } + else { + $datastreamsEl = $this->xml->createElement('datastreams'); + $this->xml->getElementsByTagName('content_model')->item(0)->appendChild($datastreamsEl); + } + + $dsEl = $this->xml->createElement('datastream'); + $dsEl->setAttribute('dsid', $dsid); + if ($display_in_fieldset == TRUE) { + $dsEl->setAttribute('display_in_fieldset', 'true'); + } + $datastreamsEl->appendChild($dsEl); + $ret = TRUE; + } + return $ret; + } + + public function removeDs($dsid) { + $ret = FALSE; + + if (self::validDsid($dsid) && ($ds = $this->getDSModel($dsid)) !== FALSE) { + $datastreamsEl = $this->xml->getElementsByTagName('datastreams')->item(0); + $datastreamsEl->removeChild($ds); + $ret = TRUE; + } + return $ret; + } + + public function displayInFieldset($dsid) { + $ret = FALSE; + if (self::validDsid($dsid) && ($ds = $this->getDSModel($dsid)) !== FALSE) { + $ret = strtolower($ds->getAttribute('display_in_fieldset')) == 'true'; + } + return $ret; + } + + public function setDisplayInFieldset($dsid, $value = TRUE) { + $ret = FALSE; + if (self::validDsid($dsid) && ($ds = $this->getDSModel($dsid)) !== FALSE) { + if ($value == FALSE && $ds->attributes->getNamedItem('display_in_fieldset') !== NULL) { + $ds->removeAttribute('display_in_fieldset'); + $ret = TRUE; + } + elseif ($value == TRUE) { + $ds->setAttribute('display_in_fieldset', 'true'); + $ret = TRUE; + } + } + return $ret; + } + + public function setDefaultDispMeth($dsid, $module, $file, $class, $method) { + $ret = FALSE; + + if (self::validDsid($dsid) && ($ds = $this->getDSModel($dsid)) !== FALSE) { + $found = FALSE; + $dispMethods = $ds->getElementsByTagName('display_method'); + for ($i = 0; !$found && $i < $dispMethods->length; $i++) { + if ($module == ($dispMethods->item($i)->getAttribute('module') == '' ? 'fedora_repository' : $dispMethods->item($i)->getAttribute('module')) && + $file == $dispMethods->item($i)->getAttribute('file') && + $class == $dispMethods->item($i)->getAttribute('class') && + $method == $dispMethods->item($i)->getAttribute('method')) { + $found=$dispMethods->item($i); + } + } + + if ($found !== FALSE) { + + for ($i = 0; $i < $dispMethods->length; $i++) { + if ($dispMethods->item($i)->attributes->getNamedItem('default') !== NULL) { + $dispMethods->item($i)->removeAttribute('default'); + } + } + + $found->setAttribute('default', 'true'); + $ret = TRUE; + } + + } + return $ret; + } + + public function removeDispMeth($dsid, $module, $file, $class, $method) { + $ret = FALSE; + if (self::validDsid($dsid) && ($ds = $this->getDSModel($dsid)) !== FALSE) { + $found = FALSE; + $dispMethods= $ds->getElementsByTagName('display_method'); + for ($i=0;!$found && $i<$dispMethods->length;$i++) { + if ($module == ($dispMethods->item($i)->getAttribute('module') == '' ? 'fedora_repository' : $dispMethods->item($i)->getAttribute('module') == '') && + $file == $dispMethods->item($i)->getAttribute('file') && + $class == $dispMethods->item($i)->getAttribute('class') && + $method == $dispMethods->item($i)->getAttribute('method')) { + $found=$dispMethods->item($i); + } + } + + if ($found !== FALSE) { + $ds->removeChild($found); + $ret = TRUE; + } + + } + return $ret; + } + + public function addDispMeth($dsid, $module, $file, $class, $method, $default = FALSE) { + $ret=FALSE; + if (self::validDsid($dsid) && ($ds = $this->getDSModel($dsid))!==FALSE) { + $dispMethEl = $this->xml->createElement('display_method'); + $dispMethEl->setAttribute('module', $module); + $dispMethEl->setAttribute('file', $file); + $dispMethEl->setAttribute('class', $class); + $dispMethEl->setAttribute('method', $method); + if ($default==TRUE) { + $dispMethEl->setAttribute('default', TRUE); + } + $ds->appendChild($dispMethEl); + $ret = TRUE; + } + return $ret; + } + + public function getAddDsMethod($ds) { + $ret=FALSE; + if (($ds = $this->getDSModel($ds))!==FALSE) { + $addDsMethod= $ds->getElementsByTagName('add_datastream_method'); + if ($addDsMethod !== FALSE && $addDsMethod->length > 0) { + $ret = array('module' => $addDsMethod->item(0)->getAttribute('module') == '' ? 'fedora_repository' : $addDsMethod->item(0)->getAttribute('module'), + 'file' => $addDsMethod->item(0)->getAttribute('file'), + 'class' => $addDsMethod->item(0)->getAttribute('class'), + 'method' => $addDsMethod->item(0)->getAttribute('method'), + 'modified_files_ext' => $addDsMethod->item(0)->getAttribute('modified_files_ext'), + 'dsid' => $addDsMethod->item(0)->getAttribute('dsid') + ); + } + } + return $ret; + } + + public function getIngestRule($rule_id) { + $ret = FALSE; + if ($this->validate()) { + $rules = $this->xml->getElementsByTagName('ingest_rules')->item(0)->getElementsByTagName('rule'); + if ($rule_id < $rules->length) + $ret = $rules->item($rule_id); + } + return $ret; + } + + public function removeAppliesTo($rule_id, $type) { + $ret = FALSE; + if (($rule = $this->getIngestRule($rule_id)) !== FALSE) { + $applies = $rule->getElementsByTagName('applies_to'); + $found = FALSE; + for ($i=0; $found === FALSE && $i < $applies->length; $i++) { + if ($type == $applies->item($i)->nodeValue) { + $found = $applies->item($i); + } + } + + if ($found) { + $rule->removeChild($found); + $ret = TRUE; + } + } + return $ret; + } + + public function addAppliesTo($rule_id, $type) { + $ret=FALSE; + if (($rule = $this->getIngestRule($rule_id))!==FALSE) { + $applies = $rule->getElementsByTagName('applies_to'); + $found = FALSE; + for ($i=0;!$found && $i<$applies->length;$i++) { + $found = ($type == $applies->item($i)->nodeValue); + } + + if (!$found) { + $newAppliesTo = $this->xml->createElement('applies_to', $type); + $rule->insertBefore($newAppliesTo, $rule->getElementsByTagName('ingest_methods')->item(0)); + $ret = TRUE; + } + } + return $ret; + } + + public function addIngestMethod($rule_id, $module, $file, $class, $method, $dsid, $modified_files_ext) { + $ret=FALSE; + if (self::validDsid($dsid) && ($rule=$this->getIngestRule($rule_id))!==FALSE) { + $methodsEl = $rule->getElementsByTagName('ingest_methods')->item(0); + $meth = $this->xml->createElement('ingest_method'); + $meth->setAttribute('module', $module); + $meth->setAttribute('file', $file); + $meth->setAttribute('class', $class); + $meth->setAttribute('method', $method); + $meth->setAttribute('dsid', $dsid); + $meth->setAttribute('modified_files_ext', $modified_files_ext); + + $methodsEl->appendChild($meth); + $ret = TRUE; + } + return $ret; + } + + public function removeIngestMethod($rule_id, $module, $file, $class, $method) { + $ret=FALSE; + if (($rule=$this->getIngestRule($rule_id))!==FALSE) { + $found=FALSE; + $methodsEl = $rule->getElementsByTagName('ingest_methods')->item(0); + $methods = $methodsEl->getElementsByTagName('ingest_method'); + for ($i=0;!$found && $i<$methods->length;$i++) { + if ($methods->item($i)->getAttribute('module') == $module && $methods->item($i)->getAttribute('file') == $file && $methods->item($i)->getAttribute('class') == $class && $methods->item($i)->getAttribute('method') == $method) { + $found = $methods->item($i); + } + } + + if ($found !== FALSE && $methods->length > 1) { + $methodsEl->removeChild($found); + $ret = TRUE; + } + } + return $ret; + } + + public function addIngestMethodParam($rule_id, $module, $file, $class, $method, $name, $value) { + $ret = FALSE; + if (($rule=$this->getIngestRule($rule_id)) !== FALSE) { + $methods = $rule->getElementsByTagName('ingest_methods')->item(0)->getElementsByTagName('ingest_method'); + $found = FALSE; + for ($i = 0; $found === FALSE && $i < $methods->length; $i++) { + if (($methods->item($i)->getAttribute('module') == '' ? 'fedora_repository' : $methods->item($i)->getAttribute('module')) == $module && + $methods->item($i)->getAttribute('file') == $file && + $methods->item($i)->getAttribute('class') == $class && + $methods->item($i)->getAttribute('method') == $method) { + $found=$methods->item($i); + } + } + + if ($found !== FALSE) { + $paramsEl = $found->getElementsByTagName('parameters'); + if ($paramsEl->length == 0) { + $paramsEl=$found->appendChild($this->xml->createElement('parameters')); + } + else { + $paramsEl=$paramsEl->item(0); + } + + $params = $paramsEl->getElementsByTagName('parameter'); + $found = FALSE; + for ($i=0; $found === FALSE && $i < $params->length; $i++) { + if ($params->item($i)->getAttribute('name') == $name) { + $found = $params->item($i); + } + } + + if ($found === FALSE) { + $param = $this->xml->createElement('parameter', $value); + $param->setAttribute('name', $name); + $paramsEl->appendChild($param); + $ret = TRUE; + } + } + } + return $ret; + } + + public function removeIngestMethodParam($rule_id, $module, $file, $class, $method, $name) { + $ret = FALSE; + if (($rule=$this->getIngestRule($rule_id)) !== FALSE) { + $found = FALSE; + $methodsEl = $rule->getElementsByTagName('ingest_methods')->item(0); + $methods = $methodsEl->getElementsByTagName('ingest_method'); + for ($i=0; !$found && $i < $methods->length; $i++) { + if ($methods->item($i)->getAttribute('module') == $module && $methods->item($i)->getAttribute('file') == $file && $methods->item($i)->getAttribute('class') == $class && $methods->item($i)->getAttribute('method') == $method) { + $found = $methods->item($i); + } + } + + if ($found !== FALSE) { + $methodEl = $found; + $paramsEl = $found->getElementsByTagName('parameters'); + if ($paramsEl->length > 0) { + $paramsEl=$paramsEl->item(0); + $params = $paramsEl->getElementsByTagName('parameter'); + $found = FALSE; + for ($i=0; $found === FALSE && $i < $params->length; $i++) { + if ($params->item($i)->getAttribute('name') == $name) { + $found=$params->item($i); + } + } + + if ($found !== FALSE) { + $paramsEl->removeChild($found); + if ($params->length == 0) { + $methodEl->removeChild($paramsEl); + } + + $ret = TRUE; + } + } + + } + } + return $ret; + } + + public function removeIngestRule($rule_id) { + $ret = FALSE; + if (($rule = $this->getIngestRule($rule_id))!==FALSE) { + $ret = $this->xml->getElementsByTagName('ingest_rules')->item(0)->removeChild($rule); + } + return $ret; + } + + public function addIngestRule($applies_to, $module, $file, $class, $method, $dsid, $modified_files_ext) { + $ret = FALSE; + if (self::validDsid($dsid) && $this->validate()) { + $ingestRulesEl = $this->xml->getElementsByTagName('ingest_rules')->item(0); + $rule = $this->xml->createElement('rule'); + $ingestMethodsEl = $this->xml->createElement('ingest_methods'); + $rule->appendChild($ingestMethodsEl); + $ingestRulesEl->appendChild($rule); + $newRuleId = $ingestRulesEl->getElementsByTagName('rule')->length - 1; + $ret = ($this->addAppliesTo($newRuleId, $applies_to) && $this->addIngestMethod($newRuleId, $module, $file, $class, $method, $dsid, $modified_files_ext)); + } + return $ret; + } + + public function getIngestRules() { + $ret = FALSE; + if ($this->validate()) { + $ret = array(); + $rules = $this->xml->getElementsByTagName('ingest_rules')->item(0)->getElementsByTagName('rule'); + for ($i=0; $i < $rules->length; $i++) { + $rule = array('applies_to' => array(), + 'ingest_methods' => array()); + $applies_to = $rules->item($i)->getElementsByTagName('applies_to'); + for ($j=0; $j < $applies_to->length; $j++) { + $rule['applies_to'][] = trim($applies_to->item($j)->nodeValue); + } + + $methods = $rules->item($i)->getElementsByTagName('ingest_methods')->item(0)->getElementsByTagName('ingest_method'); + for ($j=0; $j < $methods->length; $j++) { + $method = array('module' => $methods->item($j)->getAttribute('module') == '' ? 'fedora_repository' : $methods->item($j)->getAttribute('module'), + 'file' => $methods->item($j)->getAttribute('file'), + 'class' => $methods->item($j)->getAttribute('class'), + 'method' => $methods->item($j)->getAttribute('method'), + 'dsid' => $methods->item($j)->getAttribute('dsid'), + 'modified_files_ext' => $methods->item($j)->getAttribute('modified_files_ext'), + 'parameters' => array()); + + $params = $methods->item($j)->getElementsByTagName('parameters'); + if ($params->length > 0) { + $params=$params->item(0)->getElementsByTagName('parameter'); + for ($k=0; $k < $params->length; $k++) { + $method['parameters'][$params->item($k)->getAttribute('name')] = $params->item($k)->nodeValue; + } + } + + $rule['ingest_methods'][] = $method; + + } + + $ret[] = $rule; + } + } + return $ret; + } + + public function getIngestFormAttributes() { + $ret = FALSE; + if ($this->validate()) { + $ingest_formEl = $this->xml->getElementsByTagName('ingest_form')->item(0); + $ret=array('dsid' => $ingest_formEl->getAttribute('dsid'), + 'page' => $ingest_formEl->getAttribute('page'), + 'hide_file_chooser' => strtolower($ingest_formEl->getAttribute('hide_file_chooser')) == 'true', + 'redirect' => strtolower($ingest_formEl->getAttribute('redirect')) == 'false' ? FALSE : TRUE); + + } + return $ret; + } + + public function editIngestFormAttributes($dsid, $page, $hide_file_chooser = FALSE, $redirect = TRUE) { + $ret = FALSE; + if (self::validDsid($dsid) && $this->validate()) { + $ingest_formEl = $this->xml->getElementsByTagName('ingest_form')->item(0); + $ingest_formEl->setAttribute('dsid', $dsid); + $ingest_formEl->setAttribute('page', $page); + if (!$redirect) { + $ingest_formEl->setAttribute('redirect', 'false'); + } + else { + $ingest_formEl->removeAttribute('redirect'); + } + if ($hide_file_chooser) { + $ingest_formEl->setAttribute('hide_file_chooser', 'true'); + } + else { + $ingest_formEl->removeAttribute('hide_file_chooser'); + } + $ret = TRUE; + } + return $ret; + } + + public function getIngestFormBuilderMethod() { + $ret = FALSE; + if ($this->validate()) { + $method = $this->xml->getElementsByTagName('ingest_form')->item(0)->getElementsByTagName('form_builder_method')->item(0); + $ret = array( 'module' => ($method->getAttribute('module')==''?'fedora_repository':$method->getAttribute('module')), + 'file' => $method->getAttribute('file'), + 'class' => $method->getAttribute('class'), + 'method' => $method->getAttribute('method'), + 'handler' => $method->getAttribute('handler')); + } + return $ret; + } + + public function editIngestFormBuilderMethod($module, $file, $class, $method, $handler) { + $ret = FALSE; + if ($this->validate()) { + $methodEl = $this->xml->getElementsByTagName('ingest_form')->item(0)->getElementsByTagName('form_builder_method')->item(0); + $methodEl->setAttribute('module', $module); + $methodEl->setAttribute('file', $file); + $methodEl->setAttribute('class', $class); + $methodEl->setAttribute('method', $method); + $methodEl->setAttribute('handler', $handler); + $ret = TRUE; + } + return $ret; + } +} diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. 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 +them 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 prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. 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. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 3 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program 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, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU 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. But first, please read +. diff --git a/MimeClass.inc b/MimeClass.inc new file mode 100644 index 00000000..55228740 --- /dev/null +++ b/MimeClass.inc @@ -0,0 +1,336 @@ +mime). + * While I don't have a specific use case for a mime->extension lookup, I think + * it's good to have in here. + * + * Drupal 7 will have better mime handlers. See: + * http://api.drupal.org/api/function/file_default_mimetype_mapping/7 + * + */ + +class MimeClass { + private $private_mime_types = array( + /** + * This is a shortlist of mimetypes which should catch most + * mimetype<-->extension lookups in the context of Islandora collections. + * + * It has been cut from a much longer list. + * + * Two types of mimetypes should be put in this list: + * 1) Special emerging formats which may not yet be expressed in the system + * mime.types file. + * 2) Heavily used mimetypes of particular importance to the Islandora + * project, as lookups against this list will be quicker and less + * resource intensive than other methods. + * + * Lookups are first checked against this short list. If no results are found, + * then the lookup function may move on to check other sources, namely the + * system's mime.types file. + * + * In most cases though, this short list should suffice. + * + * If modifying this list, please note that for promiscuous mimetypes + * (those which map to multiple extensions, such as text/plain) + * The function get_extension will always return the *LAST* extension in this list, + * so you should put your preferred extension *LAST*. + * + * e.g... + * "jpeg" => "image/jpeg", + * "jpe" => "image/jpeg", + * "jpg" => "image/jpeg", + * + * $this->get_extension('image/jpeg') will always return 'jpg'. + * + */ + // openoffice: + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + // staroffice: + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + // k-office: + 'kil' => 'application/x-killustrator', + 'kpt' => 'application/x-kpresenter', + 'kpr' => 'application/x-kpresenter', + 'ksp' => 'application/x-kspread', + 'kwt' => 'application/x-kword', + 'kwd' => 'application/x-kword', + // ms office 97: + 'doc' => 'application/msword', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + // office2007: + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'docm' => 'application/vnd.ms-word.document.macroEnabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroEnabled.12', + // wordperfect (who cares?): + 'wpd' => 'application/wordperfect', + // common and generic containers: + 'pdf' => 'application/pdf', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + 'rtf' => 'text/rtf', + 'rtx' => 'text/richtext', + 'latex' => 'application/x-latex', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + // *ml: + 'css' => 'text/css', + 'htm' => 'text/html', + 'html' => 'text/html', + 'wbxml' => 'application/vnd.wap.wbxml', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xsl' => 'text/xml', + 'xml' => 'text/xml', + 'csv' => 'text/csv', + 'tsv' => 'text/tab-separated-values', + 'txt' => 'text/plain', + // images: + "bmp" => "image/bmp", + "gif" => "image/gif", + "ief" => "image/ief", + "jpeg" => "image/jpeg", + "jpe" => "image/jpeg", + "jpg" => "image/jpeg", + "jp2" => "image/jp2", + "png" => "image/png", + "tiff" => "image/tiff", + "tif" => "image/tif", + "djvu" => "image/vnd.djvu", + "djv" => "image/vnd.djvu", + "wbmp" => "image/vnd.wap.wbmp", + "ras" => "image/x-cmu-raster", + "pnm" => "image/x-portable-anymap", + "pbm" => "image/x-portable-bitmap", + "pgm" => "image/x-portable-graymap", + "ppm" => "image/x-portable-pixmap", + "rgb" => "image/x-rgb", + "xbm" => "image/x-xbitmap", + "xpm" => "image/x-xpixmap", + "xwd" => "image/x-windowdump", + // videos: + "mpeg" => "video/mpeg", + "mpe" => "video/mpeg", + "mpg" => "video/mpeg", + "qt" => "video/quicktime", + "mov" => "video/quicktime", + "mxu" => "video/vnd.mpegurl", + "avi" => "video/x-msvideo", + "movie" => "video/x-sgi-movie", + "flv" => "video/x-flv", + "swf" => "application/x-shockwave-flash", + // compressed formats: (note: http://svn.cleancode.org/svn/email/trunk/mime.types) + "tgz" => "application/x-gzip", + "gz" => "application/x-gzip", + "tar" => "application/x-tar", + "gtar" => "application/x-gtar", + "zip" => "application/x-zip", + // others: + 'bin' => 'application/octet-stream', + ); + + private $private_file_extensions; + private $system_types; + private $system_exts; + private $etc_mime_types = '/etc/mime.types'; + + public function __construct() { + + // populate the reverse shortlist: + $this->private_file_extensions = array_flip( $this->private_mime_types ); + + // pick up a local mime.types file if it is available + if (is_readable('mime.types') ) { + $this->etc_mime_types = 'mime.types'; + } + + } + + /** + * function: getType + * description: An alias to get_mimetype, + * for backwards-compatibility with our old mimetype class. + */ + public function getType( $filename ) { + return $this->get_mimetype( $filename ); + } + + /** + * function: get_mimetype + * description: returns a mimetype associated with the file extension of $filename + */ + public function get_mimetype( $filename, $debug = FALSE ) { + + $ext = strtolower( array_pop( explode( '.', $filename ) ) ); + + if ( ! empty( $this->private_mime_types[$ext] ) ) { + if ( TRUE === $debug ) + return array( 'mime_type' => $this->private_mime_types[$ext], 'method' => 'from_array' ); + return $this->private_mime_types[$ext]; + } + + if (function_exists('file_get_mimetype')) { + $drupal_mimetype = file_get_mimetype( $filename ); + if ('application/octet-stream' != $drupal_mimetype) { + if (TRUE == $debug) + return array('mime_type' => $drupal_mimetype, 'method' => 'file_get_mimetype'); + return $drupal_mimetype; + } + } + + if (!isset( $this->system_types)) + $this->system_types = $this->system_extension_mime_types(); + if (isset( $this->system_types[$ext])) { + if (TRUE == $debug) + return array('mime_type' => $this->system_types[$ext], 'method' => 'mime.types'); + return $this->system_types[$ext]; + } + + if ( TRUE === $debug ) + return array( 'mime_type' => 'application/octet-stream', 'method' => 'last_resort' ); + return 'application/octet-stream'; + + } + + /** + * function: get_extension + * description: returns *one* valid file extension for a given $mime_type + */ + public function get_extension( $mime_type, $debug = FALSE ) { + + if (!empty( $this->private_file_extensions[$mime_type])) { + if (TRUE == $debug) + return array( 'extension' => $this->private_file_extensions[$mime_type], 'method' => 'from_array' ); + return $this->private_file_extensions[$mime_type]; + } + + if (!isset ( $this->system_exts)) + $this->system_exts = $this->system_mime_type_extensions(); + if (isset( $this->system_exts[$mime_type])) { + if (TRUE == $debug) + return array( 'extension' => $this->system_exts[$mime_type], 'method' => 'mime.types' ); + return $this->system_exts[$mime_type]; + } + + if (TRUE == $debug) + return array( 'extension' => 'bin', 'method' => 'last_resort' ); + return 'bin'; + } + + /** + * function: system_mime_type_extensions + * description: populates an internal array of mimetype/extension associations + * from the system mime.types file, or a local mime.types if one is found (see + * __constuctor). + * return: array of mimetype => extension + */ + private function system_mime_type_extensions() { + $out = array(); + $file = fopen($this->etc_mime_types, 'r'); + while (($line = fgets($file)) !== FALSE) { + $line = trim(preg_replace('/#.*/', '', $line)); + if (!$line) + continue; + $parts = preg_split('/\s+/', $line); + if (count($parts) == 1) + continue; + // A single part means a mimetype without extensions, which we ignore. + $type = array_shift($parts); + if (!isset($out[$type])) + $out[$type] = array_shift($parts); + // We take the first ext from the line if many are present. + } + fclose($file); + return $out; + } + + /** + * function: system_mime_type_extensions + * description: populates an internal array of mimetype/extension associations + * from the system mime.types file, or a local mime.types if one is found (see + * __constuctor). + * return: array of extension => mimetype + */ + private function system_extension_mime_types() { + $out = array(); + $file = fopen($this->etc_mime_types, 'r'); + while (($line = fgets($file)) !== FALSE) { + $line = trim(preg_replace('/#.*/', '', $line)); + if (!$line) + continue; + $parts = preg_split('/\s+/', $line); + if (count($parts) == 1) + continue; + // A single part means a mimetype without extensions, which we ignore. + $type = array_shift($parts); + foreach ($parts as $part) + $out[$part] = $type; + } + fclose($file); + return $out; + } + +} + +/* +$helper = new MimeClass(); +print "get_extension('application/x-gzip'): \n"; +echo print_r( $helper->get_extension('application/x-gzip', true), true ) . "\n"; +print "get_extension('text/plain'): \n"; +echo print_r( $helper->get_extension('text/plain', true), true ) . "\n"; +print "get_mimetype('fucker/asdf.tar.gz'): \n"; +echo print_r( $helper->get_mimetype('fucker/asdf.tar.gz', true), true ) . "\n"; +print "get_mimetype('blah.tsp'): \n"; +echo print_r( $helper->get_mimetype('blah.tsp', true), true ) . "\n"; +/* */ diff --git a/ObjectHelper.inc b/ObjectHelper.inc new file mode 100644 index 00000000..d96fd83a --- /dev/null +++ b/ObjectHelper.inc @@ -0,0 +1,1025 @@ +fedoraUser = $connectionHelper->getUser(); + //$this->fedoraPass = $connectionHelper->getPassword(); + } + + /** + * Grabs a stream from fedora sets the mimetype and returns it. $dsID is the + * datastream id. + * @param $pid String + * @param $dsID String + */ + function makeObject($pid, $dsID, $asAttachment = FALSE, $label = NULL, $filePath=FALSE, $version=NULL) { + global $user; + module_load_include('inc','fedora_repository','ContentModel'); + if ($pid == NULL || $dsID == NULL) { + drupal_set_message(t("no pid or dsid given to create an object with"), 'error'); + return ' '; + } + $headers = module_invoke_all('file_download', "/fedora/repository/$pid/$dsID"); + if (in_array(-1, $headers)) { + drupal_set_message('hello'); + drupal_access_denied(); + + return ' '; + } + + + if (!fedora_repository_access(OBJECTHELPER :: $OBJECT_HELPER_VIEW_FEDORA, $pid, $user)) { + drupal_set_message(t("You do not have access Fedora objects within the attempted namespace."), 'error'); + drupal_access_denied(); + return ' '; + } + + if (($cm = ContentModel::loadFromObject($pid)) == FALSE) { + drupal_set_message(t("You do not have access to objects without an Islandora Content Model."), 'error'); + drupal_access_denied(); + return ' '; + } + + $cmDatastreams = $cm->listDatastreams(); + if (!in_array($dsID,$cmDatastreams)) { + drupal_set_message(t("You do not have access to the specified datastream."), 'error'); + drupal_access_denied(); + return ' '; + } + + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + $item = new Fedora_Item($pid); + + + if (isset($item->datastreams[$dsID])) { + $mimeType=$item->datastreams[$dsID]['MIMEType']; + if ($label == NULL) { + $label = $item->datastreams[$dsID]['label']; + } + } else { + drupal_not_found(); + exit(); + } + + + if ((!isset($user)) || $user->uid == 0) { + $fedoraUser = 'anonymous'; + $fedoraPass = 'anonymous'; + $contentSize= 0; + } else { + $fedoraUser = $user->name; + $fedoraPass = $user->pass; + $dataStreamInfo = $item->get_datastream_info($dsID); + $contentSize = $dataStreamInfo->datastream->size; + } + + if (function_exists("curl_init")) { + if (!isset($mimeType)) { + $pid = variable_get('fedora_default_display_pid', 'demo:10'); + $dsID = variable_get('fedora_default_display_dsid', 'TN'); + $mimeType = 'image/jpeg'; + } + + $url = variable_get('fedora_base_url', 'http://localhost:8080/fedora') . '/get/' . $pid . '/' . $dsID; + if ($version) { + $url .= '/' . $version; //drupal_urlencode($version); + } + $ch = curl_init(); + $user_agent = "Mozilla/4.0 pp(compatible; MSIE 5.01; Windows NT 5.0)"; + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); + curl_setopt($ch, CURLOPT_FAILONERROR, 1); // Fail on errors + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // allow redirects + //curl_setopt($ch, CURLOPT_TIMEOUT, 15); // times out after 15s + curl_setopt($ch, CURLOPT_USERAGENT, $user_agent); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_USERPWD, "$fedoraUser:$fedoraPass"); + // There seems to be a bug in Fedora 3.1's REST authentication, removing this line fixes the authorization denied error. + // curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 0); // return into a variable + + curl_setopt($ch, CURLOPT_URL, $url); + + if ($filePath !== FALSE) { + $fp = fopen($filePath, 'w'); + curl_setopt($ch, CURLOPT_FILE, $fp); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_exec($ch); + fclose($fp); + } else { + + header("Content-type: $mimeType"); + if ($contentSize > 0) { + header("Content-length: $contentSize"); + } + + if ($asAttachment) { + $suggestedFileName = "$label"; + $pos = strpos($suggestedFileName, '.'); + /** + * Here we used to take an object of, say, type application/pdf with label, say, "My Document" + * and we assemble the output filename extension based on the post-slash portion of the mimetype. + * (If the label has a period anywhere in it, we leave it alone.) + * + * This is great for simple mimetypes like application/pdf, text/html, image/jpeg, etc. + * but it's terrible for, say, application/vnd.oasis.opendocument.text (.odt). + * + * Instead we'll use the get_extension function in MimeClass.inc to discover a valid extension + * for the mimetype in question. + */ + if ($pos === FALSE) { + module_load_include('inc', 'fedora_repository', 'MimeClass'); + $mimeclass = new MimeClass(); + $ext = $mimeclass->get_extension($mimeType); + $suggestedFileName = "$label.$ext"; + } + + header('Content-Disposition: attachment; filename="' . $suggestedFileName . '"'); + } + curl_exec($ch); + } + curl_close($ch); + } else { + drupal_set_message(t('No curl support.'), 'error'); + } + } + + //Gets collection objects t + function getCollectionInfo($pid, $query = NULL) { + module_load_include('inc', 'fedora_repository', 'CollectionClass'); + $collectionClass = new CollectionClass(); + $results = $collectionClass->getRelatedItems($pid, $query); + return $results; + } + + /** + * returns the mime type + */ + function getMimeType($pid, $dsID) { + global $user; + if (empty($pid) || empty($dsID)) { + drupal_set_message(t('You must specify an object pid and datastream ID.'), 'error'); + return ''; + } + if (!fedora_repository_access(ObjectHelper :: $OBJECT_HELPER_VIEW_FEDORA, $pid, $user)) { + drupal_set_message(t('You do not have the appropriate permissions'), 'error'); + return; + } + + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + $item = new fedora_item($pid); + $datastream_list = $item->get_datastreams_list_as_SimpleXML(); + if (!isset($datastream_list)) { + //drupal_set_message( t("No datastreams available."), 'status' ); + return ' '; + } + foreach ($datastream_list as $datastream) { + foreach ($datastream as $datastreamValue) { + if ($datastreamValue->ID == $dsID) { + return $datastreamValue->MIMEType; + } + } + } + return ''; + } + + function getDatastreamInfo($pid, $dsID) { + global $user; + if (empty($pid) || empty($dsID)) { + drupal_set_message(t('You must specify an object pid and datastream ID.'), 'error'); + return ''; + } + if (!fedora_repository_access(ObjectHelper :: $OBJECT_HELPER_VIEW_FEDORA, $pid, $user)) { + drupal_set_message(t('You do not have the appropriate permissions'), 'error'); + return; + } + + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + $item = new fedora_item($pid); + $datastream_list = $item->get_datastreams_list_as_SimpleXML(); + if (!isset($datastream_list)) { + //drupal_set_message( t("No datastreams available."), 'status' ); + return ' '; + } + foreach ($datastream_list as $datastream) { + foreach ($datastream as $datastreamValue) { + if ($datastreamValue->ID == $dsID) { + return $datastreamValue; + } + } + } + return ''; + } + + /** + * internal function + * @param $pid String + * @param $dataStreamValue Object + */ + function create_link_for_ds($pid, $dataStreamValue) { + global $base_url; + $path = drupal_get_path('module', 'fedora_repository'); + + require_once($path . '/api/fedora_item.inc'); + $item = new Fedora_Item($pid); + + if (user_access(ObjectHelper :: $PURGE_FEDORA_OBJECTSANDSTREAMS)) { + $allow=TRUE; + if (module_exists('fedora_fesl')) { + $allow= fedora_fesl_check_roles($pid,'write'); + } + if ($allow) { + $purgeImage = 'purge datastream'; + } + } else { + $purgeImage = ' '; + } + $fullPath = base_path() . $path; + + // Add an icon to replace a datastream + // @TODO Note: using l(theme_image(..), ...); for these image links (and other links) may remove the need to have clean urls enabled. + $replaceImage= ' '; + if (user_access(ObjectHelper :: $ADD_FEDORA_STREAMS)) { + $allow=TRUE; + if (module_exists('fedora_fesl')) { + $allow= fedora_fesl_check_roles($pid,'write'); + } + if ($allow) { + $replaceImage = 'label . '" href="' . $base_url . '/fedora/repository/replaceStream/' . $pid . '/' . $dataStreamValue->ID . '/' . $dataStreamValue->label . '">replace datastream'; + } + } + + $content = ''; + $id = $dataStreamValue->ID; + $label = $dataStreamValue->label; + $label = str_replace("_", " ", $label); + $mimeType = $dataStreamValue->MIMEType; + + $view = '' . t('View') . ''; + $action = "$base_url/fedora/repository/object_download/" . drupal_urlencode($pid) . '/' . $id . '/' . drupal_urlencode(preg_replace('/\//i', '${1}_', $label)); // Necessary to handle the case of Datastream labels that contain slashes. Ugh. + $downloadVersion = '
'; + if (user_access(ObjectHelper :: $EDIT_FEDORA_METADATA)) { + $versions = $item->get_datastream_history($id); + if (is_array($versions)) { + $downloadVersion = '
'; + $downloadVersion .= '
'; + } + } + + + $content .= "$label $view $downloadVersion $mimeType $replaceImage $purgeImage\n"; + //$content .= "Mime Type :$mimeType\n"; + return $content; + } + + /** + * Queries fedora for what we call the qualified dublin core. Currently only dc.coverage has + * any qualified fields + * Transforms the returned xml to html + * This is the default metadata view. With icons for searching a dublin core field + * @param $pid String + * @return String + */ + function getQDC($pid) { + global $base_url; + $path = drupal_get_path('module', 'fedora_repository'); + module_load_include('inc', 'fedora_repository', 'ConnectionHelper'); + + $soapHelper = new ConnectionHelper(); + $client = $soapHelper->getSoapClient(variable_get('fedora_soap_url', 'http://localhost:8080/fedora/services/access?wsdl')); + + $dsId = 'QDC'; + $params = array( + 'pid' => "$pid", + 'dsID' => "$dsId", + 'asOfDateTime' => "" + ); + try { + $object = $client->__soapCAll('getDatastreamDissemination', array( + 'parameters' => $params + )); + } catch (Exception $e) { + try { //probably no QDC so we will try for the DC stream. + $dsId = 'DC'; + $params = array( + 'pid' => "$pid", + 'dsID' => "$dsId", + 'asOfDateTime' => "" + ); + $object = $client->__soapCAll('getDatastreamDissemination', array( + 'parameters' => $params + )); + } catch (exception $e2) { + drupal_set_message($e2->getMessage(), 'error'); + return; + } + } + $xmlstr = $object->dissemination->stream; + try { + $proc = new XsltProcessor(); + } catch (Exception $e) { + drupal_set_message($e->getMessage(), 'error'); + return; + } + + $proc->setParameter('', 'baseUrl', $base_url); + $proc->setParameter('', 'path', $base_url . '/' . $path); + $input = NULL; + $xsl = new DomDocument(); + try { + $xsl->load($path . '/xsl/convertQDC.xsl'); + $input = new DomDocument(); + $input->loadXML(trim($xmlstr)); + } catch (exception $e) { + watchdog(t("Fedora_Repository"), t("Problem loading XSL file: !e", array('!e' => $e)), NULL, WATCHDOG_ERROR); + } + $xsl = $proc->importStylesheet($xsl); + $newdom = $proc->transformToDoc($input); + $output = $newdom->saveXML(); + $baseUrl = base_path(); + //$baseUrl=substr($baseUrl, 0, (strpos($baseUrl, "/")-1)); + if (user_access(ObjectHelper :: $EDIT_FEDORA_METADATA)) { + $allow=TRUE; + if (module_exists('fedora_fesl')) { + $allow= fedora_fesl_check_roles($pid,'write'); + } + if ($allow) { + $output .= '
' . t('Edit Meta Data') . ''; + } + } + return $output; + } + + /** + * Gets a list of datastreams from an object using its pid + * + * We make some assumptions here. We have implemented a policy that + * we ingest in our repository will have TN (thumbnail) datastream. Even audio + * will have a picture of a speaker or something. This is not critical + * but makes searches etc. look better if there is a TN stream. + * This diplays all the streams in a collapsed fieldset at the bottom of the object page. + * you can implement a content model if you would like certain streams displayed in certain ways. + * @param $object_pid String + * @return String + * + */ + function get_formatted_datastream_list($object_pid, $contentModels, &$fedoraItem) { + global $fedoraUser, $fedoraPass, $base_url; + module_load_include('inc', 'fedora_repository', 'ConnectionHelper'); + module_load_include('inc', 'fedora_repository', 'ObjectHelper'); + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + + $path = drupal_get_path('module', 'fedora_repository'); + $dataStreamBody = ''; + $fedoraItem = new Fedora_Item($object_pid); + + if (user_access(ObjectHelper :: $VIEW_DETAILED_CONTENT_LIST)) { + $availableDataStreamsText = 'Detailed List of Content'; + //$metaDataText='Description'; + $mainStreamLabel = NULL; + $object = $fedoraItem->get_datastreams_list_as_SimpleXML(); + if (!isset($object)) { + drupal_set_message(t("No datastreams available")); + return ' '; + } + $hasOBJStream = NULL; + $hasTNStream = FALSE; + $dataStreamBody = "
\n"; + + $dataStreamBody .= $this->get_parent_objects_asHTML($object_pid); + $dataStreamBody .= ''; + foreach ($object as $datastream) { + foreach ($datastream as $datastreamValue) { + if ($datastreamValue->ID == 'OBJ') { + $hasOBJStream = '1'; + $mainStreamLabel = $datastreamValue->label; + $mainStreamLabel = str_replace("_", " ", $mainStreamLabel); + } + if ($datastreamValue->ID == 'TN') { + $hasTNStream = TRUE; + } + //create the links to each datastream + $dataStreamBody .= $this->create_link_for_ds($object_pid, $datastreamValue); //"\n"; + } + } + $dataStreamBody .= "

' . t("!text", array('!text' => $availableDataStreamsText)) . '

$key :$value
\n"; + //if they have access let them add a datastream + if (user_access(ObjectHelper :: $ADD_FEDORA_STREAMS)) { + $allow=TRUE; + if (module_exists('fedora_fesl')) { + $allow= fedora_fesl_check_roles($object_pid,'write'); + } + if ($allow) { + $dataStreamBody .= drupal_get_form('add_stream_form', $object_pid); + } + } + $fieldset = array( + '#title' => t("!text", array('!text' => $availableDataStreamsText)), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#value' => $dataStreamBody + ); + $dataStreamBody = '
' . theme('fieldset', $fieldset) . '
'; + } + $content = ''; + + if (empty($contentModels)) { + //only show this stuff if there are no contentmodels + if (isset($hasOBJStream)) { + $content .= ''; + $content .= 'Thumbnail'; + $content .= ''; + } else { + //don't use thumbnail as link, we don't know which datastream to link to + $content .= '' . $object_pid . ''; + } + } + + foreach ($contentModels as $contentModel) { + $content .= $this->createExtraFieldsets($object_pid, $contentModel); + } + + $content .= $dataStreamBody; + + if (user_access(ObjectHelper :: $PURGE_FEDORA_OBJECTSANDSTREAMS)) { + $allow=TRUE; + if (module_exists('fedora_fesl')) { + $allow= fedora_fesl_check_roles($object_pid,'write'); + } + if ($allow) { + $purgeObject = '' . t('Purge Object') . '' . t('Purge Object') . ''; + } + } else { + $purgeObject = ' '; + } + $content .= $purgeObject; + + $addObject = " "; + if (user_access(ObjectHelper :: $OBJECT_HELPER_VIEW_FEDORA)) { + $addObject = theme('add_to_basket_link', $object_pid); + } + $content .= $addObject; + + return '
' . $content . '
'; + } + + /** + * Queries a collection object for an xslt to format how the + * collection of objects is displayed. + */ + function getXslContent($pid, $path, $canUseDefault = TRUE) { + module_load_include('inc', 'fedora_repository', 'CollectionClass'); + $collectionClass = new CollectionClass(); + $xslContent = $collectionClass->getCollectionViewStream($pid); + if (!$xslContent && $canUseDefault) { //no xslt so we will use the default sent with the module + $xslContent = file_get_contents($path . '/xsl/sparql_to_html.xsl'); + } + return $xslContent; + } + + /** + * returns a stream from a fedora object given a pid and dsid + * + */ + function getStream($pid, $dsid, $showError = FALSE) { + module_load_include('inc', 'fedora_repository', 'ConnectionHelper'); + $soapHelper = new ConnectionHelper(); + try { + $client = $soapHelper->getSoapClient(variable_get('fedora_soap_url', 'http://localhost:8080/fedora/services/access?wsdl')); + $params = array( + 'pid' => "$pid", + 'dsID' => "$dsid", + 'asOfDateTime' => "" + ); + + if (!isset($client)) { + drupal_set_message(t('Error connection to Fedora using soap client.')); + return NULL; + } + $object = $client->__soapCall('getDatastreamDissemination', array('parameters' => $params)); + } catch (Exception $e) { + if ($showError) { + drupal_set_message(t('Error getting Datastream for %pid and %datastream
', array('%pid' => $pid, '%datastream' => $dsid)), 'error'); + } + return NULL; + } + $content = $object->dissemination->stream; + $content = trim($content); + return $content; + } + + /* + * gets the name of the content models for the specified object + * this now returns an array of pids as in Fedora 3 we can have more then one Cmodel for an object + */ + + function get_content_models_list($pid, $include_fedora_system_content_models = FALSE) { + module_load_include('inc', 'fedora_repository', 'CollectionClass'); + $collectionHelper = new CollectionClass(); + $pids = array(); + $query = 'select $object from <#ri> + where $object + and $object '; + $content_models = $collectionHelper->getRelatedItems($pid, $query); + + if (empty($content_models)) { + return $pids; + } + + try { + $sxml = new SimpleXMLElement($content_models); + } catch (exception $e) { + watchdog(t("Fedora_Repository"), t("Could not find a parent object for %s", $pid), NULL, WATCHDOG_ERROR); + return $pids; + } + + if (!isset($sxml)) { + return $pids; + } + + foreach ($sxml->xpath('//@uri') as $uri) { + if (strpos($uri, 'fedora-system') != FALSE && $include_fedora_system_content_models == FALSE) { + continue; + } + $pids[] = substr(strstr($uri, '/'), 1); + } + + return $pids; + } + + /* + * determines whether we can see the object or not + */ + + function fedora_repository_access($op, $pid) { + global $user; + $returnValue = FALSE; + if ($pid == NULL) { + $pid = variable_get('fedora_repository_pid', 'islandora:top'); + } + $nameSpaceAllowed = explode(" ", variable_get('fedora_pids_allowed', 'default: demo: changeme: Islandora: ilives: ')); + $pos = NULL; + foreach ($nameSpaceAllowed as $nameSpace) { + $pos = stripos($pid, $nameSpace); + if ($pos === 0) { + $returnValue = TRUE; + } + } + if ($returnValue) { + $user_access = user_access($op); + if ($user_access == NULL) { + return FALSE; + } + return $user_access; + } else { + return FALSE; + } + } + + /** + * internal function + * uses an xsl to parse the sparql xml returned from the ITQL query + * + * + * @param $content String + */ + function parseContent($content, $pid, $dsId, $collection, $pageNumber = NULL) { + $path = drupal_get_path('module', 'fedora_repository'); + global $base_url; + $collection_pid = $pid; //we will be changing the pid later maybe + //module_load_include('php', ''Fedora_Repository'', 'ObjectHelper'); + $objectHelper = $this; + $parsedContent = NULL; + $contentModels = $objectHelper->get_content_models_list($pid); + $isCollection = FALSE; + //if this is a collection object store the $pid in the session as it will come in handy + //after a purge or ingest to return to the correct collection. + + $fedoraItem = NULL; + $datastreams = $this->get_formatted_datastream_list($pid, $contentModels, $fedoraItem); + + if (!empty($contentModels)) { + foreach ($contentModels as $contentModel) { + if ($contentModel == variable_get('fedora_collection_model_pid', 'islandora:collectionCModel')) { + $_SESSION['fedora_collection'] = $pid; + $isCollection = TRUE; + } + } + } + + if ($fedoraItem !== NULL) { + $dslist = $fedoraItem->get_datastreams_list_as_array(); + if (isset($dslist['COLLECTION_POLICY'])) { + $isCollection = TRUE; + } + } + + //$label=$content; + $collectionName = $collection; + if (!$pageNumber) { + $pageNumber = 1; + } + + if (!isset($collectionName)) { + $collectionName = variable_get('fedora_repository_name', 'Collection'); + } + $xslContent = $this->getXslContent($pid, $path); + + //get collection list and display using xslt------------------------------------------- + + if (isset($content) && $content != FALSE) { + $input = new DomDocument(); + $input->loadXML(trim($content)); + $results = $input->getElementsByTagName('result'); + if ($results->length > 0) { + try { + $proc = new XsltProcessor(); + $proc->setParameter('', 'collectionPid', $collection_pid); + $proc->setParameter('', 'collectionTitle', $collectionName); + $proc->setParameter('', 'baseUrl', $base_url); + $proc->setParameter('', 'path', $base_url . '/' . $path); + $proc->setParameter('', 'hitPage', $pageNumber); + $proc->registerPHPFunctions(); + $xsl = new DomDocument(); + $xsl->loadXML($xslContent); + // php xsl does not seem to work with namespaces so removing it below + // I may have just been being stupid here + // $content = str_ireplace('xmlns="http://www.w3.org/2001/sw/DataAccess/rf1/result"', '', $content); + + $xsl = $proc->importStylesheet($xsl); + $newdom = $proc->transformToDoc($input); + + $objectList = $newdom->saveXML(); //is the xml transformed to html as defined in the xslt associated with the collection object + + if (!$objectList) { + throw new Exception("Invalid XML."); + } + } catch (Exception $e) { + drupal_set_message(t('!e', array('!e' => $e->getMessage())), 'error'); + return ''; + } + } + } else { + drupal_set_message(t("No Objects in this collection or bad query.")); + } + + //-------------------------------------------------------------------------------- + //show the collections datastreams + if ($results->length > 0 || $isCollection == TRUE) { + // if(strlen($objectList)>22||$contentModel=='Collection'||$contentModel=='Community')//length of empty dom still equals 22 because of etc + module_load_include('inc', 'Fedora_Repository', 'CollectionPolicy'); + $collectionPolicyExists = $objectHelper->getMimeType($pid, CollectionPolicy::getDefaultDSID()); + if (user_access(ObjectHelper :: $INGEST_FEDORA_OBJECTS) && $collectionPolicyExists) { + if (!empty($collectionPolicyExists)) { + $allow=TRUE; + if (module_exists('fedora_fesl')) { + $allow= fedora_fesl_check_roles($pid,'write'); + } + if ($allow) { + // $ingestObject = ' $collectionName, '!collection_pid' => $collection_pid)) . '" href="' . base_path() . + 'fedora/ingestObject/' . $collection_pid . '/' . $collectionName . '">' . t('Add a New Object') . '' . t('Add to this Collection') . ''; + } + } + } + else { + $ingestObject = ' '; + } + + + $datastreams .= $ingestObject; + + $objectListOut = ''; + if (isset($objectList)) { + $object_list_fieldset = array( + '#title' => t('Items in this collection'), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#value' => (isset($objectList) ? $objectList : ''), //collection list + ); + $objectListOut = theme('fieldset', $object_list_fieldset); + } + } else { + //$collectionName=''; + $collection_fieldset = array( + '#title' => "", + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#value' => $datastreams, + ); + $objectListOut = ''; //no collection objects to show so don't show field set + } + + $output = ''; + switch (variable_get('fedora_object_display_title', ObjectHelper::$DISPLAY_ALWAYS)) { + case ObjectHelper :: $DISPLAY_NEVER: break; + case ObjectHelper :: $DISPLAY_NO_MODEL_OUTPUT: + if (trim($datastreams) == '') { + $output .= '' . $collectionName . '
'; + } + break; + + case ObjectHelper :: $DISPLAY_ALWAYS: + default: + $output .= '' . $collectionName . '
'; + break; + } + + $output .= $datastreams; + + $showDesc = FALSE; + switch (variable_get('fedora_object_display_description', ObjectHelper :: $DISPLAY_NO_MODEL_OUTPUT)) { + case ObjectHelper :: $DISPLAY_NEVER: break; + case ObjectHelper :: $DISPLAY_NO_MODEL_OUTPUT: + if (trim($datastreams) == '') { + $showDesc = TRUE; + } + break; + + case ObjectHelper :: $DISPLAY_ALWAYS: + default: + $showDesc = TRUE; + break; + } + if ($showDesc) { + //just show default dc or qdc as we could not find a content model + $metaDataText = t('Description'); + $body = $this->getQDC($pid); + $fieldset = array( + '#title' => t("!metaDataText", array('!metaDataText' => $metaDataText)), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#value' => $body + ); + $output .= theme('fieldset', $fieldset); + } + + switch (variable_get('fedora_collection_display_list', ObjectHelper::$DISPLAY_ALWAYS)) { + case ObjectHelper :: $DISPLAY_NEVER: break; + case ObjectHelper :: $DISPLAY_NO_MODEL_OUTPUT: + if (trim($datastreams) == '') { + $output .= $objectListOut . '
'; + } + break; + + case ObjectHelper :: $DISPLAY_ALWAYS: + default: + $output .= $objectListOut . '
'; + break; + } + + return $output; + } + + /** + * Gets the parent objects that this object is related to + * + * @param unknown_type $pid + * @return unknown + */ + function get_parent_objects($pid) { + $query_string = 'select $object $title from <#ri> + where ($object $title + and $object + and $object ) + order by $title'; + $objects = $this->getCollectionInfo($pid, $query_string); + return $objects; + } + + function get_parent_objects_asHTML($pid) { + global $base_url; + $parent_collections = $this->get_parent_objects($pid); + try { + $parent_collections = new SimpleXMLElement($parent_collections); + } catch (exception $e) { + drupal_set_message(t('Error getting parent objects !e', array('!e' => $e->getMessage()))); + return; + } + + $parent_collections_HTML = ''; + foreach ($parent_collections->results->result as $result) { + $collection_label = $result->title; + foreach ($result->object->attributes() as $a => $b) { + if ($a == 'uri') { + $uri = (string) $b; + $uri = $base_url . '/fedora/repository' . substr($uri, strpos($uri, '/')) . '/-/' . $collection_label; + } + } + $parent_collections_HTML .= '' . $collection_label . '
'; + } + if (!empty($parent_collections_HTML)) { + $parent_collections_HTML = '
'; + } + + return $parent_collections_HTML; + } + + /** + * gets a list of datastreams and related function that we should use to show datastreams in their own fieldsets + * from the content model associated with the object + */ + function createExtraFieldsets($pid, $contentModel) { + //$models = $collectionHelper->getContentModels($collectionPid, FALSE); + // possible problem in below if the collection policy has multiple content models + //with different pids but same dsid we could get wrong one, low probability and functionality + // will probably change for fedora version 3. + if (empty($contentModel)) { + return NULL; + } + $output = ''; + module_load_include('inc', 'fedora_repository', 'ContentModel'); + if (($cm = ContentModel :: loadFromModel($contentModel)) !== FALSE && $cm->validate()) { + $output .= $cm->displayExtraFieldset($pid); + } + return $output; + } + + /** + * Look in the content model for rules to run on the specified datastream. + * + * @param string $pid + * @param string $dsid + * @return boolean + */ + function get_and_do_datastream_rules($pid, $dsid, $file = '') { + if (!user_access('ingest new fedora objects')) { + drupal_set_message(t('You do not have permission to add datastreams.')); + return FALSE; + } + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + if ($dsid != NULL && $pid != NULL && ($cm = ContentModel::loadFromObject($pid)) !== FALSE) { + $cm->execAddDatastreamMethods($dsid, $file); + } + } + + /** + * Get a tree of related pids - for the basket functionality + */ + function get_all_related_pids($pid) { + if (!$pid) { + return FALSE; + } + module_load_include('inc', 'fedora_repository', 'api/fedora_utils'); + + // Get title and descriptions for $pid + $query_string = 'select $title $desc from <#ri> + where $o $title + and $o $desc + and $o '; + + $url = variable_get('fedora_repository_url', 'http://localhost:8080/fedora/risearch'); + $url .= "?type=tuples&flush=true&format=csv&limit=1000&lang=itql&stream=on&query="; + $content = do_curl($url . htmlentities(urlencode($query_string))); + + $rows = explode("\n", $content); + $fields = explode(',', $rows[1]); + + $pids[$pid] = array('title' => $fields[0], 'description' => $fields[1]); + +// $pids += $this->get_child_pids(array($pid)); + + return $pids; + } + + /** + * Get children of PID - but only 2 levels deep + */ + function get_child_pids($pids) { + // Get pid, title and description for children of object $pid + $query_string = 'select $o $title from <#ri> ' . +// $query_string = 'select $o $title $desc from <#ri> '. + 'where $s $o ' . + 'and $o $title ' . +// 'and $o $desc '. + 'and ( '; + + foreach ($pids as $pid) { + $query_string .= '$s or '; + } + $query_string = substr($query_string, 0, -3) . ' )'; + + $url = variable_get('fedora_repository_url', 'http://localhost:8080/fedora/risearch'); + $url .= "?type=tuples&flush=true&format=csv&limit=1000&lang=itql&stream=on&query="; + $url .= htmlentities(urlencode($query_string)); + $content = $this->doCurl($url); + + $rows = explode("\n", $content); + // Knock of the first heading row + array_shift($rows); + + $child_pids = array(); + if (count($rows)) { + // iterate through each row + foreach ($rows as $row) { + if ($row == "") { + continue; + } + $fields = explode(',', $row); + $child_pid = substr($fields[0], 12); + $child_pids[$child_pid] = array('title' => $fields[1], 'description' => $fields[2]); + } + if (!empty($child_pids)) { + $child_pids += $this->get_child_pids(array_keys($child_pids)); + } + } + + return $child_pids; + } + + /** + * Returns XML description of the object (export). + */ + function getObject($pid, $context = 'archive', $format = FOXML_11) { + module_load_include('inc', 'fedora_repository', 'api/fedora_utils'); + + $url = variable_get('fedora_base_url', 'http://localhost:8080/fedora') . '/objects/' . $pid . '/export?context=' . $context . '&format=' . $format; + $result_data = do_curl($url); + return $result_data; + } + + /** + * Builds an array of drupal links for use in breadcrumbs. + */ + function getBreadcrumbs($pid, &$breadcrumbs, $level=10) { + module_load_include('inc', 'fedora_repository', 'api/fedora_utils'); + + $query_string = 'select $parentObject $title $content from <#ri> + where ( $title + and $parentObject $content + and ( $parentObject + or $parentObject) + and $parentObject ) + minus $content + order by $title'; + $query_string = htmlentities(urlencode($query_string)); + + $url = variable_get('fedora_repository_url', 'http://localhost:8080/fedora/risearch'); + $url .= "?type=tuples&flush=TRUE&format=CSV&limit=1&offset=0&lang=itql&stream=on&query=" . $query_string; + $result = preg_split('/[\r\n]+/',do_curl($url)); + array_shift($result); // throw away first line + $matches =str_getcsv(join("\n",$result)); + if ($matches !== NULL) { + $parent = preg_replace('/^info:fedora\//','',$matches[0]); + $breadcrumbs[] = l($matches[1], 'fedora/repository/' . $pid); + if ($parent == variable_get('fedora_repository_pid', 'islandora:top')) { + $breadcrumbs[] = l(t('Home'), ''); // l(t('Digital repository'), 'fedora/repository'); + } elseif ($level > 0) { + $this->getBreadcrumbs($parent, $breadcrumbs, $level - 1); + } + } + } + + function warnIfMisconfigured($app) { + $messMap = array( + 'Kakadu' => 'Full installation instructions for Kakadu can be found + Here', + 'ImageMagick' => 'Check the path settings in the configuration of your imageapi module.
+ Further details can be found Here', + ); + + $warnMess = "Creation of one or more datastreams failed.
"; + $configMess = "Please ensure that %app is installed and configured for this site. "; + drupal_set_message($warnMess, 'warning', false); + drupal_set_message(t($configMess . "
" . $messMap[$app] . "
", array('%app' => $app)), 'warning', false); + } + +} + diff --git a/SearchClass.inc b/SearchClass.inc new file mode 100644 index 00000000..21fa114e --- /dev/null +++ b/SearchClass.inc @@ -0,0 +1,601 @@ + $e->getMessage())), NULL, WATCHDOG_ERROR); + return 'Error getting solr search results class. Check watchdog for more info.'; + } + return $implementation->$solrFunction($query, $startPage, $fq, $dismax); + } + function build_solr_search_form($repeat = NULL, $pathToSearchTerms = NULL, $query = NULL) { + $types = $this->get_search_terms_array(NULL, 'solrSearchTerms.xml'); + $queryArray=NULL; + if (isset($query)) { + $queryArray = explode('AND', $query); + } + + $andOrArray = array( + 'AND' => 'and', + //'OR' => 'or' //removed or for now as it would be a pain to parse + ); + $form = array(); + + if (!isset($repeat)) { + $repeat = variable_get('islandora_solr_search_block_repeat', t('3')); + } + $var0 = explode(':', $queryArray[0]); + $var1 = explode(':', $queryArray[1]); + $form['search_type']['type1'] = array( + '#title' => t(''), + '#type' => 'select', + '#options' => $types, + '#default_value' => trim($var0[0]) + ); + $form['fedora_terms1'] = array( + '#size' => '24', + '#type' => 'textfield', + '#title' => t(''), + '#required' => TRUE, + '#default_value' => (count($var0) >= 2 ? trim($var0[1]) : ''), + ); + $form['andor1'] = array( + '#title' => t(''), + '#type' => 'select', + '#default_value' => 'AND', + '#options' => $andOrArray + ); + $form['search_type']['type2'] = array( + '#title' => t(''), + '#type' => 'select', + '#options' => $types, + '#default_value' => (count($var1) >= 2 ? trim($var1[0]) : ''), + ); + $form['fedora_terms2'] = array( + '#size' => '24', + '#type' => 'textfield', + '#title' => t(''), + '#default_value' => (count($var1) >= 2 ? $var1[1] : ''), + ); + if ($repeat>2 && $repeat < 9) { //don't want less then 2 or more then 9 + for ($i = 3; $i < $repeat + 1; $i++) { + $t = $i - 1; + $field_and_term = explode(':', $queryArray[$t]); + $form["andor$t"] = array( + '#title' => t(''), + '#type' => 'select', + '#default_value' => 'AND', + '#options' => $andOrArray + ); + $form['search_type']["type$i"] = array( + '#title' => t(''), + '#type' => 'select', + '#options' => $types, + '#default_value' => trim($field_and_term[0]) + ); + $form["fedora_terms$i"] = array( + '#size' => '24', + '#type' => 'textfield', + '#title' => t(''), + '#default_value' => (count($field_and_term) >= 2 ? trim($field_and_term[1]) : ''), + ); + } + } + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('search') + ); + return $form; + } + + function build_simple_solr_form() { + //$form = array(); + $form["search_query"] = array( + '#size' => '30', + '#type' => 'textfield', + '#title' => t(''), + // '#default_value' => (count($field_and_term) >= 2 ? trim($field_and_term[1]) : ''), + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('search') + ); + return $form; + } + function theme_solr_search_form($form) { + if (!isset($repeat)) { + $repeat = variable_get('islandora_solr_search_block_repeat', t('3')); + } + + $output = drupal_render($form['search_type']['type1']) ; + $output .= drupal_render($form['fedora_terms1']) ; + $output .= drupal_render($form['andor1']) . drupal_render($form['search_type']['type2']) ; + $output .= drupal_render($form['fedora_terms2']); + if ($repeat>2 && $repeat < 9) { + for ($i=3;$i<$repeat+1;$i++) { + $t = $i - 1; + $output .= drupal_render($form["andor$t"]) . drupal_render($form['search_type']["type$i"]) ; + $output .= drupal_render($form["fedora_terms$i"]) ; + } + } + $output .= drupal_render($form['submit']) ; + $output .= drupal_render($form); + return $output; + + + } + function quickSearch($type, $query, $showForm = 1, $orderBy = 0, & $userArray) { + module_load_include('inc', 'fedora_repository', 'ObjectHelper'); + module_load_include('inc', 'fedora_repository', 'api/fedora_utils'); + if (user_access('view fedora collection')) { + $numberOfHistPerPage = '5000'; //hack for IR they do not want next button + $luceneQuery = NULL; + // Demo search string ?operation=gfindObjects&indexName=DemoOnLucene&query=fgs.DS.first.text%3Achristmas&hitPageStart=11&hitPageSize=10 + $keywords = explode(' ', $query); + + foreach ($keywords as $keyword) { + $luceneQuery .= $type . ':'. $keyword . '+AND+'; + } + $luceneQuery = substr($luceneQuery, 0, strlen($luceneQuery) - 5); + + $indexName = variable_get('fedora_index_name', 'DemoOnLucene'); + $keys = htmlentities(urlencode($query)); + $searchUrl = variable_get('fedora_fgsearch_url', 'http://localhost:8080/fedoragsearch/rest'); + $searchString = '?operation=gfindObjects&indexName='. $indexName . '&restXslt=copyXml&query='. $luceneQuery; + $searchString .= '&hitPageSize='. $numberOfHistPerPage . '&hitPageStart=1'; + //$searchString = htmlentities($searchString); + $searchUrl .= $searchString; + + // $objectHelper = new ObjectHelper(); + + $resultData = do_curl($searchUrl, 1); + if (isset($userArray)) { + $doc = new DOMDocument(); + $doc->loadXML($resultData); + $xPath = new DOMXPath($doc); + // Add users to department list. This is a hack as not all users will be in dupal + $nodeList = $xPath->query('//field[@name="refworks.u1"]'); + foreach ($nodeList as $node) { + if (!in_array($node->nodeValue, $userArray)) { + $userArray[]=$node->nodeValue; + } + } + } + if ($showForm) { + $output = 'Quick Search

' . t("Belongs to these collections: ") . '

' . $parent_collections_HTML . '
'. drupal_get_form('fedora_repository_quick_search_form') . '
'; + } + $output .= $this->applyXSLT($resultData, $orderBy); + return $output; + } + } + + + // gets term from a lucene index and displays them in a list + function getTerms($fieldName, $startTerm, $displayName = NULL) { + module_load_include('inc', 'fedora_repository', 'ObjectHelper'); + module_load_include('inc', 'fedora_repository', 'api/fedora_utils'); + $indexName = variable_get('fedora_index_name', 'DemoOnLucene'); + $searchUrl = variable_get('fedora_fgsearch_url', 'http://localhost:8080/fedoragsearch/rest'); + if ($startTerm == NULL) { + $startTerm = ""; + } + $startTerm = drupal_urlencode($startTerm); + $query = 'operation=browseIndex&startTerm='. $startTerm . '&fieldName='. $fieldName . '&termPageSize=20&indexName='. $indexName . '&restXslt=copyXml&resultPageXslt=copyXml'; + // $query=drupal_urlencode($query); + $query = '?'. $query; + $searchString=$searchUrl . $query; + + $objectHelper = new ObjectHelper(); + + $resultData = do_curl($searchString, 1); + $path = drupal_get_path('module', 'Fedora_Repository'); + + $output .= $this->applySpecifiedXSLT($resultData, $path . '/xsl/browseIndexToResultPage.xslt', $displayName); + //$output .= '
'.$alpha_out; + return $output; + + } + + /* + function custom_search($query,$pathToXslt=NULL){ + module_load_include('php', 'Fedora_Repository', 'ObjectHelper'); + module_load_include('inc', 'Fedora_Repository', 'api/fedora_utils'); + if (user_access('view fedora collection')) { + $numberOfHistPerPage = '1000';//hack for IR they do not want next button + $luceneQuery = NULL; + //demo search string ?operation=gfindObjects&indexName=DemoOnLucene&query=fgs.DS.first.text%3Achristmas&hitPageStart=11&hitPageSize=10 + + + $indexName = variable_get('fedora_index_name', 'DemoOnLucene'); + $query=htmlentities(urlencode($query)); + + $searchUrl = variable_get('fedora_fgsearch_url', 'http://localhost:8080/fedoragsearch/rest'); + $searchString = '?operation=gfindObjects&indexName=' . $indexName . '&restXslt=copyXml&query=' . $query; + $searchString .= '&hitPageSize='.$numberOfHistPerPage.'&hitPageStart=1'; + //$searchString = htmlentities($searchString); + $searchUrl .= $searchString; + + $objectHelper = new ObjectHelper(); + + $resultData = do_curl($searchUrl,1); + //var_dump($resultData);exit(0); + // $doc = new DOMDocument(); + // $doc->loadXML($resultData); + if($pathToXslt==NULL) { + $output.=$this->applyLuceneXSLT($resultData,$query); + }else{ + $output.=$this->applySpecifiedXSLT($resultData,$pathToXslt); + } + + return $output; + + } + } + */ + + function custom_search($query, $startPage=1, $xslt= '/xsl/advanced_search_results.xsl', $numberOfHistPerPage = 50) { + module_load_include('inc', 'fedora_repository', 'ObjectHelper'); + module_load_include('inc', 'fedora_repository', 'api/fedora_utils'); + + if (user_access('view fedora collection')) { + //$numberOfHistPerPage = '50';//hack for IR they do not want next button + $luceneQuery = NULL; + $indexName = variable_get('fedora_index_name', 'DemoOnLucene'); + $copyXMLFile = 'copyXml'; + // if($indexName=='ilives' || $indexName=='BasicIndex'){ + // $copyXMLFile = 'copyXmliLives'; + // } + $query = trim($query); + $query = htmlentities(urlencode($query)); + $searchUrl = variable_get('fedora_fgsearch_url', 'http://localhost:8080/fedoragsearch/rest'); + $searchString = '?operation=gfindObjects&indexName=' . $indexName . '&restXslt='. $copyXMLFile . '&query=' . $query; + $searchString .= '&hitPageSize='. $numberOfHistPerPage . '&hitPageStart='. $startPage; + //$searchString = htmlentities($searchString); + $searchUrl .= $searchString; + + //$objectHelper = new ObjectHelper(); + + $resultData = do_curl($searchUrl, 1); + //var_dump($resultData);exit(0); + // $doc = new DOMDocument(); + // $doc->loadXML($resultData); + + $output .= $this->applyLuceneXSLT($resultData, $startPage, $xslt, $query); + return $output; + } + } + + + function applySpecifiedXSLT($resultData, $pathToXSLT, $displayName = NULL) { + $proc = NULL; + global $user; + if (!$resultData) { + drupal_set_message(t('No data found!')); + return ' '; //no results + } + try { + $proc = new XsltProcessor(); + } + catch (Exception $e) { + drupal_set_message(t('Error loading '. $pathToXSLT . ' xslt!') . $e->getMessage()); + return ' '; + } + + //$proc->setParameter('', 'searchUrl', url('search') . '/fedora_repository'); //needed in our xsl + $proc->setParameter('', 'objectsPage', base_path()); + $proc->setParameter('', 'userID', $user->uid); + if (isset($displayName)) { + $proc->setParameter('', 'displayName', $displayName); + } + else { + $proc->setParameter('', 'displayName', "test"); + } + + $xsl = new DomDocument(); + + $test= $xsl->load($pathToXSLT); + + if (!isset($test)) { + drupal_set_message(t('Error loading '. $pathToXSLT . ' xslt!')); + return t('Error loading !pathToXSLT xslt.', array('!pathToXSLT' => $pathToXSLT)); + } + + $input = new DomDocument(); + + $didLoadOk = $input->loadXML($resultData); + + if (!isset($didLoadOk)) { + drupal_set_message(t('Error loading XML data!')); + return t('Error loading XML data.'); + } + else { + $proc->importStylesheet($xsl); + $newdom = $proc->transformToDoc($input); + return $newdom->saveXML(); + } + } + //default function for lucene results + /* + function applyLuceneXSLT($resultData, $query = NULL){ + $path = drupal_get_path('module', 'Fedora_Repository'); + $proc = NULL; + if (!$resultData) { + drupal_set_message(t('No Results!')); + return ' '; //no results + } + try { + $proc = new XsltProcessor(); + } + catch (Exception $e) { + drupal_set_message(t('Error loading results xslt! ') . $e->getMessage()) ; + return ' '; + } + + //inject into xsl stylesheet + if(isset($query)){ + $proc->setParameter('', 'fullQuery', $query); + } + $proc->setParameter('', 'searchToken', drupal_get_token('fedora_repository_advanced_search')); //token generated by Drupal, keeps tack of what tab etc we are on + $proc->setParameter('', 'searchUrl', url('search') . '/fedora_repository'); //needed in our xsl + $proc->setParameter('', 'objectsPage', base_path()); + $proc->setParameter('', 'allowedPidNameSpaces', variable_get('fedora_pids_allowed', 'demo: changeme:')); + $proc->registerPHPFunctions(); + $xsl = new DomDocument(); + + $test= $xsl->load($path . '/xsl/results.xsl'); + if (!isset($test)) { + drupal_set_message(t('Error loading search results XSLT!')); + return t('Error loading search results XSLT.'); + } + + $input = new DomDocument(); + $didLoadOk = $input->loadXML($resultData); + + if (!isset($didLoadOk)) { + drupal_set_message(t('Error loading search results!')); + return t('Error loading search results.'); + } + else { + $proc->importStylesheet($xsl); + $newdom = $proc->transformToDoc($input); + return $newdom->saveXML(); + } + } + */ + + /** + * apply an xslt to lucene gsearch search results + * + * @param $resultData + * @param $startPage + * @param $xslt_file + * @param $query the query that was executed. May want to pass this on. + */ + function applyLuceneXSLT($resultData, $startPage = 1, $xslt_file = '/xsl/results.xsl', $query=NULL) { + $path = drupal_get_path('module', 'Fedora_Repository'); + $proc = NULL; + if (!$resultData) { + //drupal_set_message(t('No Results!')); + return ' '; //no results + } + try { + $proc = new XsltProcessor(); + } catch (Exception $e) { + drupal_set_message(t('Error loading results xslt!') . $e->getMessage()); + return ' '; + } + if (isset($query)) { + $proc->setParameter('', 'fullQuery', $query); + } + //inject into xsl stylesheet + global $user; + $proc->setParameter('', 'userID', $user->uid); + $proc->setParameter('', 'searchToken', drupal_get_token('fedora_repository_advanced_search')); //token generated by Drupal, keeps tack of what tab etc we are on + $proc->setParameter('', 'searchUrl', url('search') . '/fedora_repository'); //needed in our xsl + $proc->setParameter('', 'objectsPage', base_path()); + $proc->setParameter('', 'allowedPidNameSpaces', variable_get('fedora_pids_allowed', 'default: demo: changeme: Islandora: ilives: ')); + $proc->setParameter('', 'hitPageStart', $startPage); + $proc->registerPHPFunctions(); + $xsl = new DomDocument(); + + $test = $xsl->load($path . $xslt_file); + if (!isset($test)) { + drupal_set_message(t('Error loading search results xslt!')); + return t('Error loading search results XSLT.'); + } + + $input = new DomDocument(); + $didLoadOk = $input->loadXML($resultData); + + if (!isset($didLoadOk)) { + drupal_set_message(t('Error loading search results!')); + return t('Error loading search results.'); + } + else { + $proc->importStylesheet($xsl); + $newdom = $proc->transformToDoc($input); + return $newdom->saveXML(); + } + } + + //xslt for islandscholar these xslt functions can probably be pulled into one + function applyXSLT($resultData, $orderBy = 0) { + $path = drupal_get_path('module', 'Fedora_Repository'); + $proc = NULL; + if (!$resultData) { + //drupal_set_message(t('No Results!')); + return ' '; //no results + } + try { + $proc = new XsltProcessor(); + } catch (Exception $e) { + drupal_set_message(t('Error loading results xslt! ') . $e->getMessage()); + return ' '; + } + + //inject into xsl stylesheet + //$proc->setParameter('', 'searchToken', drupal_get_token('search_form')); //token generated by Drupal, keeps tack of what tab etc we are on + $proc->setParameter('', 'userID', $user->uid); + $proc->setParameter('', 'searchUrl', url('search') . '/fedora_repository'); //needed in our xsl + $proc->setParameter('', 'objectsPage', base_path()); + $proc->setParameter('', 'allowedPidNameSpaces', variable_get('fedora_pids_allowed', 'default: demo: changeme: Islandora: ilives: ')); + $proc->setParameter('', 'orderBy', $orderBy); + $xsl = new DomDocument(); + + $test=$xsl->load($path . '/ir/xsl/results.xsl'); + if (!isset($test)) { + drupal_set_message(t('Error loading search results xslt!')); + return t('Error loading search results XSLT.'); + } + + $input = new DomDocument(); + $didLoadOk = $input->loadXML($resultData); + + if (!isset($didLoadOk)) { + drupal_set_message(t('Error loading search results!')); + return t('Error loading search results.'); + } + else { + $xsl = $proc->importStylesheet($xsl); + $newdom = $proc->transformToDoc($input); + return $newdom->saveXML(); + } + } + + function theme_advanced_search_form($form, $repeat=NULL) { + if (!isset($repeat)) { + $repeat = variable_get('fedora_repository_advanced_block_repeat', t('3')); + } + + $output = drupal_render($form['search_type']['type1']) ; + $output .= drupal_render($form['fedora_terms1']) ; + $output .= drupal_render($form['andor1']) . drupal_render($form['search_type']['type2']) ; + $output .= drupal_render($form['fedora_terms2']); + if ($repeat>2 && $repeat < 9) { + for ($i=3;$i<$repeat+1;$i++) { + $t = $i - 1; + $output .= drupal_render($form["andor$t"]) . drupal_render($form['search_type']["type$i"]) ; + $output .= drupal_render($form["fedora_terms$i"]) ; + } + } + $output .= drupal_render($form['submit']) ; + $output .= drupal_render($form); + return $output; + } + + //build search form, custom blocks should set the number of repeats or it will use the default + function build_advanced_search_form($repeat = NULL, $pathToSearchTerms = NULL, $query = NULL) { + $types = $this->get_search_terms_array($pathToSearchTerms); + $queryArray=NULL; + if (isset($query)) { + $queryArray = explode('AND', $query); + } + + $andOrArray = array( + 'AND' => 'and', + //'OR' => 'or' //removed or for now as it would be a pain to parse + ); + $form = array(); + + if (!isset($repeat)) { + $repeat = variable_get('fedora_repository_advanced_block_repeat', t('3')); + } + $var0 = explode(':', $queryArray[0]); + $var1 = explode(':', $queryArray[1]); + $form['search_type']['type1'] = array( + '#title' => t(''), + '#type' => 'select', + '#options' => $types, + '#default_value' => trim($var0[0]) + ); + $form['fedora_terms1'] = array( + '#size' => '24', + '#type' => 'textfield', + '#title' => t(''), + '#required' => TRUE, + '#default_value' => (count($var0) >= 2 ? trim($var0[1]) : ''), + ); + $form['andor1'] = array( + '#title' => t(''), + '#type' => 'select', + '#default_value' => 'AND', + '#options' => $andOrArray + ); + $form['search_type']['type2'] = array( + '#title' => t(''), + '#type' => 'select', + '#options' => $types, + '#default_value' => (count($var1) >= 2 ? trim($var1[0]) : ''), + ); + $form['fedora_terms2'] = array( + '#size' => '24', + '#type' => 'textfield', + '#title' => t(''), + '#default_value' => (count($var1) >= 2 ? $var1[1] : ''), + ); + if ($repeat>2 && $repeat < 9) { //don't want less then 2 or more then 9 + for ($i = 3; $i < $repeat + 1; $i++) { + $t = $i - 1; + $field_and_term = explode(':', $queryArray[$t]); + $form["andor$t"] = array( + '#title' => t(''), + '#type' => 'select', + '#default_value' => 'AND', + '#options' => $andOrArray + ); + $form['search_type']["type$i"] = array( + '#title' => t(''), + '#type' => 'select', + '#options' => $types, + '#default_value' => trim($field_and_term[0]) + ); + $form["fedora_terms$i"] = array( + '#size' => '24', + '#type' => 'textfield', + '#title' => t(''), + '#default_value' => (count($field_and_term) >= 2 ? trim($field_and_term[1]) : ''), + ); + } + } + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('search') + ); + return $form; + } + + + function get_search_terms_array($path = NULL, $file = NULL) { + if (!isset($path)) { + $path = drupal_get_path('module', 'Fedora_Repository'); + } + $xmlDoc = new DomDocument(); + if (!isset($file)) { + $file = 'searchTerms.xml'; + } + $xmlDoc->load($path . '/'. $file); + $nodeList = $xmlDoc->getElementsByTagName('term'); + $types = array(); + for ($i = 0; $i < $nodeList->length; $i++) { + $field = $nodeList->item($i)->getElementsByTagName('field'); + $value = $nodeList->item($i)->getElementsByTagName('value'); + $fieldValue = $field->item(0)->nodeValue; + $valueValue = $value->item(0)->nodeValue; + $types["$fieldValue"] = "$valueValue"; + } + return $types; + } +} diff --git a/SecurityClass.inc b/SecurityClass.inc new file mode 100644 index 00000000..ded59cf3 --- /dev/null +++ b/SecurityClass.inc @@ -0,0 +1,203 @@ +getStream($collection_pid, SECURITYCLASS :: $SECURITY_CLASS_SECURITY_STREAM, FALSE); + + if ($policyStream == NULL) { + // no child policy stream so collection is wide open to anyone to ingest, that has the permission ingest in Drupal. + // maybe we should return FALSE here?? would be more secure. + return TRUE; + } + $allowedUsersAndRoles = $this->getAllowedUsersAndRoles($policyStream); + if (!$allowedUsersAndRoles) { + // error processing stream so don't let them ingest here. + return FALSE; + } + $allowedUsers = $allowedUsersAndRoles["users"]; + $allowedRoles = $allowedUsersAndRoles["roles"]; + + foreach ($user->roles as $role) { + if (in_array($role, $allowedRoles)) { + return TRUE; + } + } + + if (in_array($user->name, $allowedUsers)) { + return TRUE; + } + return FALSE; + } + + //parses our simple xacml policies checking for users or roles that are allowed to ingest + function getAllowedUsersAndRoles($policyStream) { + $allowedRoles = array(); + $allowedUsers = array(); + $usersAndRoles = array(); + try { + $xml = new SimpleXMLElement($policyStream); + } + catch (Exception $e) { + watchdog(t("Fedora_Repository"), t("No roles found in security policy, could not parse policy stream."), NULL, WATCHDOG_ERROR); + //we may not want to send this to the screen. + drupal_set_message(t('No roles found in security policy, could not parse policy stream: !message', array('!message' => $e->getMessage())), 'error'); + return NULL; + } + $xml->registerXPathNamespace('default', 'urn:oasis:names:tc:xacml:1.0:policy'); + + $conditions = $xml->xpath("//default:Condition"); + + foreach ($conditions as $condition) { + $designator = $condition->Apply->SubjectAttributeDesignator; + if (empty($designator)) {//$disignator may be wrapped by an or + $designator=$condition->Apply->Apply->SubjectAttributeDesignator; + } + $attributeId = strip_tags($designator['AttributeId']); + + if ($attributeId == "fedoraRole") { + foreach ($condition->Apply->Apply->AttributeValue as $attributeValue) { + $allowedRoles[] = strip_tags($attributeValue->asXML()); + } + foreach ($condition->Apply->Apply->Apply->AttributeValue as $attributeValue) { + $allowedRoles[] = strip_tags($attributeValue->asXML()); + } + } + if ($attributeId == "urn:fedora:names:fedora:2.1:subject:loginId") { + foreach ($condition->Apply->Apply->AttributeValue as $attributeValue) { + $allowedUsers[] = strip_tags($attributeValue->asXML()); + } + foreach ($condition->Apply->Apply->Apply->AttributeValue as $attributeValue) { + $allowedUsers[] = strip_tags($attributeValue->asXML()); + } + } + } + $usersAndRoles['users'] = $allowedUsers; + $usersAndRoles['roles'] = $allowedRoles; + return $usersAndRoles; + + } + // When a user's profile is saved in drupal we will attempt to create a collection for them in Fedora + // this will be their personal space. In the IR it is editable by users with the same role in the VRE + // it probably would not be. + function createPersonalPolicy($user) { + $doc = new DOMDocument(); + try { + $doc->load(drupal_get_path('module', 'Fedora_Repository') . '/policies/noObjectEditPolicy.xml'); + } + catch (exception $e) { + watchdog(t("Fedora_Repository"), t("Problem loading policy file."), NULL, WATCHDOG_ERROR); + } + $conditions = $doc->getElementsByTagName('Condition'); + foreach ($conditions as $condition) { + $designator = $condition->getElementsByTagName('SubjectAttributeDesignator'); + foreach ($designator as $des) { + $attributeId = $des->getAttribute('AttributeId'); + if ($attributeId == 'fedoraRole') { + $applies = $condition->getElementsByTagName('Apply'); + foreach ($applies as $apply) { + $functionId = $apply->getAttribute('FunctionId'); + if ($functionId == 'urn:oasis:names:tc:xacml:1.0:function:string-bag') { + foreach ($user->roles as $role) { + if (!($role == 'authenticated user' || $role == 'administrator')) { //don't want authenticated user included administrator already is included' + $newAttributeValue=$doc->createElement('AttributeValue', ''); + $newAttributeValue->setAttribute('DataType', 'http://www.w3.org/2001/XMLSchema#string'); + // $newAttributeValue->setAttribute('MustBePresent', 'FALSE'); + $apply->appendChild($newAttributeValue); + } + } + } + } + } + + if ($attributeId == 'urn:fedora:names:fedora:2.1:subject:loginId') { + $applies = $condition->getElementsByTagName('Apply'); + foreach ($applies as $apply) { + $functionId = $apply->getAttribute('FunctionId'); + if ($functionId == 'urn:oasis:names:tc:xacml:1.0:function:string-bag') { + $newAttributeValue=$doc->createElement('AttributeValue', $user->name); + $newAttributeValue->setAttribute('DataType', 'http://www.w3.org/2001/XMLSchema#string'); + //$newAttributeValue->setAttribute('MustBePresent', 'FALSE'); + $apply->appendChild($newAttributeValue); + } + } + } + } + } + + return $doc; //NULL; //$xml; + } + + /** + * Add a list of allowed users and roles to the given policy stream and return it. + * + * @param string $policy_stream + * @param array $users_and_roles + * @return DOMDocument + */ + function set_allowed_users_and_roles(&$policy_stream, $users_and_roles) { + $allowed_roles = $users_and_roles['roles']; + $allowed_users = $users_and_roles['users']; + $dom = new DOMDocument(); + $dom->loadXML($policy_stream); + $conditions = $dom->getElementsByTagName('Condition'); + foreach ($conditions as $condition) { + $designator = $condition->getElementsByTagName('SubjectAttributeDesignator'); + foreach ($designator as $des) { + $attributeId = $des->getAttribute('AttributeId'); + if ($attributeId == 'fedoraRole') { + // $applies = $condition->getElementsByTagName('Apply'); + $applies = $des->parentNode->getElementsByTagName('Apply'); + foreach ($applies as $apply) { + $functionId = $apply->getAttribute('FunctionId'); + if ($functionId == 'urn:oasis:names:tc:xacml:1.0:function:string-bag') { + foreach ($allowed_roles as $role) { + if (!($role == 'authenticated user' || $role == 'administrator')) { //don't want authenticated user included administrator already is included' + $newAttributeValue=$dom->createElement('AttributeValue', $role); + $newAttributeValue->setAttribute('DataType', 'http://www.w3.org/2001/XMLSchema#string'); + //$newAttributeValue->setAttribute('MustBePresent', 'FALSE'); + $apply->appendChild($newAttributeValue); + } + } + } + } + } + + if ($attributeId == 'urn:fedora:names:fedora:2.1:subject:loginId') { + // $applies = $condition->getElementsByTagName('Apply'); + $applies = $des->parentNode->getElementsByTagName('Apply'); + foreach ($applies as $apply) { + $functionId = $apply->getAttribute('FunctionId'); + if ($functionId == 'urn:oasis:names:tc:xacml:1.0:function:string-bag') { + foreach ( $allowed_users as $username ) { + $newAttributeValue=$dom->createElement('AttributeValue', $username ); + $newAttributeValue->setAttribute('DataType', 'http://www.w3.org/2001/XMLSchema#string'); + //$newAttributeValue->setAttribute('MustBePresent', 'FALSE'); + $apply->appendChild($newAttributeValue); + } + } + } + } + } + } + // $this->collection_policy_stream = $dom->saveXML(); + return $dom->saveXML(); + } +} diff --git a/XMLDatastream.inc b/XMLDatastream.inc new file mode 100644 index 00000000..e99cbbca --- /dev/null +++ b/XMLDatastream.inc @@ -0,0 +1,277 @@ +pid=$pid; + $this->dsid=$dsid; + + if ($xmlStr !== NULL) { + $this->xml = (is_object($xmlStr) && get_class($xmlStr) == DOMDocument)?$xmlStr : DOMDocument::loadXML($xmlStr); + } + } + + + /** + * Gets the identifier for this XMLDatastream + * Returns FALSE on failure. + * + * NOTE: not available if constructed directly from file. + * + * @return string identifier + */ + public function getIdentifier() { + return ($this->pid != NULL && $this->dsid != NULL) ? $this->pid . '/'. $this->dsid : FALSE; + } + + + /** + * Dumps the XMLDatastream as an XML String + * + * + * @return string xml + */ + public function dumpXml() { + if ($this->xml == NULL) { + $this->fetchXml(); + } + return $this->xml->saveXml(); + } + + + /** + * Validates the XMLDatastream against the schema location + * defined by the xmlns:schemaLocation attribute of the root + * element. If the xmlns:schemaLocation attribute does not exist, + * then it is assumed to be the old schema and it attempts to convert + * using the convertFromOldSchema method. + * + * TODO: Maybe change it so that it always validates against a known + * schema. This makes more sense because this class assumes the structure + * to be known after it has been validated. + * + * @return boolean $valid + */ + public function validate() { + if ($this->valid === NULL) { + $ret = TRUE; + if ($this->xml == NULL) { + $this->fetchXml(); + } + // figure out if we're dealing with a new or old schema + $rootEl = $this->xml->firstChild; + if (!$rootEl->hasAttributes() || $rootEl->attributes->getNamedItem('schemaLocation') === NULL ) { + //$tmpname = substr($this->pid, strpos($this->pid, ':') + 1); + $tmpname = user_password(10); + $this->convertFromOldSchema(); + drupal_add_js("fedora_repository_print_new_schema_$tmpname = function(tagID) { + var target = document.getElementById(tagID); + var content = target.innerHTML; + var text = 'Title' + + '' + content +''; + printerWindow = window.open('', '', 'toolbar=no,location=no,' + 'status=no,menu=no,scrollbars=yes,width=650,height=400'); + printerWindow.document.open(); +printerWindow.document.write(text); +} ", 'inline'); + + drupal_set_message('Warning: XMLDatastream performed conversion of \''. $this->getIdentifier() .'\' from old schema. Please update the datastream. The new datastream contents are here. '); + $rootEl = $this->xml->firstChild; + } + + $schemaLocation = NULL; + if ($this->forceSchema) { + // hack because you cant easily get the static property value from + // a subclass. + $vars = get_class_vars(get_class($this)); + $schemaLocation = $vars['SCHEMA_URI']; + + } + elseif ($rootEl->attributes->getNamedItem('schemaLocation') !== NULL) { + //figure out where the schema is located and validate. + list(, $schemaLocation) = preg_split('/\s+/', $rootEl->attributes->getNamedItem('schemaLocation')->nodeValue); + } + $schemaLocation = NULL; + return TRUE; + if ($schemaLocation !== NULL) { + if (!$this->xml->schemaValidate($schemaLocation)) { + $ret = FALSE; + $errors=libxml_get_errors(); + foreach ($errors as $err) { + self::$errors[] = 'XML Error: Line '. $err->line .': '. $err->message; + } + } + else { + $this->name=$rootEl->attributes->getNamedItem('name')->nodeValue; + } + } + else { + $ret = FALSE; + self::$errors[] = 'Unable to load schema.'; + } + + $this->valid = $ret; + } + + return $this->valid; + } + + + /** + * Saves the current XML datastream back to fedora. The XML must validate. + * + * @return boolean $success + */ + public function saveToFedora() { + module_load_include('inc', 'Fedora_Repository', 'api/fedora_item'); + if ($this->validate()) { + $item = new Fedora_Item($this->pid); + $item->modify_datastream_by_value($this->dumpXml(), $this->dsid, $this->name, NULL); + return TRUE; + } + return FALSE; + } + + /** + * Purges veersions of the datastream newer than and including the start_date. If + * End date is specified, it can be used to purge a range of versions instead. Date should be in + * DATE_RFC822 format + * + * @param string $start_date + * @param string $end_date + * @return boolean $valid + */ + public function purgeVersions($start_date, $end_date = NULL) { + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + $fedora_item = new Fedora_Item($this->pid); + return $fedora_item->purge_datastream( $this->dsid, $start_date, $end_date); + } + + /** + * Gets the history of the datastream from fedora. + * Returns false on failure. + * + * @return string[] $ret + */ + public function getHistory() { + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + $fedora_item = new Fedora_Item($this->pid); + $history = $fedora_item->get_datastream_history($this->dsid); + + $ret = FALSE; + if ($history !== FALSE) { + $ret = array(); + foreach ($history as $version) { + if ($version->versionID !== NULL) $ret[] = array('versionID' => $version->versionID, 'createDate' => $version->createDate); + } + } + return $ret; + } + + /** + * Attempts to convert from the old XML schema to the new by + * traversing the XML DOM and building a new DOM. When done + * $this->xml is replaced by the newly created DOM.. + * + * @return void + */ + abstract protected function convertFromOldSchema(); + +} diff --git a/api/dublin_core.inc b/api/dublin_core.inc new file mode 100644 index 00000000..04321677 --- /dev/null +++ b/api/dublin_core.inc @@ -0,0 +1,124 @@ + array(), + 'dc:creator' => array(), + 'dc:subject' => array(), + 'dc:description' => array(), + 'dc:publisher' => array(), + 'dc:contributor' => array(), + 'dc:date' => array(), + 'dc:type' => array(), + 'dc:format' => array(), + 'dc:identifier' => array(), + 'dc:source' => array(), + 'dc:language' => array(), + 'dc:relation' => array(), + 'dc:coverage' => array(), + 'dc:rights' => array(), + ); + public $owner; + + /** + * Constructs a Dublin_Core object from a Fedora_Item object and populates + * the $dc array. + * @param $item + */ + function Dublin_Core($item = NULL) { + if (!empty($item)) { + $this->owner = $item; + $dc_xml = $item->get_datastream_dissemination('DC'); + $this->dc = self::import_from_xml_string($dc_xml)->dc; + } + } + + /** + * + * @param $element_name + * @param $value + */ + function add_element( $element_name, $value ) { + if (is_string($value) && is_array($this->dc[$element_name])) { + $this->dc[$element_name][] = $value; + } + } + +/** + * Replace the given DC element with the values in $values + * @param string $elemnt_name + * @param array $values + */ + function set_element($element_name, $values) { + if (is_array($values)) { + $this->dc[$element_name] = $values; + } + elseif (is_string($values)) { + $this->dc[$element_name] = array($values); + } + } + + /** + * Serialize this object to XML and return it. + */ + function as_xml( $with_preamble = FALSE ) { + $dc_xml = new DomDocument(); + $oai_dc = $dc_xml->createElementNS('http://www.openarchives.org/OAI/2.0/oai_dc/', 'oai_dc:dc'); + $oai_dc->setAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); + foreach ($this->dc as $dc_element => $values) { + if (is_array($values) && !empty($values)) { + foreach ($values as $value) { + $new_item = $dc_xml->createElement($dc_element, $value); + $oai_dc->appendChild($new_item); + } + } + else { + $new_item = $dc_xml->createElement($dc_element); + $oai_dc->appendChild($new_item); + } + } + $dc_xml->appendChild($oai_dc); + return $dc_xml->saveXML(); + } + + static function create_dc_from_dict() { + + } + + function save($alt_owner = NULL) { + $item_to_update = (!empty($alt_owner) ? $alt_owner : $this->owner); + // My Java roots showing, trying to do polymorphism in PHP. + if (!empty($item_to_update)) { + $item_to_update->modify_datastream_by_value($this->as_xml(), 'DC', 'Default Dublin Core Metadata', 'text/xml'); + } + } + + /** + * Creates a new instance of the class by parsing dc_xml + * @param string $dc_xml + * @return Dublin_Core + */ + static function import_from_xml_string($dc_xml) { + $dc_doc = new DomDocument(); + if ($dc_doc->loadXML($dc_xml)) { + $oai_dc = $dc_doc->getElementsByTagNameNS('http://purl.org/dc/elements/1.1/', '*'); + $new_dc = new Dublin_Core(); + foreach ($oai_dc as $child) { + array_push($new_dc->dc[$child->nodeName], $child->nodeValue); + } + return $new_dc; + } + else { + return NULL; + } + } + +} + diff --git a/api/fedora_collection.inc b/api/fedora_collection.inc new file mode 100644 index 00000000..c0dce02a --- /dev/null +++ b/api/fedora_collection.inc @@ -0,0 +1,133 @@ + $format + */ +function export_collection($collection_pid, $relationship = 'isMemberOfCollection', $format = 'info:fedora/fedora-system:FOXML-1.1' ) { + $collection_item = new Fedora_Item($collection_pid); + $foxml = $collection_item->export_as_foxml(); + + $file_dir = file_directory_path(); + + // Create a temporary directory to contain the exported FOXML files. + $container = tempnam($file_dir, 'export_'); + file_delete($container); + print $container; + if (mkdir($container) && mkdir($container . '/'. $collection_pid)) { + $foxml_dir = $container . '/'. $collection_pid; + $file = fopen($foxml_dir . '/'. $collection_pid . '.xml', 'w'); + fwrite($file, $foxml); + fclose($file); + + $member_pids = get_related_items_as_array($collection_pid, $relationship); + foreach ($member_pids as $member) { + $file = fopen($foxml_dir . '/'. $member . '.xml', 'w'); + $item = new Fedora_Item($member); + $item_foxml = $item->export_as_foxml(); + fwrite($file, $item_foxml); + fclose($file); + } + if (system("cd $container;zip -r $collection_pid.zip $collection_pid/* >/dev/NULL") == 0) { + header("Content-type: application/zip"); + header('Content-Disposition: attachment; filename="' . $collection_pid . '.zip'. '"'); + $fh = fopen($container . '/'. $collection_pid . '.zip', 'r'); + $the_data = fread($fh, filesize($container . '/'. $collection_pid . '.zip')); + fclose($fh); + echo $the_data; + } + if (file_exists($container . '/'. $collection_pid)) { + system("rm -rf $container"); // I'm sorry. + } + } + else { + drupal_set_message("Error creating temp directory for batch export.", 'error'); + return FALSE; + } + return TRUE; +} + +/** + * Returns an array of pids that match the query contained in teh collection + * object's QUERY datastream or in the suppled $query parameter. + * @param $collection_pid + * @param $query + * @param $query_format R + */ +function get_related_items_as_xml($collection_pid, $relationship = array('isMemberOfCollection'), $limit = 10000, $offset = 0, $active_objects_only = TRUE, $cmodel = NULL) { + module_load_include('inc', 'fedora_repository', 'ObjectHelper'); + $collection_item = new Fedora_Item($collection_pid); + + global $user; + if (!fedora_repository_access(OBJECTHELPER :: $OBJECT_HELPER_VIEW_FEDORA, $pid, $user)) { + drupal_set_message(t("You do not have access to Fedora objects within the attempted namespace or access to Fedora denied."), 'error'); + return array(); + } + + $query_string = 'select $object $title $content from <#ri> + where ($object $title + and $object $content + and ('; + + if (is_array($relationship)) { + foreach ($relationship as $rel) { + $query_string .= '$object '; + if (next($relationship)) { + $query_string .= ' OR '; + } + } + } + elseif (is_string($relationship)) { + $query_string .= '$object '; + } + else { + return ''; + } + + $query_string .= ') '; + $query_string .= $active_objects_only ? 'and $object ' : ''; + + if ($cmodel) { + $query_string .= ' and $content '; + } + + $query_string .= ') + minus $content + order by $title'; + + $query_string = htmlentities(urlencode($query_string)); + + + $content = ''; + $url = variable_get('fedora_repository_url', 'http://localhost:8080/fedora/risearch'); + $url .= "?type=tuples&flush=TRUE&format=Sparql&limit=$limit&offset=$offset&lang=itql&stream=on&query=". $query_string; + $content .= do_curl($url); + + return $content; +} + +function get_related_items_as_array($collection_pid, $relationship = 'isMemberOfCollection', $limit = 10000, $offset = 0, $active_objects_only = TRUE, $cmodel = NULL) { + $content = get_related_items_as_xml($collection_pid, $relationship, $limit, $offset, $active_objects_only, $cmodel); + if (empty($content)) { + return array(); + } + + $content = new SimpleXMLElement($content); + + $resultsarray = array(); + foreach ($content->results->result as $result) { + $resultsarray[] = substr($result->object->attributes()->uri, 12); // Remove 'info:fedora/'. + } + return $resultsarray; +} diff --git a/api/fedora_export.inc b/api/fedora_export.inc new file mode 100644 index 00000000..d2b08baf --- /dev/null +++ b/api/fedora_export.inc @@ -0,0 +1,179 @@ +get_datastreams_list_as_SimpleXML($pid)) { + $log[] = log_line(t("Failed to get datastream %dsid for pid %pid", array('%dsid' => $ds->ID, '%pid' => $pid)), 'error'); + return FALSE; + } + + // Datastreams added as a result of the ingest process + $ignore_dsids = array('QUERY'); + + $paths = array(); + foreach ($object->datastreamDef as $ds) { + if (!in_array($ds->ID, $ignore_dsids)) { + $file = $dir .'/'. $ds->label .'.'. get_file_extension($ds->MIMEType); + $paths[$ds->ID] = $file; + + //$content = $ob_helper->getDatastreamDissemination($pid, $ds->ID); + if ($content = $ob_helper->getStream($pid, $ds->ID, FALSE)) { + if (!$fp = @fopen($file, 'w')) { + $log[] = log_line(t("Failed to open file %file to write datastream %dsid for pid %pid", array('%file' => $file, '%dsid' => $ds->ID, '%pid' => $pid)), 'error'); + return FALSE; + } + fwrite($fp, $content); + fclose($fp); + } + else { + $log[] = log_line(t("Failed to get datastream %dsid for pid %pid", array('%dsid' => $ds->ID, '%pid' => $pid)), 'error'); + } + } + } + return $paths; +} + +function export_foxml_for_pid($pid, $dir, $paths, &$log, $format = FOXML_11, $remove_islandora = FALSE) { + module_load_include('inc', 'fedora_repository', 'ObjectHelper'); + $ob_helper = new ObjectHelper(); + if (!$object_xml = $ob_helper->getObject($pid, 'migrate', $format)) { + $log[] = log_line(t("Failed to get foxml for %pid", array('%pid' => $pid)), 'error'); + return FALSE; + } + + $foxml = new DOMDocument(); + $foxml->loadXML($object_xml); + + $xpath = new DOMXpath($foxml); + + // Remove rdf elements added during ingest (if present) + if ($remove_islandora) { + $xpath->registerNamespace('rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'); + $descNode = $xpath->query("//rdf:RDF/rdf:Description")->item(0); + + if ($model = $descNode->getElementsByTagName('hasModel')->item(0)) { + $descNode->removeChild($model); + } + + if ($member = $descNode->getElementsByTagName('rel:isMemberOfCollection')->item(0)) { + $descNode->removeChild($member); + } + } + + if ($remove_islandora) { + // Update object paths in the foxml for this pid + switch ($format) { + case FOXML_10: + case FOXML_11: + + $disallowed_groups = array('E', 'R'); + + // Update datastream uris + $xpath->registerNamespace('foxml', 'info:fedora/fedora-system:def/foxml#'); + foreach ($xpath->query("//foxml:datastream[@ID]") as $dsNode) { + + // Don't update datastreams having external uris + if (in_array($dsNode->getAttribute('CONTROL_GROUP'), $disallowed_groups)) { + continue; + } + + $dsId = $dsNode->getAttribute('ID'); + + // Remove QUERY datastream + if ($dsId == "QUERY") { + $parentNode = $xpath->query('/foxml:digitalObject')->item(0); + $parentNode->removeChild($dsNode); + } + + foreach ($dsNode->getElementsByTagName('*') as $contentNode) { + if ($str = $contentNode->getAttribute('REF')) { + $contentNode->setAttribute('REF', url($paths[$dsId], array('absolute' => TRUE))); + } + } + } + break; + + case METS_10: + case METS_11: + // Update datastream uris + $xpath->registerNamespace('METS', 'http://www.loc.gov/METS/'); + foreach ($xpath->query('//METS:fileGrp[@ID="DATASTREAMS"]/METS:fileGrp') as $dsNode) { + + $dsId = $dsNode->getAttribute('ID'); + + // Remove QUERY datastream + if ($dsId == "QUERY") { + $parentNode = $xpath->query('//METS:fileGrp[@ID="DATASTREAMS"]')->item(0); + $parentNode->removeChild($dsNode); + } + + $xpath->registerNamespace('xlink', 'http://www.loc.gov/METS/'); + foreach ($xpath->query('METS:file[@OWNERID!="E"][@OWNERID!="R"]/METS:FLocat[@xlink:href]', $dsNode) as $Floc) { + $Floc->setAttribute('xlink:href', url($paths[$dsId], array('absolute' => TRUE))); + } +/* + foreach ($dsNode->getElementsByTagName('METS:file') as $contentNode) { + // Don't update datastreams having external uris + if (in_array($dsNode->getAttribute('OWNERID'), $disallowed_groups)) { + continue; + } + + foreach ($xpath->('METS:FLocat[@xlink:href]', $contentNode) as $Floc) { + $Floc->setAttribute('xlink:href', url($paths[$dsId], array('absolute' => true))); + } + `} +*/ + } + + break; + + default: + $log[] = log_line(t("Unknown or invalid format: ". $format), 'error'); + return FALSE; + } + } //if $remove_islandora + + $file = $dir .'/'. $pid .'.xml'; + if (!$foxml->save($file)) { + $log[] = log_line(t("Failed to write datastream %dsid for pid %pid to %file", array('%dsid' => $ds->ID, '%pid' => $pid, '%file' => $file)), 'error'); + return FALSE; + } + else { + $log[] = log_line(t("Exported %pid to %file", array('%pid' => $pid, '%file' => $file)), 'info'); + } + + return TRUE; +} + +function get_file_extension($mimeType) { + return substr(strstr($mimeType, '/'), 1); +} + +function log_line($msg, $severity = 'info', $sep = "\t") { + return date("Y-m-d H:i:s") . $sep . ucfirst($severity) . $sep . $msg; +} diff --git a/api/fedora_item.inc b/api/fedora_item.inc new file mode 100644 index 00000000..01d71cfa --- /dev/null +++ b/api/fedora_item.inc @@ -0,0 +1,780 @@ +pid = $pid; + if (isset(Fedora_Item::$instantiated_pids[$pid])) { + $this->objectProfile =& Fedora_Item::$instantiated_pids[$pid]->objectProfile; + $this->datastreams =& Fedora_Item::$instantiated_pids[$pid]->datastreams; + $this->datastreams_list =& Fedora_Item::$instantiated_pids[$pid]->datastreams_list; + } else { + if (empty(self::$connection_helper)) { + self::$connection_helper = new ConnectionHelper(); + } + + $raw_objprofile = $this->soap_call('getObjectProfile', array('pid' => $this->pid, 'asOfDateTime' => "")); + + if (!empty($raw_objprofile)) { + $this->objectProfile = $raw_objprofile->objectProfile; + $this->datastreams = $this->get_datastreams_list_as_array(); + } else { + $this->objectProfile = ''; + $this->datastreams = array(); + } + Fedora_Item::$instantiated_pids[$pid]=&$this; + } + } + + function exists() { + return (!empty($this->objectProfile)); + } + + function add_datastream_from_file($datastream_file, $datastream_id, $datastream_label = NULL, $datastream_mimetype = '', $controlGroup = 'M',$logMessage = null) { + module_load_include('inc', 'fedora_repository', 'MimeClass'); + if (empty($datastream_mimetype)) { + // Get mime type from the file extension. + $mimetype_helper = new MimeClass(); + $datastream_mimetype = $mimetype_helper->getType($datastream_file); + } + $original_path = $datastream_file; + // Temporarily move file to a web-accessible location. + file_copy($datastream_file, file_directory_path()); + $datastream_url = drupal_urlencode($datastream_file); + $url = file_create_url($datastream_url); + + $return_value = $this->add_datastream_from_url($url, $datastream_id, $datastream_label, $datastream_mimetype, $controlGroup,$logMessage); + + if ($original_path != $datastream_file) { + file_delete($datastream_file); + } + return $return_value; + } + + function add_datastream_from_url($datastream_url, $datastream_id, $datastream_label = NULL, $datastream_mimetype = '', $controlGroup = 'M',$logMessage = null) { + if (empty( $datastream_label)) { + $datastream_label = $datastream_id; + } + + $params = array( + 'pid' => $this->pid, + 'dsID' => $datastream_id, + 'altIDs' => NULL, + 'dsLabel' => $datastream_label, + 'versionable' => TRUE, + 'MIMEType' => $datastream_mimetype, + 'formatURI' => NULL, + 'dsLocation' => $datastream_url, + 'controlGroup' => $controlGroup, + 'dsState' => 'A', + 'checksumType' => 'DISABLED', + 'checksum' => 'none', + 'logMessage' => ($logMessage != null)?$logMessage: 'Ingested object '. $datastream_id + ); + + return $this->soap_call( 'addDataStream', $params )->datastreamID; + } + + function add_datastream_from_string($str, $datastream_id, $datastream_label = NULL, $datastream_mimetype = 'text/xml', $controlGroup = 'M',$logMessage = null) { + $dir = sys_get_temp_dir(); + $tmpfilename = tempnam($dir, 'fedoratmp'); + $tmpfile = fopen($tmpfilename, 'w'); + fwrite($tmpfile, $str, strlen($str)); + fclose($tmpfile); + $returnvalue = $this->add_datastream_from_file($tmpfilename, $datastream_id, $datastream_label, $datastream_mimetype, $controlGroup,$logMessage); + unlink($tmpfilename); + return $returnvalue; + } + + /** + * Add a relationship string to this object's RELS-EXT. + * does not support rels-int yet. + * @param string $relationship + * @param $object + */ + function add_relationship($relationship, $object, $namespaceURI = RELS_EXT_URI) { + $ds_list = $this->get_datastreams_list_as_array(); + + if (empty($ds_list['RELS-EXT'])) { + $this->add_datastream_from_string(' + + + ', 'RELS-EXT', 'Fedora object-to-object relationship metadata', 'text/xml', 'X'); + } + + $relsext = $this->get_datastream_dissemination('RELS-EXT'); + + if (substr($object, 0, 12) != 'info:fedora/') { + $object = "info:fedora/$object"; + } + + $relsextxml = new DomDocument(); + + $relsextxml->loadXML($relsext); + $description = $relsextxml->getElementsByTagNameNS('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'Description'); + if ($description->length == 0) { + $description = $relsextxml->getElementsByTagNameNS('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'description'); + } + $description=$description->item(0); + + // Create the new relationship node. + $newrel = $relsextxml->createElementNS($namespaceURI, $relationship); + + $newrel->setAttribute('rdf:resource', $object); + + $description->appendChild($newrel); + $this->modify_datastream_by_value( $relsextxml->saveXML(), 'RELS-EXT', "Fedora Object-to-Object Relationship Metadata", 'text/xml'); + //print ($description->dump_node()); + /* + $params = array( 'pid' => $this->pid, + 'relationship' => $relationship, + 'object' => $object, + 'isLiteral' => FALSE, + 'datatype' => '', + ); + + return $this->soap_call( 'addRelationship', $params ); + */ + } + + /** + * Removes the given relationship from the item's RELS-EXT and re-saves it. + * @param string $relationship + * @param string $object + */ + function purge_relationship($relationship, $object) { + $relsext = $this->get_datastream_dissemination('RELS-EXT'); + $namespaceURI = 'info:fedora/fedora-system:def/relations-external#'; + // Pre-pend a namespace prefix to recognized relationships + + switch ($relationship) { + case 'rel:isMemberOf': + case 'fedora:isMemberOf': + $relationship = "isMemberOf"; + $namespaceURI = 'info:fedora/fedora-system:def/relations-external#'; + break; + case "rel:isMemberOfCollection": + case "fedora:isMemberOfCollection": + $relationship = "isMemberOfCollection"; + $namespaceURI = 'info:fedora/fedora-system:def/relations-external#'; + break; + case "fedora:isPartOf": + $relationship = "isPartOf"; + $namespaceURI = 'info:fedora/fedora-system:def/relations-external#'; + break; + } + + if (substr($object, 0, 12) != 'info:fedora/') { + $object = "info:fedora/$object"; + } + + $relsextxml = new DomDocument(); + $relsextxml->loadXML($relsext); + $modified = FALSE; + $rels = $relsextxml->getElementsByTagNameNS($namespaceURI, $relationship); + if (!empty($rels)) { + foreach ($rels as $rel) { + if ($rel->getAttributeNS('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'resource') == $object) { + $rel->parentNode->removeChild($rel); + $modified = TRUE; + } + } + } + if ($modified) { + $this->modify_datastream_by_value( $relsextxml->saveXML(), 'RELS-EXT', "Fedora Object-to-Object Relationship Metadata", 'text/xml'); + } + return $modified; + //print ($description->dump_node()); + } + + function export_as_foxml() { + $params = array( + 'pid' => $this->pid, + 'format' => 'info:fedora/fedora-system:FOXML-1.1', + 'context' => 'migrate', + ); + $result = self::soap_call('export', $params); + return $result->objectXML; + } + + /** + * Does a search using the "query" format followed by the Fedora REST APi. + * + * @param string $pattern to search for, including wildcards. + * @param string $field The field to search on, e.g. pid, title, cDate. See http://www.fedora-commons.org/confluence/display/FCR30/REST+API#RESTAPI-findObjects for details + * @param int $max_results not used at this time + * @return Array of pid => title pairs that match the results + */ + static function find_objects_by_pattern($pattern = '*', $field = 'pid', $max_results = 100, $resultFields = array()) { + module_load_include('inc', 'fedora_repository', 'api/fedora_utils'); + + $pattern = drupal_urlencode($pattern); + $done = FALSE; + $cursor = 0; + $session_token = ''; + $i = 0; + $results = array(); + while (!$done && $i < 5) { + $i++; + $url = variable_get('fedora_base_url', 'http://localhost:8080/fedora'); + if ($cursor == 0) { + $url .= "/objects?query=$field~$pattern&pid=true&title=true&resultFormat=xml&maxResults=$max_results"; + } + else { + $url .= "/objects?pid=true&title=truesessionToken=$session_token&resultFormat=xml&maxResults=$max_results"; + } + + if (count($resultFields) > 0) { + $url .= '&'.join('=true&',$resultFields).'=true'; + } + + $resultxml = do_curl($url); + + libxml_use_internal_errors(TRUE); + $resultelements = simplexml_load_string($resultxml); + if ($resultelements === FALSE) { + libxml_clear_errors(); + break; + } + $cursor += count($resultelements->resultList->objectFields); + if (count($resultelements->resultList->objectFields) < $max_results + || count($resultelements->resultList->objectFields) == 0) { + $done = TRUE; + } + foreach ($resultelements->resultList->objectFields as $obj) { + + $ret = (string)$obj->title; + if (count($resultFields) > 0) { + $ret = array('title' => $ret); + foreach ($resultFields as $field) { + $ret[$field]=(string)$obj->$field; + } + } + $results[(string)$obj->pid] = $ret; + $cursor++; + if ($cursor >= $max_results) { + $done = TRUE; + break; + } + } + $session_token = $resultelements->listSession->token; + $done = !empty($session_token); + } + return $results; + } + + function get_datastream_dissemination($dsid, $as_of_date_time = "") { + $params = array( + 'pid' => $this->pid, + 'dsID' => $dsid, + 'asOfDateTime' => $as_of_date_time, + ); + $object = self::soap_call('getDataStreamDissemination', $params); + if (!empty($object)) { + $content = $object->dissemination->stream; + $content = trim($content); + } + else { + $content = ""; + } + return $content; + } + + function get_datastream($dsid, $as_of_date_time = "") { + $params = array( + 'pid' => $this->pid, + 'dsID' => $dsid, + 'asOfDateTime' => $as_of_date_time, + ); + $object = self::soap_call('getDatastream', $params); + + return $object->datastream; + } + + function get_datastream_history($dsid) { + $params = array( + 'pid' => $this->pid, + 'dsID' => $dsid + ); + $object = self::soap_call('getDatastreamHistory', $params); + $ret = FALSE; + if (!empty($object)) { + $ret = $object->datastream; + } + + return $ret; + } + + function get_dissemination($service_definition_pid, $method_name, $parameters = array(), $as_of_date_time = null) { + $params = array( + 'pid' => $this->pid, + 'serviceDefinitionPid' => $service_definition_pid, + 'methodName' => $method_name, + 'parameters' => $parameters, + 'asOfDateTime' => $as_of_date_time, + ); + $object = self::soap_call('getDissemination', $params); + if (!empty($object)) { + $content = $object->dissemination->stream; + $content = trim($content); + } + else { + $content = ""; + } + return $content; + } + + /** + * Retrieves and returns a SimpleXML list of this item's datastreams, and stores them + * as an instance variable for caching purposes. + * + * @return SimpleXMLElement + */ + function get_datastreams_list_as_SimpleXML() { + //if ( empty( $this->datastreams_list ) ) { + $params = array( + 'pid' => $this->pid, + 'asOfDateTime' => "" + ); + + $this->datastreams_list = $this->soap_call('listDataStreams', $params); + //} + return $this->datastreams_list; + } + /** + * * DatastreamControlGroup controlGroup - String restricted to the values of "X", "M", "R", or "E" (InlineXML,Managed Content,Redirect, or External Referenced). + * String ID - The datastream ID (64 characters max). + * String versionID - The ID of the most recent datastream version + * String[] altIDs - Alternative IDs for the datastream, if any. + * String label - The Label of the datastream. + * boolean versionable - Whether the datastream is versionable. + * String MIMEType - The mime-type for the datastream, if set. + * String formatURI - The format uri for the datastream, if set. + * String createDate - The date the first version of the datastream was created. + * long size - The size of the datastream in Fedora. Only valid for inline XML metadata and managed content datastreams. + * String state - The state of the datastream. Will be "A" (active), "I" (inactive) or "D" (deleted). + * String location - If the datastream is an external reference or redirect, the url to the contents. TODO: Managed? + * String checksumType - The algorithm used to compute the checksum. One of "DEFAULT", "DISABLED", "MD5", "SHA-1", "SHA-256", "SHA-385", "SHA-512". + * String checksum - The value of the checksum represented as a hexadecimal string. + + * + * @param string $dsid + * @return datastream object + * get the mimetype size etc. in one shot. instead of iterating throught the datastream list for what we need + */ + function get_datastream_info($dsid,$as_of_date_time = ""){ + $params = array( + 'pid' => $this->pid, + 'dsID' => $dsid, + 'asOfDateTime' => $as_of_date_time + ); + + return $this->soap_call('getDatastream', $params); + + } + + /** + * Returns an associative array of this object's datastreams. Results look like this: + * + * 'DC' => + * array + * 'label' => string 'Dublin Core Record for this object' (length=34) + * 'MIMEType' => string 'text/xml' (length=8) + * 'RELS-EXT' => + * array + * 'label' => string 'RDF Statements about this object' (length=32) + * 'MIMEType' => string 'application/rdf+xml' (length=19) + * + * @return array + */ + function get_datastreams_list_as_array() { + $this->get_datastreams_list_as_SimpleXML(); + $ds_list = array(); + if ($this->datastreams_list != FALSE) { + + // This is a really annoying edge case: if there is only one + // datastream, instead of returning it as an array, only + // the single item will be returned directly. We have to + // check for this. + if (count($this->datastreams_list->datastreamDef) >= 2) { + foreach ($this->datastreams_list->datastreamDef as $ds) { + if (!is_object($ds)) { + print_r($ds); + } + $ds_list[$ds->ID]['label'] = $ds->label; + $ds_list[$ds->ID]['MIMEType'] = $ds->MIMEType; + $ds_list[$ds->ID]['URL'] = $this->url() . '/'. $ds->ID . '/'. drupal_urlencode($ds->label); + } + } + else { + $ds = $this->datastreams_list->datastreamDef; + $ds_list[$ds->ID]['label'] = $ds->label; + $ds_list[$ds->ID]['MIMEType'] = $ds->MIMEType; + $ds_list[$ds->ID]['URL'] = $this->url().'/'.$ds->ID.'/'.drupal_urlencode($ds->label); + } + } + + return $ds_list; + } + + /** + * Returns a MIME type string for the given Datastream ID. + * + * @param string $dsid + * @return string + */ + function get_mimetype_of_datastream($dsid) { + $this->get_datastreams_list_as_SimpleXML(); + + $mimetype = ''; + foreach ($datastream_list as $datastream) { + foreach ($datastream as $datastreamValue) { + if ($datastreamValue->ID == $dsid) { + return $datastreamValue->MIMEType; + } + } + } + + return ''; + } + + /** + * Currently the Fedora API call getRelationships is reporting an uncaught + * exception so we will parse the RELS-EXT ourselves and simulate the + * documented behaviour. + * @param String $relationship - filter the results to match this string. + */ + function get_relationships($relationship = NULL) { + $relationships = array(); + try { + $relsext = $this->get_datastream_dissemination('RELS-EXT'); + } + catch (exception $e) { + drupal_set_message("Error retrieving RELS-EXT of object $pid", 'error'); + return $relationships; + } + + // Parse the RELS-EXT into an associative array. + $relsextxml = new DOMDocument(); + $relsextxml->loadXML($relsext); + $relsextxml->normalizeDocument(); + $rels = $relsextxml->getElementsByTagNameNS('info:fedora/fedora-system:def/relations-external#', '*'); + + foreach ($rels as $child) { + if (empty($relationship) || preg_match("/$relationship/", $child->tagName)) { + $relationships[] = array( + 'subject' => $this->pid, + 'predicate' => $child->tagName, + 'object' => substr($child->getAttributeNS('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'resource'), 12), + ); + } + } + return $relationships; + //$children = $relsextxml->RDF->description; + //$children = $relsextxml->RDF->description; + //$params = array( 'pid' => $this->pid, + // 'relationship' => 'NULL' ); + //return $this->soap_call( 'getRelationships', $params ); + } + + /** + * Creates a RELS-EXT XML stream from the supplied array and saves it to + * the item on the server. + * @param $relationships + */ + function save_relationships($relationships) { + // Verify the array format and that it isn't empty. + if (!empty($relationships)) { + $relsextxml = '' + . ''; + + foreach ($relationships as $rel) { + if (empty($rel['subject']) || empty($rel['predicate']) || empty($rel['object']) || $rel['subject'] != 'info:fedora/'. $this->pid) { + // drupal_set_message should use parameterized variables, not interpolated. + drupal_set_message("Error with relationship format: ". $rel['subject'] . " - ". $rel['predicate'] . ' - '. $rel['object'], "error"); + return FALSE; + } + } + } + + // Do the messy work constructing the RELS-EXT XML. Because addRelationship is broken. + + return FALSE; + } + + /** + * Removes this object form the repository. + */ + function purge($log_message = 'Purged using Islandora API.', $force = FALSE) { + $params = array( + 'pid' => $this->pid, + 'logMessage' => $log_message, + 'force' => $force + ); + + return $this->soap_call('purgeObject', $params); + } + + function purge_datastream( $dsID, $start_date = NULL, $end_date = NULL, $log_message = 'Purged datastream using Islandora API', $force = FALSE) { + $params = array( + 'pid' => $this->pid, + 'dsID' => $dsID, + 'startDT' => $start_date, + 'endDT' => $end_date, + 'logMessage' => $log_message, + 'force' => $force, + ); + return $this->soap_call('purgeDatastream', $params); + } + + function url() { + global $base_url; + return $base_url . '/fedora/repository/'. $this->pid . (!empty($this->objectProfile) ? '/-/'. drupal_urlencode($this->objectProfile->objLabel) : ''); + } + + static function get_next_PID_in_namespace( $pid_namespace = '') { + if (empty($pid_namespace)) { + // Just get the first one in the config settings. + $allowed_namespaces = explode(" ", variable_get('fedora_pids_allowed', 'default: demo: changeme: Islandora: ilives: ')); + $pid_namespace = $allowed_namespaces[0]; + if (!empty($pid_namespace)) { + $pid_namespace = substr($pid_namespace, 0, strpos($pid_namespace, ":")); + } + else { + $pid_namespace = 'default'; + } + } + + $params = array( + 'numPIDs' => '', + 'pidNamespace' => $pid_namespace, + ); + + $result = self::soap_call('getNextPID', $params); + return $result->pid; + } + + static function ingest_from_FOXML($foxml) { + $params = array('objectXML' => $foxml->saveXML(), 'format' => "info:fedora/fedora-system:FOXML-1.1", 'logMessage' => "Fedora Object Ingested"); + $object = self::soap_call('ingest', $params); + return new Fedora_Item($object->objectPID); + } + + static function ingest_from_FOXML_file($foxml_file) { + $foxml = new DOMDocument(); + $foxml->load( $foxml_file ); + return self::ingest_from_FOXML($foxml); + } + + static function ingest_from_FOXML_files_in_directory($path) { + // Open the directory + $dir_handle = @opendir($path); + // Loop through the files + while ($file = readdir($dir_handle)) { + if ($file == "." || $file == ".." || strtolower(substr($file, strlen($file) - 4)) != '.xml') { + continue; + } + + try { + self::ingest_from_FOXML_file( $path . '/'. $file ); + } + catch (exception $e) { + } + } + // Close + closedir($dir_handle); + } + + function modify_object($label = '', $state = null, $ownerId = null, $logMessage = 'Modified by Islandora API', $quiet=TRUE) { + + $params = array( + 'pid' => $this->pid, + 'ownerId' => $ownerId, + 'state' => $state, + 'label' => $label, + 'logMessage' => $logMessage + ); + + return self::soap_call('modifyObject', $params, $quiet); + } + + function modify_datastream_by_value($content, $dsid, $label, $mime_type, $force = FALSE, $logMessage = 'Modified by Islandora API',$quiet=FALSE) { + $params = array( + 'pid' => $this->pid, + 'dsID' => $dsid, + 'altIDs' => NULL, + 'dsLabel' => $label, + 'MIMEType' => $mime_type, + 'formatURI' => NULL, + 'dsContent' => $content, + 'checksumType' => 'DISABLED', + 'checksum' => 'none', + 'logMessage' => $logMessage, + 'force' => $force + ); + return self::soap_call('modifyDatastreamByValue', $params,$quiet); + } + + static function soap_call( $function_name, $params_array, $quiet = FALSE ) { + if (!self::$connection_helper) { + module_load_include('inc', 'fedora_repository', 'ConnectionHelper'); + self::$connection_helper = new ConnectionHelper(); + } + switch ($function_name) { + case 'ingest': + case 'addDataStream': + case 'addRelationship': + case 'export': + case 'getDatastream': + case 'getDatastreamHistory': + case 'getNextPID': + case 'getRelationships': + case 'modifyDatastreamByValue': + case 'purgeDatastream': + case 'purgeObject': + case 'modifyObject': + $soap_client = self::$connection_helper->getSoapClient(variable_get('fedora_soap_manage_url', 'http://localhost:8080/fedora/services/management?wsdl')); + try { + if (!empty($soap_client)) { + $result = $soap_client->__soapCall($function_name, array('parameters' => $params_array)); + } + else { + watchdog(t("FEDORA_REPOSITORY"), t("Error trying to get SOAP client connection.")); + return NULL; + } + } + catch (exception $e) { + if (!$quiet) { + + if (preg_match('/org\.fcrepo\.server\.security\.xacml\.pep\.AuthzDeniedException/',$e->getMessage())) { + drupal_set_message(t('Error: Insufficient permissions to call SOAP function !fn.', array('!fn' => $function_name) ), 'error'); + } else { + drupal_set_message(t("Error trying to call SOAP function $function_name. Check watchdog logs for more information."), 'error'); + } + + watchdog(t("FEDORA_REPOSITORY"), t("Error Trying to call SOAP function !fn: !e", array('!fn' => $function_name, '!e' => $e)), NULL, WATCHDOG_ERROR); + } + return NULL; + } + break; + + default: + try { + $soap_client = self::$connection_helper->getSoapClient( variable_get('fedora_soap_url', 'http://localhost:8080/fedora/services/access?wsdl')); + if (!empty($soap_client)) { + $result = $soap_client->__soapCall($function_name, array('parameters' => $params_array)); + } + else { + watchdog(t("FEDORA_REPOSITORY"), t("Error trying to get SOAP client connection.")); + return NULL; + } + } + catch (exception $e) { + + if (!$quiet) { + watchdog(t("FEDORA_REPOSITORY"), t("Error trying to call SOAP function !fn: !e", array('!fn' => $function_name, '!e' => $e)), NULL, WATCHDOG_ERROR); + } + return NULL; + } + } + return $result; + } + + + /** + * Creates the minimal FOXML for a new Fedora object, which is then passed to + * ingest_from_FOXML to be added to the repository. + * + * @param string $pid if none given, getnextpid will be called. + * @param string $state The initial state, A - Active, I - Inactive, D - Deleted + */ + static function create_object_FOXML($pid = '', $state = 'A', $label = 'Untitled', $owner = '') { + $foxml = new DOMDocument("1.0", "UTF-8"); + $foxml->formatOutput = TRUE; + if (empty($pid)) { + // Call getNextPid + $pid = self::get_next_PID_in_namespace(); + } + if (empty($owner)) { + if (!empty($user->uid)) { // Default to current Drupal user. + $owner = $user->uid; + } + else { // We are not inside Drupal + $owner = 'fedoraAdmin'; + } + } + + $root_element = $foxml->createElement("foxml:digitalObject"); + $root_element->setAttribute("VERSION", "1.1"); + $root_element->setAttribute("PID", $pid); + $root_element->setAttribute("xmlns:foxml", "info:fedora/fedora-system:def/foxml#"); + $root_element->setAttribute("xmlns:xsl", "http://www.w3.org/2001/XMLSchema-instance"); + $root_element->setAttribute("xsl:schemaLocation", "info:fedora/fedora-system:def/foxml# http://www.fedora.info/definitions/1/0/foxml1-1.xsd"); + $foxml->appendChild($root_element); + + // FOXML object properties section + $object_properties = $foxml->createElement("foxml:objectProperties"); + $state_property = $foxml->createElement("foxml:property"); + $state_property->setAttribute("NAME", "info:fedora/fedora-system:def/model#state"); + $state_property->setAttribute("VALUE", $state); + + $label_property = $foxml->createElement("foxml:property"); + $label_property->setAttribute("NAME", "info:fedora/fedora-system:def/model#label"); + $label_property->setAttribute("VALUE", $label); + + $owner_property = $foxml->createElement("foxml:property"); + $owner_property->setAttribute("NAME", "info:fedora/fedora-system:def/model#ownerId"); + $owner_property->setAttribute("VALUE", $owner ); + + $object_properties->appendChild($state_property); + $object_properties->appendChild($label_property); + $object_properties->appendChild($owner_property); + $root_element->appendChild($object_properties); + $foxml->appendChild($root_element); + + return $foxml; + } + + static function ingest_new_item($pid = '', $state = 'A', $label = '', $owner = '') { + return self::ingest_from_FOXML(self::create_object_FOXML( $pid, $state, $label, $owner)); + } + + static function fedora_item_exists($pid) { + $item = new Fedora_Item($pid); + return $item->exists(); + } + + /******************************************************** + * Relationship Functions + ********************************************************/ + + /** + * Returns an associative array of relationships that this item has + * in its RELS-EXT. + */ +} + diff --git a/api/fedora_utils.inc b/api/fedora_utils.inc new file mode 100644 index 00000000..1d7606b1 --- /dev/null +++ b/api/fedora_utils.inc @@ -0,0 +1,88 @@ +uid == 0) { + $fedora_user = 'anonymous'; + $fedora_pass = 'anonymous'; + } + else { + $fedora_user = $user->name; + $fedora_pass = $user->pass; + } + + if (function_exists("curl_init")) { + $ch = curl_init(); + $user_agent = "Mozilla/4.0 pp(compatible; MSIE 5.01; Windows NT 5.0)"; + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); + curl_setopt($ch, CURLOPT_FAILONERROR, TRUE); // Fail on errors + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // allow redirects + curl_setopt($ch, CURLOPT_TIMEOUT, 90); // times out after 90s + curl_setopt($ch, CURLOPT_USERAGENT, $user_agent); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, $return_to_variable); // return into a variable + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_USERPWD, "$fedora_user:$fedora_pass"); + //curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + if ($number_of_post_vars>0&&$post) { + curl_setopt($ch, CURLOPT_POST, $number_of_post_vars); + curl_setopt($ch, CURLOPT_POSTFIELDS, "$post"); + } + return curl_exec($ch); + } + else { + if (function_exists(drupal_set_message)) { + drupal_set_message(t('No curl support.'), 'error'); + } + return NULL; + } +} + +function fedora_available() { + $ret = do_curl(variable_get('fedora_soap_url', 'http://localhost:8080/fedora/services/management?wsdl'), 1); + // A bit of a hack but the SOAP parser will cause a fatal error if you give it the wrong URL. + return (strpos($ret, 'wsdl:definitions') != FALSE); +} + +/** + * Returns a UTF-8-encoded transcripiton of the string given in $in_str. + * @param string $in_str + * @return string A UTF-8 encoded string. + */ +function fix_encoding($in_str) { + $cur_encoding = mb_detect_encoding($in_str) ; + if ($cur_encoding == "UTF-8" && mb_check_encoding($in_str, "UTF-8")) { + return $in_str; + } + else { + return utf8_encode($in_str); + } +} + + function validPid($pid) { + $valid = FALSE; + if (strlen(trim($pid)) <= 64 && preg_match('/^([A-Za-z0-9]|-|\.)+:(([A-Za-z0-9])|-|\.|~|_|(%[0-9A-F]{2}))+$/', trim($pid))) { + $valid = TRUE; + } + + return $valid; + } + + function validDsid($dsid) { + $valid = FALSE; + if (strlen(trim($dsid)) <= 64 && preg_match('/^[a-zA-Z0-9\_\-\.]+$/', trim($dsid))) { + $valid = TRUE; + } + + return $valid; + } diff --git a/api/rels-ext.inc b/api/rels-ext.inc new file mode 100644 index 00000000..06aa4cd5 --- /dev/null +++ b/api/rels-ext.inc @@ -0,0 +1,43 @@ +get_datastream_dissemination('RELS-EXT'); + + } + + function modified() { + return !(empty(array_diff($this->relsExtArray, $this->originalRelsExtArray)) && + empty(array_diff($this->originalRelsExtArray, $this->relsExtArray))); + } + + /** + * Save the current state of the RELS-EXT array out to the repository item + * as a datastream. + */ + function save() { + + } +} + diff --git a/api/tagging.inc b/api/tagging.inc new file mode 100644 index 00000000..4156d7d4 --- /dev/null +++ b/api/tagging.inc @@ -0,0 +1,69 @@ +item = $item; + $this->load(); + } + } + + function load() { + $tagsxml = isset($this->item->datastreams[$this->tagsDSID])? $this->item->get_datastream_dissemination($this->tagsDSID) : NULL; + if (empty($tagsxml)) { + $this->tags = array(); + return FALSE; + } + $tagsdoc = new DOMDocument(); + $tagsdoc->loadXML($tagsxml); + $tags = $tagsdoc->getElementsByTagName('tag'); + foreach ($tags as $tag) { + $this->tags[] = array( + 'name' => $tag->nodeValue, + 'creator' => $tag->getAttribute('creator') + ); + } + } + + /** + * Saves an associative array of tags to a datastream. + */ + function save() { + $tagdoc = new DomDocument(); + $e_tags = new DomElement('tags'); + $tagdoc->appendChild($e_tags); + foreach ($this->tags as $tag) { + $e_tag = $tagdoc->createElement('tag', $tag['name']); + $e_tag->setAttribute('creator', (!empty($tag['creator'])) ? $tag['creator'] : ''); + $e_tags->appendChild($e_tag); + } + try { + $datastreams = $this->item->get_datastreams_list_as_array(); + if (empty($datastreams[$this->tagsDSID])) { + $this->item->add_datastream_from_string($tagdoc->saveXML(), $this->tagsDSID, 'Tags', 'text/xml', 'X'); + } + else { + $this->item->modify_datastream_by_value($tagdoc->saveXML(), $this->tagsDSID, 'Tags', 'text/xml', 'X'); + } + } + catch (exception $e) { + drupal_set_message('There was an error saving the tags datastream: !e', array('!e' => $e), 'error'); + return FALSE; + } + return TRUE; + } +} diff --git a/collection_policies/COLLECTION-COLLECTION POLICY.xml b/collection_policies/COLLECTION-COLLECTION POLICY.xml new file mode 100644 index 00000000..5888fd35 --- /dev/null +++ b/collection_policies/COLLECTION-COLLECTION POLICY.xml @@ -0,0 +1,21 @@ + + + + + + dc.title + dc.creator + dc.description + dc.date + dc.identifier + dc.language + dc.publisher + dc.rights + dc.subject + dc.relation + dcterms.temporal + dcterms.spatial + Full Text + + isMemberOfCollection + diff --git a/collection_policies/FLV-COLLECTION POLICY.xml b/collection_policies/FLV-COLLECTION POLICY.xml new file mode 100644 index 00000000..e7ea27a8 --- /dev/null +++ b/collection_policies/FLV-COLLECTION POLICY.xml @@ -0,0 +1,67 @@ + + + + + + vre:test + vre:contentmodel + STANDARD_FLV + + + + + dc.description + + dc.title + dc.title + + + dc.creator + dc.creator + + + dc.description + dc.description + + + dc.date + dc.date + + + dc.identifier + dc.identifier + + + dc.language + dc.language + + + dc.publisher + dc.publisher + + + dc.rights + dc.rights + + + dc.subject + dc.subject + + + dc.relation + dc.relation + + + dcterms.temporal + dcterms.temporal + + + dcterms.spatial + dcterms.spatial + + + fgs.DS.first.text + Full Text + + + \ No newline at end of file diff --git a/collection_policies/Image-COLLECTION POLICY.xml b/collection_policies/Image-COLLECTION POLICY.xml new file mode 100644 index 00000000..ec374632 --- /dev/null +++ b/collection_policies/Image-COLLECTION POLICY.xml @@ -0,0 +1,70 @@ + + + + + + vre:spdf + vre:contentmodel + STANDARD_IMAGE + + + + isMemberOfCollection + + + dc.description + + dc.title + dc.title + + + dc.creator + dc.creator + + + dc.description + dc.description + + + dc.date + dc.date + + + dc.identifier + dc.identifier + + + dc.language + dc.language + + + dc.publisher + dc.publisher + + + dc.rights + dc.rights + + + dc.subject + dc.subject + + + dc.relation + dc.relation + + + dcterms.temporal + dcterms.temporal + + + dcterms.spatial + dcterms.spatial + + + fgs.DS.first.text + Full Text + + + diff --git a/collection_policies/JPG-COLLECTION POLICY.xml b/collection_policies/JPG-COLLECTION POLICY.xml new file mode 100644 index 00000000..98f3520b --- /dev/null +++ b/collection_policies/JPG-COLLECTION POLICY.xml @@ -0,0 +1,70 @@ + + + + + + demo:Smiley + demo:DualResImage + ISLANDORACM + + + + isMemberOf + + + dc.description + + dc.title + dc.title + + + dc.creator + dc.creator + + + dc.description + dc.description + + + dc.date + dc.date + + + dc.identifier + dc.identifier + + + dc.language + dc.language + + + dc.publisher + dc.publisher + + + dc.rights + dc.rights + + + dc.subject + dc.subject + + + dc.relation + dc.relation + + + dcterms.temporal + dcterms.temporal + + + dcterms.spatial + dcterms.spatial + + + fgs.DS.first.text + Full Text + + + diff --git a/collection_policies/PDF-COLLECTION POLICY.xml b/collection_policies/PDF-COLLECTION POLICY.xml new file mode 100644 index 00000000..3f86bf16 --- /dev/null +++ b/collection_policies/PDF-COLLECTION POLICY.xml @@ -0,0 +1 @@ + dc.title dc.creator dc.description dc.date dc.identifier dc.language dc.publisher dc.rights dc.subject dc.relation dcterms.temporal dcterms.spatial Full Text isMemberOfCollection \ No newline at end of file diff --git a/collection_policies/PERSONAL-COLLECTION-POLICY.xml b/collection_policies/PERSONAL-COLLECTION-POLICY.xml new file mode 100644 index 00000000..be51e65b --- /dev/null +++ b/collection_policies/PERSONAL-COLLECTION-POLICY.xml @@ -0,0 +1,79 @@ + + + + + ir:ref + vre:contentmodel + REFWORKS + + + ir:ref + vre:contentmodel + STANDARD_PDF + + + ir:image + vre:contentmodel + STANDARD_IMAGE + + + + isMemberOfCollection + + + dc.description + + dc.title + dc.title + + + dc.creator + dc.creator + + + dc.description + dc.description + + + dc.date + dc.date + + + dc.identifier + dc.identifier + + + dc.language + dc.language + + + dc.publisher + dc.publisher + + + dc.rights + dc.rights + + + dc.subject + dc.subject + + + dc.relation + dc.relation + + + dcterms.temporal + dcterms.temporal + + + dcterms.spatial + dcterms.spatial + + + fgs.DS.first.text + Full Text + + + diff --git a/collection_policies/README.txt b/collection_policies/README.txt new file mode 100644 index 00000000..59165662 --- /dev/null +++ b/collection_policies/README.txt @@ -0,0 +1,7 @@ +This folder holds sample collection policy xml files. + +These are not used by the module directly from this location but should +be added as datatreams to objects with a content model property of Collection or +Community. The datastream id should be COLLECTION_POLICY. + +PERSONAL-COLLECTION-POLCIY is referenced from code do not remove \ No newline at end of file diff --git a/collection_policies/REFWORKS_COLLECTION_POLICY.xml b/collection_policies/REFWORKS_COLLECTION_POLICY.xml new file mode 100644 index 00000000..3bf8014e --- /dev/null +++ b/collection_policies/REFWORKS_COLLECTION_POLICY.xml @@ -0,0 +1,11 @@ + + + + + + ir:ref + islandora:refworksCModel + ISLANDORACM + + + diff --git a/collection_policies/RIRI COLLECTION POLICY.xml b/collection_policies/RIRI COLLECTION POLICY.xml new file mode 100644 index 00000000..8424f9e9 --- /dev/null +++ b/collection_policies/RIRI COLLECTION POLICY.xml @@ -0,0 +1,67 @@ + + + + + + vre:riri- + vre:contentmodel + STANDARD_PDF + + + + + dc.description + + dc.title + dc.title + + + dc.creator + dc.creator + + + dc.description + dc.description + + + dc.date + dc.date + + + dc.identifier + dc.identifier + + + dc.language + dc.language + + + dc.publisher + dc.publisher + + + dc.rights + dc.rights + + + dc.subject + dc.subject + + + dc.relation + dc.relation + + + dcterms.temporal + dcterms.temporal + + + dcterms.spatial + dcterms.spatial + + + fgs.DS.first.text + Full Text + + + diff --git a/collection_policies/book_collection_policy.xml b/collection_policies/book_collection_policy.xml new file mode 100644 index 00000000..4a6c4a0f --- /dev/null +++ b/collection_policies/book_collection_policy.xml @@ -0,0 +1,69 @@ + + + + + islandora + ilives:bookCModel + ISLANDORACM + + + + isMemberOfCollection + + + dc.description + + dc.title + dc.title + + + dc.creator + dc.creator + + + dc.description + dc.description + + + dc.date + dc.date + + + dc.identifier + dc.identifier + + + dc.language + dc.language + + + dc.publisher + dc.publisher + + + dc.rights + dc.rights + + + dc.subject + dc.subject + + + dc.relation + dc.relation + + + dcterms.temporal + dcterms.temporal + + + dcterms.spatial + dcterms.spatial + + + fgs.DS.first.text + Full Text + + + diff --git a/collection_policies/large_image_collection_policy.xml b/collection_policies/large_image_collection_policy.xml new file mode 100644 index 00000000..4f9793a6 --- /dev/null +++ b/collection_policies/large_image_collection_policy.xml @@ -0,0 +1,84 @@ + + + + + islandora:slide + islandora:slideCModel + ISLANDORACM + + + islandora:map + islandora:mapCModel + ISLANDORACM + + + islandora:herb + islandora:herbCModel + ISLANDORACM + + + + isMemberOfCollection + + dc.description + + dc.title + dc.title + + + dc.creator + dc.creator + + + dc.description + dc.description + + + dc.date + dc.date + + + dc.identifier + dc.identifier + + + dc.language + dc.language + + + dc.publisher + dc.publisher + + + dc.rights + dc.rights + + + dc.subject + dc.subject + + + dc.relation + dc.relation + + + dcterms.temporal + dcterms.temporal + + + dcterms.spatial + dcterms.spatial + + + fgs.DS.first.text + Full Text + + + diff --git a/collection_policies/slide_collection_policy.xml b/collection_policies/slide_collection_policy.xml new file mode 100644 index 00000000..7cc375b9 --- /dev/null +++ b/collection_policies/slide_collection_policy.xml @@ -0,0 +1,76 @@ + + + + + + + islandora:slide + islandora:slideCModel + ISLANDORACM + + + + isMemberOfCollection + + dc.description + + dc.title + dc.title + + + dc.creator + dc.creator + + + dc.description + dc.description + + + dc.date + dc.date + + + dc.identifier + dc.identifier + + + dc.language + dc.language + + + dc.publisher + dc.publisher + + + dc.rights + dc.rights + + + dc.subject + dc.subject + + + dc.relation + dc.relation + + + dcterms.temporal + dcterms.temporal + + + dcterms.spatial + dcterms.spatial + + + fgs.DS.first.text + Full Text + + + diff --git a/collection_policy.xsd b/collection_policy.xsd new file mode 100644 index 00000000..8822f103 --- /dev/null +++ b/collection_policy.xsd @@ -0,0 +1,47 @@ + + + + Islandora Collection Policy Schema + Islandora, Robertson Library, University of Prince Edward Island, 550 University Ave., Charlottetown, Prince Edward Island + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/collection_views/COLLECTION_VIEW.xml b/collection_views/COLLECTION_VIEW.xml new file mode 100644 index 00000000..7b30307d --- /dev/null +++ b/collection_views/COLLECTION_VIEW.xml @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + /fedora/repository/ + /-/ + + + + /fedora/repository/ + / + / + + + + + + + + + /fedora/repository/ + /TN + + +
+ + + + + +
-- + + + /fedora/repository/ + /-/ + + + DETAILS + -- +
+ + + + + + +
+ + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/collection_views/Collection_QUERY.txt b/collection_views/Collection_QUERY.txt new file mode 100644 index 00000000..840d0331 --- /dev/null +++ b/collection_views/Collection_QUERY.txt @@ -0,0 +1,7 @@ +select $object $title from <#ri> + where ($object $title + and $object $content + and $object + and $object ) + + order by $title \ No newline at end of file diff --git a/collection_views/Coverflow_Collection_View.xsl b/collection_views/Coverflow_Collection_View.xsl new file mode 100644 index 00000000..1e67f79f --- /dev/null +++ b/collection_views/Coverflow_Collection_View.xsl @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + +
Testing YUI's CoverFlow version 0.1 (beta)
+
+ + +
+
+ + diff --git a/collection_views/Coverflow_PRE_Collection_View.xsl b/collection_views/Coverflow_PRE_Collection_View.xsl new file mode 100644 index 00000000..e9bae87d --- /dev/null +++ b/collection_views/Coverflow_PRE_Collection_View.xsl @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + +
Testing YUI's CoverFlow version 0.1 (beta)
+
+ + +
+
+ + diff --git a/collection_views/FLV-COLLECTION VIEW(2).xml b/collection_views/FLV-COLLECTION VIEW(2).xml new file mode 100644 index 00000000..e591de97 --- /dev/null +++ b/collection_views/FLV-COLLECTION VIEW(2).xml @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +
+
+ + + + + + + + + + + + + + + + + + + + + /fedora/repository//-/ + + + /fedora/repository//OBJ/ + + + + + + + /fedora/repository//-/ + + + /fedora/repository//TN + + + +
+ + + /fedora/repository//-/ + + + + + + + + + + +
+ + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/collection_views/README.txt b/collection_views/README.txt new file mode 100644 index 00000000..ce9df576 --- /dev/null +++ b/collection_views/README.txt @@ -0,0 +1,11 @@ +This folder holds xslt files that can be used to transform the +display of a collection. These files are not used by the module from +this location but should be added as datastreams to objects that have a +content model of Collection. + +The datastream id should be COLLECTION_VIEW + +NOTE: If you add a invalid xslt to a as a collection view you will +no longer have access to that object or collection. You may have to +fire up the fedora-admin utility and move or modify the Collection_View datastream. This +is a bug but not sure when it will be fixed. \ No newline at end of file diff --git a/collection_views/REFWORKS-COLLECTION_VIEW.xml b/collection_views/REFWORKS-COLLECTION_VIEW.xml new file mode 100644 index 00000000..26d637e4 --- /dev/null +++ b/collection_views/REFWORKS-COLLECTION_VIEW.xml @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + /fedora/repository//-/ + + + /fedora/repository//OBJ/ + + + + + + + +
+ + /fedora/repository//-/ + + + + + + + + + + +
+ + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/collection_views/SIMPLE-COLLECTION_VIEW.xml b/collection_views/SIMPLE-COLLECTION_VIEW.xml new file mode 100644 index 00000000..a01e66e0 --- /dev/null +++ b/collection_views/SIMPLE-COLLECTION_VIEW.xml @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + /fedora/repository//-/ + + + /fedora/repository//OBJ/ + + + + + + + + + + /fedora/repository//TN + + + +
+ +
+ + /fedora/repository//-/ + + + + + + + + + + +
+ + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/collection_views/SmileyStuff-COLLECTION_VIEW.xml b/collection_views/SmileyStuff-COLLECTION_VIEW.xml new file mode 100644 index 00000000..58e28287 --- /dev/null +++ b/collection_views/SmileyStuff-COLLECTION_VIEW.xml @@ -0,0 +1,67 @@ + + + + + + + + +
+ +

+ +
+ + + +

+
+
+
+
+ + + + + + + + +
+
+ + + + /fedora/repository/ + + /FULL_SIZE + + + + + /fedora/repository/ + + /MEDIUM_SIZE + + +
+ ( Full Size ) +
+
+
+ + + + + /fedora/repository/ + /-/ + + + + +
+ +
+
+
+
diff --git a/collection_views/default-sparqltoHtml.xsl b/collection_views/default-sparqltoHtml.xsl new file mode 100644 index 00000000..2f88acb5 --- /dev/null +++ b/collection_views/default-sparqltoHtml.xsl @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + /fedora/repository//TN + + + + + /fedora/repository//-/ + + + +
+ +
+
+
\ No newline at end of file diff --git a/collection_views/simple_list_view.xml b/collection_views/simple_list_view.xml new file mode 100644 index 00000000..e8711651 --- /dev/null +++ b/collection_views/simple_list_view.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + diff --git a/collection_views/yui_coverflow/css/test.css b/collection_views/yui_coverflow/css/test.css new file mode 100644 index 00000000..c6f215a7 --- /dev/null +++ b/collection_views/yui_coverflow/css/test.css @@ -0,0 +1,18 @@ +body { + font-family: Arial; + font-size: 12px; + color: gray; + background: black; +} + +.title { + font-size: 18px; + font-weight: bold; +} + +#coverFlowTest .coverFlowLabel { + margin-top: 10px; + font-size: 14px; + color: #C0C0C0; + font-weight: bold; +} \ No newline at end of file diff --git a/collection_views/yui_coverflow/js/CoverFlow.js b/collection_views/yui_coverflow/js/CoverFlow.js new file mode 100644 index 00000000..3b041592 --- /dev/null +++ b/collection_views/yui_coverflow/js/CoverFlow.js @@ -0,0 +1,936 @@ +/** + * @author elmasse (Maximiliano Fierro) + * @version 0.1 beta + * + * @usage: + * + * var images = [ + * {src: 'images/ardillitaMac.jpg'}, + * {src: 'http://farm2.static.flickr.com/1380/1426855399_b4b8eccbdb.jpg?v=0'}, + * {src: 'http://farm1.static.flickr.com/69/213130158_0d1aa23576_d.jpg'} + * ]; + * var myCoverFlow = new YAHOO.ext.CoverFlow('coverFlowTest', {height: 200, width: 800, images: images, bgColor: '#C0C0C0'}); + * + * + */ + + +YAHOO.namespace("ext"); + +//(function(){ + + /** + * @class CoverFlow + * @namespace YAHOO.ext + * @constructor + * @param el {String|HTMLElement} Reference to element where CoverFlow will be rendered. + * @param config {Object} configuration object + * config.height {Number} Element height. Optional. Default: CoverFlow.DEFAULT_HEIGHT. + * config.width {Number} Element width. Optional. Default: CoverFlow.DEFAULT_WIDTH. + * config.images {Array} Array of Images. [{src:}] + * config.bgColor {String} Background color. Could be in the form #00000 or black. Optional. Default: CoverFlow.DEFAULT_BG_COLOR. + * + */ + YAHOO.ext.CoverFlow = function(el, userConfig){ + if(el) + this.init(el, userConfig || {}); + }; + + //shortcuts + var CoverFlow = YAHOO.ext.CoverFlow; + var Dom = YAHOO.util.Dom; + + + /** + * Defaults + */ + CoverFlow.DEFAULT_HEIGHT = 300; + CoverFlow.DEFAULT_WIDTH = 800; + CoverFlow.DEFAULT_BG_COLOR = '#000000'; + CoverFlow.IMAGE_SEPARATION = 50; + CoverFlow.RIGHT = 'right'; + CoverFlow.LEFT = 'left'; + CoverFlow.LABEL_CLASS = 'coverFlowLabel'; + + CoverFlow.prototype = { + //Images array (it's a sort of transient var) + images: [], + //Items array {CoverFlowItem[]} + coverFlowItems: [], + + remainingImages: 9999, + + element: null, + labelElement: null, + containerHeight: 0, + containerWidth: 0, + + imageHeightRatio: 0.6, + imageWidthRatio: 0.2, + reflectionRatio: 0.6, // this causes: imageTotalHeightRatio = imageHeightRatio + imageHeightRatio*reflectionRatio + topRatio: 0.1, + sideRatio: 0.4, + + perspectiveAngle: 20, + imageZIndex: 1000, + selectedImageZIndex: 9999, + selectedItem: 0, + + moveQueue: [], + animationWorking: false, + + init: function(el, userConfig){ + + this.element = Dom.get(el); + this.applyConfig(userConfig); + + if(userConfig.images) + this.addImages(userConfig.images); + + this.attachEventListeners(); + this.createLabelElement(); + }, + + applyConfig: function(config){ + this.containerHeight = config.height || CoverFlow.DEFAULT_HEIGHT; + this.containerWidth = config.width || CoverFlow.DEFAULT_WIDTH; + this.backgroundColor = config.bgColor || CoverFlow.DEFAULT_BG_COLOR; + + this.element.style.position = 'relative'; + this.element.style.height = this.containerHeight + 'px'; + this.element.style.width = this.containerWidth + 'px'; + this.element.style.background = this.backgroundColor; + this.element.style.overflow = 'hidden'; + }, + + addImages: function(images){ + this.images = []; + this.remainingImages = images.length; + + for(var i=0; i < images.length; i++){ + var img = images[i]; + var image = new Image(); + image.id = Dom.generateId(); + image.index = i; + image.onclick = img.onclick; + image.label = img.label; + + //hide images + image.style.visibility = 'hidden'; + image.style.display = 'none'; + //this is to maintain image order since image.onload will be called randomly + this.element.appendChild(image); + //a shortcut to not create another context to call onload + var me = this; +// image.onload = function(){ +// CoverFlow.preloadImage(me, this); // this = image +// }; + YAHOO.util.Event.on(image, 'load', this.preloadImage, image, this); + image.src = img.src; + + }; + + }, + + /** + * @function preloadImage + * @param event + * @param image + * @return void + */ + preloadImage : function(e, image){ + this.images.push(image); + this.checkAllImagesLoaded(); + }, + + checkAllImagesLoaded: function(){ + this.remainingImages--; + if(!this.remainingImages){ + this.setup(); + } + }, + + setup: function(){ + this.createCoverFlowItems(); + this.sortCoverFlowItems(); + this.initCoverFlow(); + }, + + initCoverFlow: function(){ + + for(var i=0; i < this.coverFlowItems.length; i++){ + var coverFlowItem = this.coverFlowItems[i]; + + var angle = 0; + var direction; + + if(i==0){ + coverFlowItem.setZIndex(this.selectedImageZIndex); + coverFlowItem.setLeft(this.getCenter() - coverFlowItem.element.width/2); + coverFlowItem.isSelected(true); + this.selectedItem = 0; + this.showLabel(coverFlowItem.getLabel()); + }else{ + angle = this.perspectiveAngle; + direction = CoverFlow.LEFT; + coverFlowItem.setZIndex(this.imageZIndex - i); + coverFlowItem.setLeft( this.getRightStart()+ (i - 1)* CoverFlow.IMAGE_SEPARATION); + coverFlowItem.isSelected(false); + } + coverFlowItem.setAngle(angle); + coverFlowItem.drawInPerspective(direction); + } + }, + + createLabelElement: function(){ + var label = document.createElement('div'); + label.id = Dom.generateId(); + label.style.position = 'absolute'; + label.style.top = this.getFooterOffset() + 'px'; + label.innerHTML = ' '; + label.style.textAlign = 'center'; + label.style.width = this.containerWidth + 'px'; + label.style.zIndex = this.selectedImageZIndex + 10; + label.className = CoverFlow.LABEL_CLASS; + this.labelElement = this.element.appendChild(label); + }, + + showLabel: function(text){ + if(text) + this.labelElement.innerHTML = text; + else + this.labelElement.innerHTML = ''; + }, + + attachEventListeners: function(){ + new YAHOO.util.KeyListener(this.element, { keys:39 }, + { fn:this.selectNext, + scope:this, + correctScope:true } ).enable(); + + new YAHOO.util.KeyListener(this.element, { keys:37 }, + { fn:this.selectPrevious, + scope:this, + correctScope:true } ).enable(); + + + }, + + select: function(e,coverFlowItem){ + var distance = this.selectedItem - coverFlowItem.index; + if(distance < 0){ + for(var i=0; i < -distance; i++) + this.selectNext(); + }else{ + for(var i=0; i < distance; i++) + this.selectPrevious(); + } + }, + + + selectNext: function(){ + if(this.animationWorking){ + this.moveQueue.push('moveLeft'); + return; + } + + var animateItems = []; + + for(var i=0; i < this.coverFlowItems.length; i++){ + var coverFlowItem = this.coverFlowItems[i]; + var isLast = (this.selectedItem == this.coverFlowItems.length -1); + if(!isLast){ + var distance = i-this.selectedItem; + + if(distance == 0){// selected + coverFlowItem.setZIndex(this.imageZIndex); + coverFlowItem.isSelected(false); + animateItems.push({item: coverFlowItem, attribute:{angle: {start: 0, end: this.perspectiveAngle} } }); + + coverFlowItem = this.coverFlowItems[++i]; + coverFlowItem.isSelected(true); + this.showLabel(coverFlowItem.getLabel()); + animateItems.push({item: coverFlowItem, attribute:{angle: {start: this.perspectiveAngle, end: 0} } }); + + }else{ + animateItems.push({item: coverFlowItem, attribute: {left: {start:coverFlowItem.getLeft(), end: coverFlowItem.getLeft() - CoverFlow.IMAGE_SEPARATION} }}); + } + } + } + + var animation = new CoverFlowAnimation({ + direction: CoverFlow.LEFT, + center: this.getCenter(), + startLeftPos: this.getLeftStart(), + startRightPos: this.getRightStart() + }, + animateItems, 0.5); + + animation.onStart.subscribe(this.handleAnimationWorking, this); + animation.onComplete.subscribe(this.handleQueuedMove, this); + + animation.animate(); + + if(this.selectedItem + 1 < this.coverFlowItems.length) + this.selectedItem++; + }, + + selectPrevious: function(){ + if(this.animationWorking){ + this.moveQueue.push('moveRight'); + return; + } + + var animateItems = []; + + for(var i=0; i < this.coverFlowItems.length; i++){ + var coverFlowItem = this.coverFlowItems[i]; + var isFirst = (this.selectedItem == 0); + var distance = i-this.selectedItem; + if(!isFirst){ + if(distance == - 1){ + coverFlowItem.setZIndex(this.selectedImageZIndex); + coverFlowItem.isSelected(true); + this.showLabel(coverFlowItem.getLabel()); + animateItems.push({item: coverFlowItem, attribute: {angle: {start: this.perspectiveAngle, end: 0}}}); + + coverFlowItem = this.coverFlowItems[++i]; + coverFlowItem.isSelected(false); + coverFlowItem.setZIndex(this.imageZIndex); + animateItems.push({item: coverFlowItem, attribute: {angle: {start: 0, end: this.perspectiveAngle}}}); + }else{ + coverFlowItem.setZIndex(coverFlowItem.getZIndex() - 1); + animateItems.push({item: coverFlowItem, attribute: {left: {start:coverFlowItem.getLeft(), end: coverFlowItem.getLeft() + CoverFlow.IMAGE_SEPARATION} }}); + } + } + } + var animation = new CoverFlowAnimation({ + direction: CoverFlow.RIGHT, + center: this.getCenter(), + startLeftPos: this.getLeftStart(), + startRightPos: this.getRightStart() + }, + animateItems, 0.5); + + animation.onStart.subscribe(this.handleAnimationWorking, this); + animation.onComplete.subscribe(this.handleQueuedMove, this); + + animation.animate(); + + if(this.selectedItem > 0) + this.selectedItem--; + }, + + handleAnimationWorking: function(a, b, cf){ + cf.animationWorking = true; + }, + + handleQueuedMove: function(msg, data, cf){ + cf.animationWorking = false; + + var next = cf.moveQueue.pop(); + if(next == 'moveLeft') + cf.selectNext(); + if(next == 'moveRight') + cf.selectPrevious(); + }, + + getCenter: function(){ + return this.containerWidth / 2; + }, + + getRightStart: function() { + return this.containerWidth - this.sideRatio * this.containerWidth; + }, + + getLeftStart: function() { + return this.sideRatio * this.containerWidth; + }, + + sortCoverFlowItems: function(){ + function sortFunction(aCoverFlowItem, bCoverFlowItem){ + return aCoverFlowItem.index - bCoverFlowItem.index; + } + + this.coverFlowItems.sort(sortFunction); + }, + + createCoverFlowItems: function(){ + this.coverFlowItems = []; + for(var i=0; i this.getMaxImageHeight() && image.width <= this.getMaxImageWidth()){ + height = ((image.height / this.getMaxImageHeight())) * image.height; + } + if(image.height <= this.getMaxImageHeight() && image.width > this.getMaxImageWidth()){ + height = ((image.width / this.getMaxImageWidth())) * image.height; + } + if(image.height > this.getMaxImageHeight() && image.width > this.getMaxImageWidth()){ + if(image.height > image.width) + height = ((this.getMaxImageHeight() / image.height)) * image.height; + else + height = ((this.getMaxImageWidth() / image.width)) * image.height; + } + return height; + }, + + scaleWidth: function(image){ + var width = 0; + if(image.height <= this.getMaxImageHeight() && image.width <= this.getMaxImageWidth()){ + width = image.width; + } + if(image.height > this.getMaxImageHeight() && image.width <= this.getMaxImageWidth()){ + width = ((image.height / this.getMaxImageHeight())) * image.width; + } + if(image.height <= this.getMaxImageHeight() && image.width > this.getMaxImageWidth()){ + width = ((image.width / this.getMaxImageWidth())) * image.width; + } + if(image.height > this.getMaxImageHeight() && image.width > this.getMaxImageWidth()){ + if(image.height > image.width) + width = ((this.getMaxImageHeight() / image.height)) * image.width; + else + width = ((this.getMaxImageWidth() / image.width)) * image.width; + } + return width; + }, + + + getMaxImageHeight: function(){ + return (this.containerHeight * this.imageHeightRatio); + }, + + getMaxImageWidth: function(){ + return (this.containerWidth * this.imageWidthRatio); + }, + + getTopOffset: function(){ + return this.containerHeight * this.topRatio; + }, + + getFooterOffset: function(){ + return this.containerHeight * (this.topRatio + this.imageHeightRatio); + } + }; + + + /** + * @class CoverFlowItem + * + */ + CoverFlowItem = function(image, config){ + if(image) + this.init(image, config); + }; + + CoverFlowItem.prototype = { + canvas: null, + element: null, + index: null, + id: null, + angle: 0, + selected: false, + onclickFn: null, + selectedOnclickFn: null, + label: null, + + onSelected: null, + + init: function(image, config){ + var scaledWidth = config.scaledWidth; + var scaledHeight = config.scaledHeight; + var reflectionRatio = config.reflectionRatio; + var bgColor = config.bgColor; + + this.id = image.id; + this.index = image.index; + this.onclickFn = config.onclick; + this.selectedOnclickFn = image.onclick; + this.label = image.label; + var parent = image.parentNode; + this.canvas = this.createImageCanvas(image,scaledWidth,scaledHeight,reflectionRatio, bgColor); + this.element = this.canvas.cloneNode(false); + this.element.id = this.id; + parent.replaceChild(this.element, image); + + this.onSelected = new YAHOO.util.CustomEvent('onSelected', this); + this.onSelected.subscribe(this.handleOnclick); + + }, + + getLabel: function(){ + return this.label; + }, + + handleOnclick: function(){ + YAHOO.util.Event.removeListener(this.element, 'click'); + if(!this.selected){ + YAHOO.util.Event.addListener(this.element, 'click', this.onclickFn.fn, this, this.onclickFn.scope); + }else{ + if(this.selectedOnclickFn && this.selectedOnclickFn.fn) + YAHOO.util.Event.addListener(this.element, 'click', this.selectedOnclickFn.fn, this, this.selectedOnclickFn.scope); + else + YAHOO.util.Event.addListener(this.element, 'click', this.selectedOnclickFn); + } + }, + + isSelected: function(selected){ + this.selected = selected; + this.onSelected.fire(); + }, + + setAngle: function(angle){ + this.angle = angle; + }, + + getAngle: function(){ + return this.angle; + }, + + setTop: function(top){ + this.element.style.top = top + 'px'; + }, + + setLeft: function(left){ + this.element.style.left = left + 'px'; + }, + + getLeft: function(){ + var ret = this.element.style.left; + return new Number(ret.replace("px", "")); + }, + + getZIndex: function(){ + return this.element.style.zIndex; + }, + + setZIndex: function(zIndex){ + this.element.style.zIndex = zIndex; + }, + + createImageCanvas: function(image, sWidth, sHeight, reflectionRatio, bgColor){ + + var imageCanvas = document.createElement('canvas'); + + if(imageCanvas.getContext){ + + var scaledWidth = sWidth; + var scaledHeight = sHeight; + var reflectionHeight = scaledHeight * reflectionRatio; + + imageCanvas.height = scaledHeight + reflectionHeight; + imageCanvas.width = scaledWidth; + + var ctx = imageCanvas.getContext('2d'); + + ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height); + ctx.globalCompositeOperation = 'source-over'; + ctx.fillStyle = 'rgba(0, 0, 0, 1)'; + ctx.fillRect(0, 0, imageCanvas.width, imageCanvas.height); + + //draw the reflection image + ctx.save(); + ctx.translate(0, (2*scaledHeight)); + ctx.scale(1, -1); + ctx.drawImage(image, 0, 0, scaledWidth, scaledHeight); + ctx.restore(); + //create the gradient effect + ctx.save(); + ctx.translate(0, scaledHeight); + ctx.globalCompositeOperation = 'destination-out'; + var grad = ctx.createLinearGradient( 0, 0, 0, scaledHeight); + grad.addColorStop(1, 'rgba(0, 0, 0, 1)'); + grad.addColorStop(0, 'rgba(0, 0, 0, 0.75)'); + ctx.fillStyle = grad; + ctx.fillRect(0, 0, scaledWidth, scaledHeight); + //apply the background color to the gradient + ctx.globalCompositeOperation = 'destination-over'; + ctx.fillStyle = bgColor; '#000'; + ctx.globalAlpha = 0.8; + ctx.fillRect(0, 0 , scaledWidth, scaledHeight); + ctx.restore(); + //draw the image + ctx.save(); + ctx.translate(0, 0); + ctx.globalCompositeOperation = 'source-over'; + ctx.drawImage(image, 0, 0, scaledWidth, scaledHeight); + ctx.restore(); + + return imageCanvas; + } + }, + + drawInPerspective: function(direction, frameSize){ + var canvas = this.element; + var image = this.canvas; + var angle = Math.ceil(this.angle); + var ctx; + var originalWidth = image.width; + var originalHeight = image.height; + var destinationWidth = destinationWidth || originalWidth; // for future use + var destinationHeight = destinationHeight || originalHeight; // for future use + + var perspectiveCanvas = document.createElement('canvas'); + perspectiveCanvas.height = destinationHeight; + perspectiveCanvas.width = destinationWidth; + var perspectiveCtx = perspectiveCanvas.getContext('2d'); + + var alpha = angle * Math.PI/180; // Math uses radian + + if(alpha > 0){ // if we have an angle greater than 0 then apply the perspective + var right = (direction == CoverFlow.RIGHT); + + var initialX=0, finalX=0, initialY=0, finalY=0; + + frameSize = frameSize || 1; + var xDes, yDes; + var heightDes, widthDes; + var perspectiveWidht = destinationWidth; + + var frameFactor = frameSize / originalWidth; + var frames = Math.floor(originalWidth / frameSize); + + var widthSrc = frameSize ; + var heightSrc = originalHeight; + + for(var i=0; i < frames; i++){ + var xSrc = (i) * frameSize; + var ySrc = 0; + var betaTan = 0; + width = destinationWidth * (i) * frameFactor; + horizon = destinationHeight / 2; + + if(right){ + betaTan = horizon/((Math.tan(alpha)*horizon) + width); + xDes = (betaTan*width)/(Math.tan(alpha) + betaTan); + yDes = Math.tan(alpha) * xDes; + + if(i == frames -1){ + finalX=xDes; + finalY=yDes; + } + }else{ + betaTan = horizon/((Math.tan(alpha)*horizon) +(destinationWidth-width)); + xDes = (Math.tan(alpha)*(destinationWidth) + (betaTan * width))/(Math.tan(alpha) + betaTan); + yDes = -Math.tan(alpha)*xDes + (Math.tan(alpha)*(destinationWidth)); + + if(i == 0){ + initialX = xDes; + initialY = yDes; + finalX = destinationWidth; + finalY = 0; + } + } + + heightDes = destinationHeight - (2*yDes); + widthDes = heightDes / destinationHeight * destinationWidth; + + perspectiveCtx.drawImage(image, xSrc, ySrc, widthSrc, heightSrc, xDes, yDes, widthDes, heightDes); + + } + + perspectiveWidth = finalX - initialX; + originalCanvasWidth = destinationWidth; + canvas.width = perspectiveWidth; + + ctx = canvas.getContext('2d'); + + //remove exceeded pixels + ctx.beginPath(); + if(right){ + ctx.moveTo(0, 0); + ctx.lineTo(finalX, finalY); + ctx.lineTo(finalX, finalY + (destinationHeight - 2*finalY)); + ctx.lineTo(0, destinationHeight); + ctx.lineTo(0,0); + }else{ + var initialX1 = initialX - (originalCanvasWidth - perspectiveWidth); + var finalX1 = finalX - (originalCanvasWidth - perspectiveWidth); + ctx.moveTo(0, initialY); + ctx.lineTo(finalX1, finalY); + ctx.lineTo(finalX1, destinationHeight); + ctx.lineTo(initialX1, initialY + (destinationHeight - 2*initialY)); + ctx.lineTo(0, initialY); + } + ctx.closePath(); + ctx.clip(); + + ctx.drawImage(perspectiveCanvas, initialX, 0, perspectiveWidth, destinationHeight, 0, 0, perspectiveWidth, destinationHeight); + + }else{ + + canvas.width = perspectiveCanvas.width; + canvas.height = perspectiveCanvas.height; + perspectiveCtx.drawImage(image, 0, 0, originalWidth, originalHeight, 0, 0, destinationWidth, destinationHeight); + ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(perspectiveCanvas, 0, 0); + } + } + + }; + + /** + * @class CoverFlowAnimation + * @requires YAHOO.util.AnimMgr + */ + CoverFlowAnimation = function(config, animationItems, duration){ + this.init(config, animationItems, duration); + }; + + CoverFlowAnimation.prototype = { + direction: null, + + center: null, + + startLeftPos: null, + + startRightPos: null, + + animationItems: null, + + method : YAHOO.util.Easing.easeNone, + + animated: false, + + startTime: null, + + actualFrames : 0, + + useSeconds : true, // default to seconds + + currentFrame : 0, + + totalFrames : YAHOO.util.AnimMgr.fps, + + init: function(config, animationItems, duration){ + this.direction = config.direction; + this.center = config.center; + this.startLeftPos = config.startLeftPos; + this.startRightPos = config.startRightPos; + this.animationItems = animationItems; + this.duration = duration || 1; + this.registerEvents(); + }, + + registerEvents: function(){ + /** + * Custom event that fires after onStart, useful in subclassing + * @private + */ + this._onStart = new YAHOO.util.CustomEvent('_start', this, true); + + /** + * Custom event that fires when animation begins + * Listen via subscribe method (e.g. myAnim.onStart.subscribe(someFunction) + * @event onStart + */ + this.onStart = new YAHOO.util.CustomEvent('start', this); + + /** + * Custom event that fires between each frame + * Listen via subscribe method (e.g. myAnim.onTween.subscribe(someFunction) + * @event onTween + */ + this.onTween = new YAHOO.util.CustomEvent('tween', this); + + /** + * Custom event that fires after onTween + * @private + */ + this._onTween = new YAHOO.util.CustomEvent('_tween', this, true); + + /** + * Custom event that fires when animation ends + * Listen via subscribe method (e.g. myAnim.onComplete.subscribe(someFunction) + * @event onComplete + */ + this.onComplete = new YAHOO.util.CustomEvent('complete', this); + /** + * Custom event that fires after onComplete + * @private + */ + this._onComplete = new YAHOO.util.CustomEvent('_complete', this, true); + + this._onStart.subscribe(this.doOnStart); + this._onTween.subscribe(this.doOnTween); + this._onComplete.subscribe(this.doOnComplete); + + }, + + isAnimated : function() { + return this.animated; + }, + + getStartTime : function() { + return this.startTime; + }, + + doMethod: function(start, end) { + return this.method(this.currentFrame, start, end - start, this.totalFrames); + }, + + animate : function() { + if ( this.isAnimated() ) { + return false; + } + + this.currentFrame = 0; + + this.totalFrames = ( this.useSeconds ) ? Math.ceil(YAHOO.util.AnimMgr.fps * this.duration) : this.duration; + + if (this.duration === 0 && this.useSeconds) { // jump to last frame if zero second duration + this.totalFrames = 1; + } + YAHOO.util.AnimMgr.registerElement(this); + return true; + }, + + stop : function(finish) { + if (!this.isAnimated()) { // nothing to stop + return false; + } + + if (finish) { + this.currentFrame = this.totalFrames; + this._onTween.fire(); + } + YAHOO.util.AnimMgr.stop(this); + }, + + doOnStart : function() { + this.onStart.fire(); + + this.runtimeItems = []; + for (var i=0; i 0){ + runtimeItem.attribute[attr].perspectiveDirection = this.direction; + runtimeItem.attribute[attr].center = true; + }else{ + runtimeItem.attribute[attr].perspectiveDirection = this.direction == CoverFlow.RIGHT ? CoverFlow.LEFT : CoverFlow.RIGHT; + runtimeItem.attribute[attr].center = false; + } + } + } + this.runtimeItems.push(runtimeItem); + }, + + setItemAttributes: function(item){ + + for(var attr in item.attribute){ + + var value = Math.ceil(this.doMethod(item.attribute[attr].start, item.attribute[attr].end)); + + if(attr == 'angle'){ + item.item.setAngle(value); + var frameSize = Math.ceil(this.doMethod(3, 1)); + item.item.drawInPerspective(item.attribute[attr].perspectiveDirection, frameSize); + var left; + if(item.attribute[attr].center){ + left = this.doMethod(item.item.getLeft(), this.center - item.item.element.width/2); + }else{ + if(this.direction == CoverFlow.LEFT) + left = this.doMethod(item.item.getLeft(), this.startLeftPos - item.item.element.width); + else + left = this.doMethod(item.item.getLeft(), this.startRightPos); + } + item.item.setLeft(Math.ceil(left)); + + }else{ + item.item.setLeft(value); + } + } + } + }; + +//}); \ No newline at end of file diff --git a/collection_views/yui_coverflow/js/test.js b/collection_views/yui_coverflow/js/test.js new file mode 100644 index 00000000..7ba4d637 --- /dev/null +++ b/collection_views/yui_coverflow/js/test.js @@ -0,0 +1,37 @@ + + + +YAHOO.util.Event.onDOMReady(function(){ + + var images = [ + {src: 'images/ardillitaMac.jpg', label: 'Ardileta!', onclick: function(){alert('image1');}}, + {src: 'http://farm2.static.flickr.com/1380/1426855399_b4b8eccbdb.jpg?v=0'}, + {src: 'http://farm1.static.flickr.com/69/213130158_0d1aa23576_d.jpg'}, + {src: 'http://farm1.static.flickr.com/69/213130158_0d1aa23576_d.jpg'}, + {src: 'images/msn2.jpg', label: 'My Mac'}, + {src: 'images/msn2.jpg', label: 'My Mac again...'} + + ]; + var myCoverFlow = new YAHOO.ext.CoverFlow('coverFlowTest', {height: 200, width: 600, images: images}); + + function moveLeft(e, coverFlow){ + coverFlow.selectNext(); + } + function moveRight(e, coverFlow){ + coverFlow.selectPrevious(); + } + var myMoveLeftBtn = new YAHOO.widget.Button('moveLeftButton', {onclick: {fn: moveLeft, obj: myCoverFlow}}); + var myMoveRightBtn = new YAHOO.widget.Button('moveRightButton', {onclick: {fn: moveRight, obj: myCoverFlow}}); + + + var otherImages = [ + {src: 'images/ardillitaMac.jpg', label: 'Ardileta!', onclick: function(){alert('image1');}}, + {src: 'images/msn2.jpg', label: 'My Mac'}, + {src: 'images/msn2.jpg', label: 'My Mac again...'} + + ]; + var anotherCoverFlow = new YAHOO.ext.CoverFlow('anotherCoverFlowTest', {height: 150, width: 500, images: otherImages, bgColor: '#C0C0C0'}); + + + +}); \ No newline at end of file diff --git a/content_modeller/.buildpath b/content_modeller/.buildpath new file mode 100644 index 00000000..606f236d --- /dev/null +++ b/content_modeller/.buildpath @@ -0,0 +1,5 @@ + + + + + diff --git a/content_modeller/.project b/content_modeller/.project new file mode 100644 index 00000000..ddc88b18 --- /dev/null +++ b/content_modeller/.project @@ -0,0 +1,28 @@ + + + Islandora Content Modeler + + + + + + org.eclipse.wst.jsdt.core.javascriptValidator + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.dltk.core.scriptbuilder + + + + + + org.eclipse.php.core.PHPNature + org.eclipse.wst.jsdt.core.jsNature + + diff --git a/content_modeller/.settings/.jsdtscope b/content_modeller/.settings/.jsdtscope new file mode 100644 index 00000000..3c58e917 --- /dev/null +++ b/content_modeller/.settings/.jsdtscope @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/content_modeller/.settings/org.eclipse.php.core.prefs b/content_modeller/.settings/org.eclipse.php.core.prefs new file mode 100644 index 00000000..6ebb55f7 --- /dev/null +++ b/content_modeller/.settings/org.eclipse.php.core.prefs @@ -0,0 +1,3 @@ +#Mon May 17 18:04:49 CDT 2010 +eclipse.preferences.version=1 +include_path=0;/islandora_content_modeler diff --git a/content_modeller/.settings/org.eclipse.wst.jsdt.ui.superType.container b/content_modeller/.settings/org.eclipse.wst.jsdt.ui.superType.container new file mode 100644 index 00000000..3bd5d0a4 --- /dev/null +++ b/content_modeller/.settings/org.eclipse.wst.jsdt.ui.superType.container @@ -0,0 +1 @@ +org.eclipse.wst.jsdt.launching.baseBrowserLibrary \ No newline at end of file diff --git a/content_modeller/.settings/org.eclipse.wst.jsdt.ui.superType.name b/content_modeller/.settings/org.eclipse.wst.jsdt.ui.superType.name new file mode 100644 index 00000000..05bd71b6 --- /dev/null +++ b/content_modeller/.settings/org.eclipse.wst.jsdt.ui.superType.name @@ -0,0 +1 @@ +Window \ No newline at end of file diff --git a/content_modeller/css/content_modeller.css b/content_modeller/css/content_modeller.css new file mode 100644 index 00000000..e4834b83 --- /dev/null +++ b/content_modeller/css/content_modeller.css @@ -0,0 +1,33 @@ +.contentModeller .collection +{ + width: 400px; + float: left; +} + +.contentModeller .model +{ + width: 475px; + float: right; +} + +.contentModeller img +{ + width: 16px; + padding: 0px; + vertical-align: middle; + cursor: hand; +} + +.contentModeller .ajaxForm +{ + margin-top: 25px; + padding: 10px; + width:300px; + border: 1px dashed black; + display: none; +} + +.contentModeller #ajaxBusy +{ + display: none; +} diff --git a/content_modeller/css/jquery.jnotify.css b/content_modeller/css/jquery.jnotify.css new file mode 100644 index 00000000..e3ab53aa --- /dev/null +++ b/content_modeller/css/jquery.jnotify.css @@ -0,0 +1,16 @@ +.jnotify-item +{ + height: auto; + padding: 4px 4px 4px 4px; + margin: 0 0 5px 0; + display: block; + position: relative; +} + +.jnotify-item-close +{ + float: right; + margin-left: 2px; +} + + diff --git a/content_modeller/css/jquery.treeview.css b/content_modeller/css/jquery.treeview.css new file mode 100644 index 00000000..94de11f8 --- /dev/null +++ b/content_modeller/css/jquery.treeview.css @@ -0,0 +1,68 @@ +.treeview, .treeview ul { + padding: 0; + margin: 0; + list-style: none; +} + +.treeview ul { + + margin-top: 4px; +} + +.treeview .hitarea { + background: url(../images/treeview-default.gif) -64px -25px no-repeat; + height: 16px; + width: 16px; + margin-left: -16px; + float: left; + cursor: pointer; +} +/* fix for IE6 */ +* html .hitarea { + display: inline; + float:none; +} + +.treeview li { + margin: 0; + padding: 3px 0pt 3px 16px; +} + +.treeview a.selected { + background-color: #eee; +} + +#treecontrol { margin: 1em 0; display: none; } + +.treeview .hover { color: red; cursor: pointer; } + +.treeview li { background: url(../images/treeview-default-line.gif) 0 0 no-repeat; } +.treeview li.collapsable, .treeview li.expandable { background-position: 0 -176px; } + +.treeview .expandable-hitarea { background-position: -80px -3px; } + +.treeview li.last { background-position: 0 -1766px } +.treeview li.lastCollapsable, .treeview li.lastExpandable { background-image: url(../images/treeview-default.gif); } +.treeview li.lastCollapsable { background-position: 0 -111px } +.treeview li.lastExpandable { background-position: -32px -67px } + +.treeview div.lastCollapsable-hitarea, .treeview div.lastExpandable-hitarea { background-position: 0; } + +.treeview-red li { background-image: url(../images/treeview-red-line.gif); } +.treeview-red .hitarea, .treeview-red li.lastCollapsable, .treeview-red li.lastExpandable { background-image: url(../images/treeview-red.gif); } + +.treeview-black li { background-image: url(../images/treeview-black-line.gif); } +.treeview-black .hitarea, .treeview-black li.lastCollapsable, .treeview-black li.lastExpandable { background-image: url(../images/treeview-black.gif); } + +.treeview-gray li { background-image: url(../images/treeview-gray-line.gif); } +.treeview-gray .hitarea, .treeview-gray li.lastCollapsable, .treeview-gray li.lastExpandable { background-image: url(../images/treeview-gray.gif); } + +.treeview-famfamfam li { background-image: url(../images/treeview-famfamfam-line.gif); } +.treeview-famfamfam .hitarea, .treeview-famfamfam li.lastCollapsable, .treeview-famfamfam li.lastExpandable { background-image: url(../images/treeview-famfamfam.gif); } + + +.filetree li { padding: 3px 0 2px 16px; } +.filetree span.folder, .filetree span.file { padding: 1px 0 1px 20px; } +.filetree span.folder { background: url(../images/folder.gif) 0 0 no-repeat; } +.filetree li.expandable span.folder { background: url(../images/folder-closed.gif) 0 0 no-repeat; } +.filetree span.file { background: url(../images/file.gif) 0 0 no-repeat; } diff --git a/content_modeller/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png b/content_modeller/css/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 0000000000000000000000000000000000000000..5b5dab2ab7b1c50dea9cfe73dc5a269a92d2d4b4 GIT binary patch literal 180 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FscKIb$B>N1x91EQ4=4yQ7#`R^ z$vje}bP0l+XkK DSH>_4 literal 0 HcmV?d00001 diff --git a/content_modeller/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png b/content_modeller/css/smoothness/images/ui-bg_flat_75_ffffff_40x100.png new file mode 100644 index 0000000000000000000000000000000000000000..ac8b229af950c29356abf64a6c4aa894575445f0 GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0vp^8bF-F!3HG1q!d*FsY*{5$B>N1x91EQ4=4yQYz+E8 zPo9&<{J;c_6SHRil>2s{Zw^OT)6@jj2u|u!(plXsM>LJD`vD!n;OXk;vd$@?2>^GI BH@yG= literal 0 HcmV?d00001 diff --git a/content_modeller/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png b/content_modeller/css/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 0000000000000000000000000000000000000000..ad3d6346e00f246102f72f2e026ed0491988b394 GIT binary patch literal 120 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnour0hLi978O6-<~(*I$*%ybaDOn z{W;e!B}_MSUQoPXhYd^Y6RUoS1yepnPx`2Kz)7OXQG!!=-jY=F+d2OOy?#DnJ32>z UEim$g7SJdLPgg&ebxsLQ09~*s;{X5v literal 0 HcmV?d00001 diff --git a/content_modeller/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png b/content_modeller/css/smoothness/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 0000000000000000000000000000000000000000..42ccba269b6e91bef12ad0fa18be651b5ef0ee68 GIT binary patch literal 105 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnouqzpV=978O6-=0?FV^9z|eBtf= z|7WztIJ;WT>{+tN>ySr~=F{k$>;_x^_y?afmf9pRKH0)6?eSP?3s5hEr>mdKI;Vst E0O;M1& literal 0 HcmV?d00001 diff --git a/content_modeller/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png b/content_modeller/css/smoothness/images/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 0000000000000000000000000000000000000000..5a46b47cb16631068aee9e0bd61269fc4e95e5cd GIT binary patch literal 111 zcmeAS@N?(olHy`uVBq!ia0vp^j6gJjgAK^akKnouq|7{B978O6lPf+wIa#m9#>Unb zm^4K~wN3Zq+uP{vDV26o)#~38k_!`W=^oo1w6ixmPC4R1b Tyd6G3lNdZ*{an^LB{Ts5`idse literal 0 HcmV?d00001 diff --git a/content_modeller/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/content_modeller/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 0000000000000000000000000000000000000000..7c9fa6c6edcfcdd3e5b77e6f547b719e6fc66e30 GIT binary patch literal 101 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 literal 0 HcmV?d00001 diff --git a/content_modeller/css/smoothness/images/ui-icons_2e83ff_256x240.png b/content_modeller/css/smoothness/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..09d1cdc856c292c4ab6dd818c7543ac0828bd616 GIT binary patch literal 4369 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 literal 0 HcmV?d00001 diff --git a/content_modeller/css/smoothness/images/ui-icons_454545_256x240.png b/content_modeller/css/smoothness/images/ui-icons_454545_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..59bd45b907c4fd965697774ce8c5fc6b2fd9c105 GIT binary patch literal 4369 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 oBo%DI*Kv;w;*%(i9W@f3_WCF#rGn literal 0 HcmV?d00001 diff --git a/content_modeller/css/smoothness/images/ui-icons_cd0a0a_256x240.png b/content_modeller/css/smoothness/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 0000000000000000000000000000000000000000..2ab019b73ec11a485fa09378f3a0e155194f6a5d GIT binary patch literal 4369 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

iK!JW;kWGJzJebcm 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 D@CR4= literal 0 HcmV?d00001 diff --git a/content_modeller/images/edit.gif b/content_modeller/images/edit.gif new file mode 100644 index 0000000000000000000000000000000000000000..0222e1dc295fa7d5875753ed6d5436b9a57cf1c8 GIT binary patch literal 364 zcmZ?wbhEHb6krfwxXJ(md-m*UYim2QfFUg{ZRX6GXJ)3od-pCo-0bDOGp{$=E@%$x zk7RoPY`2e-+M(55*REaLzJ2@4moLwpIdlE+lDE%}mX?;DUzNFT-MZViZ~uI8tfQ-S zacgK#PtUcr*+-5XdH?$Oxn&uPTEYvWEf=(epI=+}|NnomwbUULf3h&LGpI4>fQ$qA ziGi)tVRC_o4xcph5~G@ycA$zfcICq0nMRl#Hq6&|+b5bFXC|kYQq%QVKVKHU?dRZo>$pu0@ vd>lNi0ur!0m5Y~9sd L;uWgGz+epk&R;19 literal 0 HcmV?d00001 diff --git a/content_modeller/images/folder-closed.gif b/content_modeller/images/folder-closed.gif new file mode 100644 index 0000000000000000000000000000000000000000..541107888e6739a7b367ad3d208e1efa933f877d GIT binary patch literal 105 zcmZ?wbhEHb6ky4lrdiO0bk3ZcRLy6E?4f Hg~1vCyv-@{ literal 0 HcmV?d00001 diff --git a/content_modeller/images/folder.gif b/content_modeller/images/folder.gif new file mode 100644 index 0000000000000000000000000000000000000000..2b31631ca2bfec3a8afb1bfdd4f8ed4c5bcc3a18 GIT binary patch literal 106 zcmZ?wbhEHb6ky=hKW2GJ7 I#Kd3?0MGgBcQg?+9^7hOr*1vvd z(ed+Dn|E~Y*}JE)(`xtr$qQD7tzEmeXR62A?J?iKf8Vfq#=M0sdyXY--W&Dk+5JPu zr%az+b@|3zZ$I0kC$?_dGS@G_H@6`B+O?}Q76$A-lDTd7vfe2%%h#v3PjFhbDf$2Z z{|yZdi5XhO)kfiQYT;2~{(*i=*LhBv>%V=^gp1c_uUOkWXPH-JlljtBL7R4`xVbxT z-PODQ$mDCc=XX!B+rE9flatkztH+O?o_6x|j#-O>BBR5DLIN2E2q^w!VPt2pX3zoI z4~i28_R|g4P0cOVHa0CG4Bb85dK^LCYS#Mv>|x;!M%;$n-qxx{l41<(qQ4iPK;KZ z{_-B?3{AK1ikLo76c<+(e11#jm8qANr+BEd>}LiUg#{8GO)Y)OCVB!tpQk8vakBDX zl3;vvzpc^Kh|Qwuu~T2`3#mmOj!FlPDa*~Aqs_R$p?kWJicjQ5CKf?u2Cp{_7gwk+ L7Pe+$WUvMRm(ABW literal 0 HcmV?d00001 diff --git a/content_modeller/images/red_info.png b/content_modeller/images/red_info.png new file mode 100644 index 0000000000000000000000000000000000000000..017b5945a19fe1c0702a47e06cc5a5955904d11f GIT binary patch literal 29159 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 literal 0 HcmV?d00001 diff --git a/content_modeller/images/treeview-gray-line.gif b/content_modeller/images/treeview-gray-line.gif new file mode 100644 index 0000000000000000000000000000000000000000..37600447dc002bbc0dd16e1391b711360aae2e34 GIT binary patch literal 1877 zcmeH``8OK|0LL@bF~qSc#n50?JXNI+k>{#)#go3O{g`&sTA06+?`JRv{%1VB*=kSggPTmbZ?E2@Hm10RaqGJr~M_swEK9(fuU z9bXMur@nY#Xn3Qp;v6X2AT> zA>EM+>y@m1^a49s_E1X&+bZH^!_a=e{N6Su)s&U>sDnd!~=b?JFEB#FT8 zioZMFaK52~t&52oqdWW~2MJ1ue%%?en#qCfU7u!nC|sSJi5E`}X3h8)ly{+5*|mp+ zYqNE`8MkZN-)k};oze-j%*tB&sNuZif|=0wM%!=l0sXF(V)Zg+3_ zjbW;EBEX2ytHG=$Dx2Ha{HE%5V3a1hHnd2R32m%R*9#gFWf^4tEW&~!6yIiV)*e*l zireg~b4-8KL1b9Tn<4TQc-aUN7>=bBxE!0Aep)ES$dA>bZ zlac_>GSf$}Amq|xpG2f_X<$)PU0E=BExZ&}FuYP8&bwD%fgJCOsK5%!A}jIpO{>+( zGV75wBp|A>JOyOlNY1dq-7SE-P1iq3bVQx6%Yk<{*5}84F?-+_`Yq}S#U}_=SCzX? zX`+&GsCqhDsvFh7AWWM#ck=(Rps}W(-f3)-hf$w$i>58wymeug)RAsnbE^=j9L-?f zpi-Z);Tuuyqj4L}9YcZxTIWO&p4K%*X3?0-?f4ef8jtm%XgkyEBd6Ds8uOK?%`{)>0D(Cr`A4IcZoG`&U18YKqjoYtzQo|Dt=**7zk597IbK9sJXFyhNi zwtW@gRcSW?gGKMW@kxrlG#OY-do~qJ*-V&3Js8`Y3GYwom_@$iB+g=ml}U5>1u1QR zAzAiA(jrL>&74Uwwqm}`xJ1AT;O_GsONn5#!*ULyhq;oEIkp%2g#mF(6lf@Jxhn4i zV~t87;8y4{2KZG5ao+KL=SUTJlQq*4|E@_PjJ3`!o(GG)^}?JsMpy*)mQYPCf1;Sk`8-A8at@Xm#Gb>oe(v%3b{156)aykj?nu&|IbZfh z!lZx$?iVd}BHwuNSkmco3iwy5Drr$xV-lid6PqT(u95$N>8c#Jm2RMN_SB1WJe9Oq z(x$uU@TQLQX@QVr>U~Sa;vEmbZ_QL7odp^*-BsqVXSqmf)oqYES3tl*66F0YO=#d1 rlgk$~puxAlhuJ^ZHqgioD1o1DVtQEUit~KllU0$5nh)jV0N{TCC3Zqn literal 0 HcmV?d00001 diff --git a/content_modeller/images/treeview-gray.gif b/content_modeller/images/treeview-gray.gif new file mode 100644 index 0000000000000000000000000000000000000000..cfb8a2f0961b880d9ecc9e59f831d0c0bf9d06db GIT binary patch literal 1230 zcmZ?wbhEHbOkikb_|5F}1vNN-#bFE}n2rob1X65+pkEe*DZwLSXy)sc-G6LrLE|#j%ny@ltx#{|>*k3`b zudj+f+$8$@s#fHh^zF++cYjOsT#=i)PgZ*SBbQFk<=6g7TzYe_x%fYqoR5WW_>5Nf zsIRkcJbHA@c(c>D3XzqU)=k#8J9nngIK5|{y|vw$2$RE?mT4~XtDRLEe|<_~c?_?$ zLGJZ!!S{W4%zp58Z=>w%X})`Qe0&`0sXRUALwVLJuj#KvHA1S2+ow&BYJYdfr2O^1 zna!)!@5=7^`T51=)#j2iwbhrWRX*39_q(R{^Y??#w#k`8!n#b?Ah=gT4^b_6Je!p-pqnUi6)T{Kw4)JLb3tD)cb}Z;R&ALM=NKo^J zQum3S7LQAK7iB!^v-vhdvhxz-&N&k%mqtvOXdV2 literal 0 HcmV?d00001 diff --git a/content_modeller/images/treeview-red-line.gif b/content_modeller/images/treeview-red-line.gif new file mode 100644 index 0000000000000000000000000000000000000000..df9e749a8f1f58862588efc64988003efad01d51 GIT binary patch literal 1877 zcmeH``BxGK0LPh{$5cGF%CuRcF0W^1+Eb91Y0har@ns%QJPI-bCps}CP-u#GY96he zH{=x}8cR_?6wMSBHp}~@d8B5Ec_ztT`#0A2hwu02&*#0br`JzT&Np-c8>+j2FWJW9 z|J$$i)xiJGfXTkfHvoVFVDp9i%O?Qp3V=f6d5==7Sk4KMl>Ucik`YxtvJ<)$%#TPKR% zdeBegW3Nr-TZ$_DvX!7#=EQu`)|{{_NO4?Hnuz--`I}N3UjF&+?uwLk+=>GJd{B_Tc2OKbjN9H> z96NPSF%oo|+^NT@plDh-RsN>!ws$&N>eV+-RSK`GN-+xQlcpP|{VXMbZmGY?Sg+bE z&yqDeRb-m~Xn;($Rkc9os0lNWR0x7V&viYtFw425?`)O{#Xnxi^Z-rV%z`xuPf1K3U`ignx^9LiY{9RF`M1 z((4&CBBq9gRTxIqvdNQH4XvU-teM=&r#I{BRpE@Mf`UowW*aYDTj; zR~d}wJj7bmv%$EvhL%3@95+YnP@jZFjkyaK?LZqem&Cp}E@#J}7%>obDszfKXbSed{!N zqe%A5h1G9<<|OWgt(pyPNUi%mt_OllWQ$!DHV#9VzJogSLla83`aMi%;zcml$vXr7 zf<%YcL08HgN8s@2tufz(==0;jh0Ny@q4f1T-P5MdE9;FzHSA-Pm3Wn^KX5K-#%-h+YndE*c}A#} z97|nymmXX*xOhw~p_=<#*Rp;q1c;i_)TkyvQ;xgVtY5mTyh_&|ZEzU`%%?)%UDt;N sUpBjNE)^Df<9oQ%i{r+6SwTgJWAz*lYeQMK?>m|{T3!EviV6VoFXq=mv;Y7A literal 0 HcmV?d00001 diff --git a/content_modeller/images/treeview-red.gif b/content_modeller/images/treeview-red.gif new file mode 100644 index 0000000000000000000000000000000000000000..3bbb3a157f1568d59d6db02b55a3b3fe9a1ff7a7 GIT binary patch literal 1230 zcmZ?wbhEHbOkikb_|5q9rFMYXq;dkzBCUd%AXV z7f->2WoKqf=UU0E5MF-1&C2oHA5RfQ-wyu&du5`wWCYCfT`X0jHDP7Qa?|x$vA=>= zUtbk}xJmT)RjtT1>D!lu?*5kMxgs}ppRDxuM=qV7%dh>Fxb)^;bMb#JIUft%@ENV{ zQD0}@c=YI)@n)xQ6(TDyt(&ZGckWD~aeB`@duzKh5hjN(Ez?}&S39dT{`!=}@)%xg zgWT)eg75q8nEl}G-bUHi(|q^r`1m-~Q+ayKhw`jdUejNTYJ^l3w@;fM)&A~|N%`x2 zGn-ed-<93-^Ye?#tIZ{4YO60#t9-6I?{`h@=kEuf$II8+R(@HSYaUSgyf$2+OgK}0 zZsRjUi+L5dHILtK5edt9=qI|b{eIzIMl<7PRm>?O4!tnstX#kf7!b zrS20uEgqNfF3Nb+XY*}_WalNuopUBkE{&Kl(L5+ZuF(6`HI*Fyu5b4nE$kweHiYk+ hA)Dse=Ki!azG~*w!`ELg-+K7}|Nm#soax(iqpPR= z=97<$*X>?<;QqC1SG(7oU$*Dc%*_|JZr}3w@#EgUo)_=F@4x(f{n7h#cHLaNcGbFF zC(hq}H+94LmX@Zw&p$2NabfA&r7dgE9lieQ-m~v}F1+Yjdu_qK`)l`Jo4EPf<`Z|; zZe7bj8BqMm!pP3R&Y%M_2IMCOw)O)(Ngg_0d?%WfPWtH0ZEq~m3N@O}+)`$}!D(e* zUqnrBmZL0h%3a4A_Qb=7l@8fh39E=*Ol+|c6_w_ck*x}3mX+t_mXi}xV`>Wvl2zxG z=9ZRLnbbbDh>?|5TCKI+qefmsoKZqBgX#)$(WM8dhSrMV#dwqQl?KyTz KL`2q+!5RPy>y20d literal 0 HcmV?d00001 diff --git a/content_modeller/islandora_content_modeller.info b/content_modeller/islandora_content_modeller.info new file mode 100644 index 00000000..1df4ff16 --- /dev/null +++ b/content_modeller/islandora_content_modeller.info @@ -0,0 +1,7 @@ +; $Id$ +name = Islandora Content Modeller +dependencies[] = fedora_repository +description = Allows you to manage and build content models for Islandora/Fedora. +package = Fedora Repository +version = 6.1dev +core = 6.x diff --git a/content_modeller/islandora_content_modeller.module b/content_modeller/islandora_content_modeller.module new file mode 100644 index 00000000..8d64e84e --- /dev/null +++ b/content_modeller/islandora_content_modeller.module @@ -0,0 +1,4376 @@ + 'Islandora Content Modeller', + 'description' => 'Manage Islandora/Fedora Content Models', + 'page callback' => 'islandora_content_modeller_main', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_NORMAL_ITEM + ); + + $items['admin/content/modeller/ajax/listModels'] = array( + 'page callback' => 'icm_ajax_model_list', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK + ); + + $items['admin/content/modeller/ajax/getFiles'] = array( + 'page callback' => 'icm_ajax_pluginFiles', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK + ); + + $items['admin/content/modeller/ajax/getClasses'] = array( + 'page callback' => 'icm_ajax_getClasses', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK + ); + + $items['admin/content/modeller/ajax/getMethods'] = array( + 'page callback' => 'icm_ajax_getMethods', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK + ); + + $items['admin/content/modeller/ajax/button'] = array( + 'page callback' => 'icm_ajax_button', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK + ); + + $items['admin/content/modeller/ajax/processForm/%'] = array( + 'page callback' => 'icm_ajax_processForm', + 'page arguments' => array(5), + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK + ); + + $items['admin/content/modeller/ajax/model'] = array( + 'page callback' => 'icm_ajax_model_tree', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK + ); + + $items['admin/content/modeller/ajax/collection'] = array( + 'page callback' => 'icm_ajax_collection_tree', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK + ); + + return $items; +} + +function icm_get_modules() +{ + $files = module_rebuild_cache(); + + $options=array(''); + foreach ($files as $key=>$val) + { + if ($val->info['package'] == 'Fedora Repository') //only list islandora modules + { + $options[$key]=$key; + } + } + return $options; + +} + +function icm_ajax_pluginFiles() +{ + + echo ''; + $plugin_path = drupal_get_path('module',$_GET['module']); + + if (!$plugin_path) + { + exit(); + } + + $plugin_path.='/plugins'; + + $files = array(''); + + if ( is_dir($plugin_path) && ($dir = opendir($plugin_path)) !== false) + { + while (($file = readdir($dir)) !== false) + { + if (preg_match('/\.inc$/',$file)) + { + echo ''; + } + } + } + exit(); +} + +function icm_ajax_getClasses() +{ + echo ''; + $file = drupal_get_path('module',$_GET['module']); + if (!$file) + { + exit(); + } + + $file.= '/'. $_GET['file']; + + if (file_exists($file)) + { + // hack.. we should really enforce the class name being the same as the file name. + // either that or consider replacing with a plugin register call in each included plugin. + $before_classes = get_declared_classes(); + require_once $file; + $after_classes = get_declared_classes(); + + foreach ($after_classes as $class) + { + if (!in_array($class,$before_classes)) + { + echo ''; + } + } + + } + exit(); +} + +function icm_ajax_getMethods() +{ + echo ''; + $file = drupal_get_path('module',$_GET['module']); + if (!$file) + { + exit(); + } + + $file.= '/'. $_GET['file']; + $class = $_GET['className']; + + if (file_exists($file)) + { + require_once $file; + if (class_exists($class)) + { + $methods = get_class_methods($class); + + foreach ($methods as $method) + { + echo ''; + } + } + + } + exit(); +} + +function icm_ajax_model_list() +{ + global $base_url; + $moduleRoot=drupal_get_path('module','islandora_content_modeller'); + require_once($moduleRoot.'/treeview.inc'); + + $moduleRoot = $base_url.'/'.$moduleRoot; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + module_load_include('inc', 'fedora_repository', 'CollectionClass'); + + $modelTree = new treeview('Installed Content Models','folder',' ','models'); + + $collectionHelper = new CollectionClass(); + $items = new SimpleXMLElement( $collectionHelper->getRelatedItems(variable_get('fedora_content_model_collection_pid','islandora:ContentModelCollection') ,null,null)); + $modelCount = 0; + if (count($items->results->result) > 0) + { + + foreach ($items->results->result as $res) + { + $child_pid=substr($res->object['uri'],strpos($res->object['uri'],'/')+1); + + if (($cm = ContentModel::loadFromModel($child_pid))!==false) + { + $modelTree->addChild(''.$child_pid.'','file',' '); + $modelCount++; + } + } + } + if ($modelCount == 0) + { + $modelTree->addChild(t('No installed content models found.')); + } + + echo $modelTree->buildTree('filetree'); +} + +function icm_ajax_model_tree() +{ + global $base_url; + $ret =false; + $moduleRoot=drupal_get_path('module','islandora_content_modeller'); + require_once($moduleRoot.'/treeview.inc'); + $moduleRoot=$base_url.'/'.$moduleRoot; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + if (isset($_GET['model_pid'])) + { + $cm = ContentModel::loadFromModel(trim($_GET['model_pid'])); + } else + $cm = ContentModel::loadFromObject(variable_get('fedora_repository_pid', 'islandora:top')); + + if ($cm !== false) + { + $cmValid = $cm->validate(); + + $modelTree = new treeview($cm->pid. ($cmValid?'':'!'),'folder',' ','models'); + + if (!$cmValid) + { + // echo '

'.htmlentities($cm->dumpXml()).'
'; + $errorsEl = $modelTree->addChild('XML Errors','folder'); + + foreach (ContentModel::$errors as $err) + { + $errorsEl->addChild(''.$err.''); + } + + } else + { + $mimeTypesEl = $modelTree->addChild('Mimetypes','folder',' '); + $mimetypes=$cm->getMimeTypes(); + foreach ($mimetypes as $type) + { + $mimeTypesEl->addChild($type,'file', ' '); + } + + $datastreams = $cm->listDatastreams(); + $dstreamsEl = $modelTree->addChild('Datastreams','folder', ' '); + + if (count($datastreams) > 0) + { + foreach ($datastreams as $ds) + { + $dsEl = $dstreamsEl->addChild($ds . ($cm->displayInFieldSet($ds)? '(Display in Fieldset) ':''),'file', + ' '. + ' '); + + $dispMethods = $cm->getDisplayMethods($ds); + $dispMethEl = $dsEl->addChild('Display Methods','folder',' '); + if ($dispMethods !== false && count($dispMethods) > 0) + { + foreach ($dispMethods as $meth) + { + $dispMethEl->addChild(($meth['default']==true?'':'').$meth['file'].'
'.$meth['class'].'->'.$meth['method'],'file', + ' '. + ' '. + ($meth['default']==true?'
':'')); + } + } + + $addMethods = $cm->getAddDsMethod($ds); + $addMethodEl = $dsEl->addChild('Add Datastream Method', 'file',' '); + + + } + } + + $ingestRulesEl = $modelTree->addChild('Ingest Rules','folder', ' '); + $rules = $cm->getIngestRules(); + foreach ($rules as $id=>$rule) + { + $ruleEl = $ingestRulesEl->addChild('Rule '.$id,'folder', ' '); + $applToEl = $ruleEl->addChild('Applies To','folder', ' '); + foreach ($rule['applies_to'] as $type) + { + $applToEl->addChild($type,'file', ' '); + } + + $methodsEl=$ruleEl->addChild('Ingest Methods','folder', ' '); + foreach ($rule['ingest_methods'] as $method) + { + $methodEl = $methodsEl->addChild($method['file'].'
'.$method['class'].'->'.$method['method'],'file',' '); + $paramEl = $methodEl->addChild('Parameters','folder',''); + if (count($method['parameters']) > 0) + { + foreach ($method['parameters'] as $key=>$val) + { + $paramEl->addChild($key.' = '.$val,'file', ' '); + } + } + } + + } + + $attr = $cm->getIngestFormAttributes(); + $ingestFormEl = $modelTree->addChild('Ingest Form
(dsid: '.$attr['dsid'].' page: '.$attr['page'].' file_chooser: '.($attr['hide_file_chooser']?'hidden':'visible').' )','folder', + ' '); + + $builderMethod = $cm->getIngestFormBuilderMethod(); + $ingestFormEl->addChild($builderMethod['file'].'
Builder: '.$builderMethod['class'].'->'.$builderMethod['method'].'
Handler: '.$builderMethod['class'].'->'.$builderMethod['handler'],'file'); + + $elementsEl = $ingestFormEl->addChild('Form Elements','folder',' '); + + if (($elements = $cm->getIngestFormElements())!==false) + { + $j=0; + foreach ($elements as $element) + { + + $icons = ' '; + + if ($j > 0) + { + $icons.=' '; + } + + if ($j < count($elements) - 1) + { + $icons.=' '; + } + + $icons .= ' '; + + $elementEl = $elementsEl->addChild(''.$element['label'] .' ' . $element['name'].' ('.$element['type'].')','file',$icons); + $params = $cm->getIngestFormElementParams($element['name']); + $paramEl = $elementEl->addChild('Parameters','folder',''); + if (count($params) > 0) + { + foreach ($params as $key=>$val) + { + $paramEl->addChild($key.' = '.$val,'file', ' '); + } + } + + + if ($element['type'] == 'select' || $element['type'] == 'radio' || $element['type'] == 'other_select') + { + $authListEl = $elementEl->addChild(t('Authoritative List'),'folder',' '); + $i=0; + foreach ($element['authoritative_list'] as $value=>$label) + { + $icons = ' '; + if ($i > 0) + { + $icons .= ' '; + } + + if ($i < count($element['authoritative_list']) - 1) + { + $icons .= ' '; + } + $authListEl->addChild(''.$label.' '.$value,'file',$icons); + $i++; + } + } + + $j++; + } + } + + $addIcon = ''; + $metadataMethod = $cm->getEditMetadataMethod(); + if ($metadataMethod == false) + { + $addIcon = ' '; + } + $metaDataEl = $modelTree->addChild('Edit Metadata Method','folder',$addIcon); + + if ($metadataMethod !== false) + { + $icons = ' '; + $icons .= ' '; + $metaDataEl->addChild('DSID: '.$metadataMethod['dsid'].'
'.$metadataMethod['file'].'
Builder: '.$metadataMethod['class'].'->'.$metadataMethod['method'].'
Handler: '.$metadataMethod['class'].'->'.$metadataMethod['handler'],'file',$icons); + } + + + $modelTree->addChild('Services','folder',' '); + + } + + + $ret = $modelTree->buildTree('filetree'); + } + + if ($ret !== false) + { + echo $ret; + } else + echo t('Error: Unable to load content model'); + + exit(); +} + +function icm_ajax_collection_tree() +{ + global $base_url; + $ret =false; + $moduleRoot=drupal_get_path('module','islandora_content_modeller'); + require_once($moduleRoot.'/treeview.inc'); + $moduleRoot=$base_url.'/'.$moduleRoot; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + module_load_include('inc', 'fedora_repository', 'CollectionPolicy'); + module_load_include('inc', 'fedora_repository', 'CollectionClass'); + + $collectionHelper = new CollectionClass(); + + $pid=(!isset($_GET['collection_pid']) || strtolower($_GET['collection_pid']) == 'false')?variable_get('fedora_repository_pid', 'islandora:top'):trim($_GET['collection_pid']); + $cm=ContentModel::loadFromObject($pid); + $cp=CollectionPolicy::loadFromCollection($pid); + + $cssPid = $pid;//str_replace(':','_',$pid); + + if ($cm !== false && $cp !== false) + { + $cmValid = $cm->validate(); + $cpValid = $cp->validate(); + + $collectionTree = new treeview($pid .($cpValid?'':'!').' ('.$cm->pid. ($cmValid?'':'!').')','folder',' ','collections'); + + if (!$cmValid || !$cpValid) + { + // echo '
'.htmlentities($cm->dumpXml()).'
'; + $errorsEl = $collectionTree->addChild('XML Errors','folder'); + + foreach (ContentModel::$errors as $err) + { + $errorsEl->addChild(''.$err.''); + } + foreach (CollectionPolicy::$errors as $err) + { + $errorsEl->addChild(''.$err.''); + } + } else + { + + $childrenAllowed = false; + $contentModels = $cp->getContentModels(); + $cm_tree = $collectionTree->addChild('Allowed Content Models','folder',' ',$cssPid.'-cmodels'); + foreach ($contentModels as $ccm) + { + if ($ccm->pid == variable_get('fedora_collection_model_pid','islandora:collectionCModel') ) + { + $childrenAllowed = true; + } + + $cm_tree->addChild($ccm->name.' ('.$ccm->pid.')', 'file', ' '); + } + + $dstreams=$cm->listDatastreams(); + if (count($dstreams) > 0) + { + $ds_tree = $collectionTree->addChild('Datastreams','folder',null,$cssPid.'-dstreams'); + foreach ($dstreams as $ds) + $ds_tree->addChild($ds); + } + + $terms=$cp->getSearchTerms(true); + $search_tree = $collectionTree->addChild('Search Terms','folder',' ', $cssPid.'-terms'); + if (count($terms) > 0) + { + foreach ($terms as $term) + { + $search_tree->addChild(($term['default']?'':'').$term['value'].' ('.$term['field'].')'.($term['default']?'':''), 'file', ' Remove Set as Default '); + } + } + + if ($childrenAllowed) + { + $items = new SimpleXMLElement( $collectionHelper->getRelatedItems($pid,null,null)); + $child_tree= new TreeView('Child Collections','folder',' ',$cssPid.'-children'); + + if (count($items->results->result) > 0) + { + foreach ($items->results->result as $res) + { + $child_pid=substr($res->object['uri'],strpos($res->object['uri'],'/')+1); + $model_pid=substr($res->content['uri'] ,strpos($res->content['uri'],'/')+1); + + $child_cp=CollectionPolicy::loadFromCollection($child_pid); + if ($child_cp !== false) + { + $child_tree->addChild(''.$child_pid.' ('.$model_pid.')','file',' '); + } + } + } + $collectionTree->addChild($child_tree); + } + } + $ret = $collectionTree->buildTree('filetree'); + } + + if ($ret !== false) + { + echo $ret; + } else + echo t('Error: Missing content model and/or collection policy datastream(s) for '. $pid); + + exit(); +} + + +function islandora_content_modeller_main() +{ + global $base_url; + $moduleRoot=drupal_get_path('module','islandora_content_modeller'); + drupal_add_css($moduleRoot.'/css/jquery.treeview.css'); + drupal_add_css($moduleRoot.'/css/content_modeller.css'); + drupal_add_css($moduleRoot.'/css/jquery.jnotify.css'); + drupal_add_css($moduleRoot.'/css/smoothness/jquery-ui-1.8.1.custom.css'); + + drupal_add_js($moduleRoot.'/js/jquery.cookie.js'); + drupal_add_js($moduleRoot.'/js/jquery.treeview.min.js'); + drupal_add_js($moduleRoot.'/js/content_modeller.js'); + drupal_add_js($moduleRoot.'/js/jquery.jnotify.js'); + + $moduleRoot = $base_url.'/'.$moduleRoot; + + $content = '
'; + $content .= 'Refresh Loading
'; + $content .= '

'.t('Collections').'

'; + $content .= '
'; + $content .= '
'; + $content .= '

'.t('Models').'

'; + $content .= 'List Installed Models'; + $content .= '
'; + $content .= '
'; + $content .= '
'; + $content .= '
'; + $content .= '
'; + + return $content; +} + +function icm_ajax_formExists($formName) +{ + $validForms = array('icm_collection_new','icm_collection_edit','icm_collection_purge','icm_model_new','icm_model_purge','icm_model_edit_ingestForm','icm_model_add_mime','icm_collection_add_term','icm_collection_add_cmodel','icm_model_add_ingestMethodParam','icm_model_add_ingestMethod','icm_model_add_ingestRule','icm_model_add_appliesTo', + 'icm_model_add_ds','icm_model_add_dispmeth','icm_model_add_ingestFormElement','icm_model_edit_ingestFormElement','icm_model_add_authListItem','icm_model_update_editMetadataMethod','icm_model_rollback','icm_collection_rollback','icm_display_rawXml','icm_model_add_ingestElementParam', + 'icm_model_edit_adddsmeth','icm_model_service_add'); + return in_array($formName,$validForms); +} + +function icm_ajax_processForm($formName) +{ + $params=null; + if (isset($_GET['formReq'])) + { + $params=preg_split('/\s+/',trim($_GET['formReq'])); + } + + // require_once(drupal_get_path('module','islandora_content_modeller').'/ajaxForms.inc'); + if (icm_ajax_formExists($formName)) + { + + $formContent = drupal_get_form($formName,$params); + echo theme_status_messages('error'); + echo $formContent; + } +} + +function icm_ajax_button() +{ + + module_load_include('inc', 'fedora_repository', 'CollectionPolicy'); + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + $params=null; + if (isset($_GET['formReq'])) + { + $params=preg_split('/\s+/',trim($_GET['formReq'])); + } + + switch (strtolower($params[0])) + { + case 'icm_model_remove_editmetadatamethod': + if (count($params) == 2) + { + $model_pid = $params[1]; + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->removeEditMetadataMethod() || !$cm->saveToFedora()) + { + echo t('Error: Unable to remove edit metadata method from model %cm_pid.',array('%cm_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully removed edit metadata method from model %cm_pid.',array('%cm_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + } else + { + echo t('Error: Missing parameters to remove edit metadata method. Please try again.'); + } + break; + + case 'icm_model_remove_authlistitem': + if (count($params) == 4) + { + $model_pid = $params[1]; + $elementName = $params[2]; + $authValue = $params[3]; + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->removeAuthListItem($elementName,$authValue) || !$cm->saveToFedora()) + { + echo t('Error: Unable to remove authoritative list item %value in model %m_pid.',array('%value'=>$authValue,'%m_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully removed authoritative list item %value in model %m_pid.',array('%value'=>$authValue,'%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + } else + { + echo t('Error: Missing parameters to remove authoritative list item. Please try again.'); + } + break; + + case 'icm_model_inc_authlistitem': + if (count($params) == 4) + { + $model_pid = $params[1]; + $elementName = $params[2]; + $authValue = $params[3]; + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->incAuthListItem($elementName,$authValue) || !$cm->saveToFedora()) + { + echo t('Error: Unable to increment authoritative list item %value in model %m_pid.',array('%value'=>$authValue,'%m_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully incremented authoritative list item %value in model %m_pid.',array('%value'=>$authValue,'%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + } else + { + echo t('Error: Missing parameters to increment authoritative list item. Please try again.'); + } + break; + + case 'icm_model_dec_authlistitem': + if (count($params) == 4) + { + $model_pid = $params[1]; + $elementName = $params[2]; + $authValue = $params[3]; + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->decAuthListItem($elementName,$authValue) || !$cm->saveToFedora()) + { + echo t('Error: Unable to decrement authoritative list item %value in model %m_pid.',array('%value'=>$authValue,'%m_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully decremented authoritative list item %value in model %m_pid.',array('%value'=>$authValue,'%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + } else + { + echo t('Error: Missing parameters to decrement authoritative list item. Please try again.'); + } + break; + + case 'icm_model_remove_ingestformelement': + if (count($params) == 3) + { + $model_pid = $params[1]; + $name = $params[2]; + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->removeIngestFormElement($name) || !$cm->saveToFedora()) + { + echo t('Error: Unable to remove ingest form element %name from model %m_pid.',array('%name'=>$name,'%m_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully removed ingest form element %name from model %m_pid.',array('%name'=>$name,'%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + + } else + { + echo t('Error: Missing parameters to remove ingest form element. Please try again.'); + } + break; + + case 'icm_model_inc_ingestformelement': + if (count($params) == 3) + { + $model_pid = $params[1]; + $name = $params[2]; + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->incIngestFormElement($name) || !$cm->saveToFedora()) + { + echo t('Error: Unable to increment ingest form element %name from model %m_pid.',array('%name'=>$name,'%m_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully incremented ingest form element %name from model %m_pid.',array('%name'=>$name,'%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + + } else + { + echo t('Error: Missing parameters to increment ingest form element. Please try again.'); + } + break; + + case 'icm_model_dec_ingestformelement': + if (count($params) == 3) + { + $model_pid = $params[1]; + $name = $params[2]; + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->decIngestFormElement($name) || !$cm->saveToFedora()) + { + echo t('Error: Unable to decrement ingest form element %name from model %m_pid.',array('%name'=>$name,'%m_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully decremented ingest form element %name from model %m_pid.',array('%name'=>$name,'%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + + } else + { + echo t('Error: Missing parameters to decrement ingest form element. Please try again.'); + } + break; + + + case 'icm_model_remove_dispmeth': + if (count($params) == 7) + { + $model_pid = $params[1]; + $dsid = $params[2]; + $module = $params[3]; + $file = $params[4]; + $class = $params[5]; + $method = $params[6]; + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->removeDispMeth($dsid, $module, $file,$class,$method) || !$cm->saveToFedora()) + { + echo t('Error: Unable to remove display method for datastream %dsid of model %m_pid.',array('%dsid'=>$dsid,'%m_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully remove display method for datastream %dsid of model %m_pid.',array('%dsid'=>$dsid, '%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + + } else + { + echo t('Error: Missing parameters to remove display method of datastream.'); + } + break; + + case 'icm_model_default_dispmeth': + if (count($params) == 7) + { + $model_pid = $params[1]; + $dsid = $params[2]; + $module = $params[3]; + $file = $params[4]; + $class = $params[5]; + $method = $params[6]; + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->setDefaultDispMeth($dsid,$module,$file,$class,$method) || !$cm->saveToFedora()) + { + echo t('Error: Unable to set default display method for datastream %dsid of model %m_pid.',array('%dsid'=>$dsid,'%m_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully set default display method for datastream %dsid of model %m_pid.',array('%dsid'=>$dsid, '%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + + } else + { + echo t('Error: Missing parameters to set default display method of datastream.'); + } + break; + + case 'icm_model_remove_ds': + if (count($params) == 3) + { + + $model_pid = $params[1]; + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->removeDs($params[2]) || !$cm->saveToFedora()) + { + echo t('Error: Unable to remove datastream from model %m_pid.',array('%m_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully removed datastream model %m_pid.',array('%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + + } else + { + echo t('Error: Missing parameters for removal of application mimetype.'); + } + break; + case 'icm_model_remove_appliesto': + if (count($params) == 4) + { + + $model_pid = $params[1]; + $rule_id = $params[2]; + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->removeAppliesTo($rule_id,$params[3] ) || !$cm->saveToFedora()) + { + echo t('Error: Unable to remove application mimetype from Rule %rule_id in model %m_pid.',array('%rule_id'=>$rule_id,'%m_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully removed application mimetype from Rule %rule_id in model %m_pid.',array('%rule_id'=>$rule_id,'%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + + } else + { + echo t('Error: Missing parameters for removal of application mimetype.'); + } + break; + + + case 'icm_model_remove_ingestrule': + + if (count($params) == 3) + { + + $model_pid = $params[1]; + $rule_id = $params[2]; + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->removeIngestRule($rule_id ) || !$cm->saveToFedora()) + { + echo t('Error: Unable to remove ingest Rule %rule_id in model %m_pid.',array('%rule_id'=>$rule_id,'%m_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully removed ingest Rule %rule_id in model %m_pid.',array('%rule_id'=>$rule_id,'%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + + } else + { + echo t('Error: Missing parameters for removal of ingest rule.'); + } + break; + + case 'icm_model_remove_ingestmethod': + if (count($params) == 7) + { + + $model_pid = $params[1]; + $rule_id = $params[2]; + $module = $params[3]; + $file = $params[4]; + $class = $params[5]; + $method = $params[6]; + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->removeIngestMethod($rule_id, $module, $file, $class, $method) || !$cm->saveToFedora()) + { + echo t('Error: Unable to remove ingest method of Rule %rule_id in model %m_pid.',array('%rule_id'=>$rule_id,'%m_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully removed ingest method of Rule %rule_id in model %m_pid.',array('%rule_id'=>$rule_id,'%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + + } else + { + echo t('Error: Missing parameters for removal of ingest method.'); + } + break; + + case 'icm_model_remove_ingestmethodparam': + + if (count($params) == 8) + { + + $model_pid = $params[1]; + $rule_id = $params[2]; + $module = $params[3]; + $file = $params[4]; + $class = $params[5]; + $method = $params[6]; + $name = $params[7]; + + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->removeIngestMethodParam($rule_id, $module, $file, $class, $method, $name) || !$cm->saveToFedora()) + { + echo t('Error: Unable to remove parameter of Rule %rule_id in model %m_pid.',array('%rule_id'=>$rule_id,'%m_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully removed parameter of Rule %rule_id in model %m_pid.',array('%rule_id'=>$rule_id,'%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + + } else + { + echo t('Error: Missing parameters for removal of ingest method parameter.'); + } + + break; + + case 'icm_model_remove_ingestelementparam': + + if (count($params) == 4) + { + + $model_pid = $params[1]; + $element_name = $params[2]; + $name = $params[3]; + + if (($cm=ContentModel::loadFromModel($model_pid))!==FALSE) + { + if (!$cm->setIngestFormElementParam($element_name, $name, FALSE) || !$cm->saveToFedora()) + { + echo t('Error: Unable to remove parameter of element %element in model %m_pid.',array('%element'=>$element_name,'%m_pid'=>$cm->pid)); + } else + { + echo 'success:'. t('Successfully removed parameter of element %element in model %m_pid.',array('%element'=>$element_name,'%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + + } else + { + echo t('Error: Missing parameters for removal of form element parameter.'); + } + + break; + + case 'icm_model_toggle_dsdisplay': + if (isset($params[1]) && isset($params[2])) + { + if (($cm=ContentModel::loadFromModel($params[1]))!==FALSE) + { + $dsid = trim($params[2]); + if ($dsid == '') + { + echo t('Error: Datastream missing or not specified. Please try again.'); + } else if (!$cm->setDisplayInFieldset($dsid, !$cm->displayInFieldset($dsid)) || !$cm->saveToFedora()) + { + echo t('Error: Unable to update datastream %dsid in model %m_pid.',array('%dsid'=>$dsid,'%m_pid'=>$cm->pid)); + } else + { + echo 'success:'.t('Successfully updated datastream %dsid in model %m_pid',array('%dsid'=>$dsid,'%m_pid'=>$cm->pid)); + exit(); + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + } else + { + echo t('Error: Missing parameters for toggle datastream display in fieldset.'); + } + break; + + case 'icm_collection_remove_cmodel': + + if (isset($params[1]) && isset($params[2])) + { + $cp = CollectionPolicy::loadFromCollection($params[1]); + $cm = ContentModel::loadFromModel($params[2]); + if ($cm !== false && $cp !== false) + { + if (!$cp->removeModel($cm) || !$cp->saveToFedora()) + { + echo t('Error: Unable to remove content model %m_pid from collection policy of %c_pid.',array('%m_pid'=>$cm->pid,'%c_pid'=>$cp->pid)); + } else + { + echo 'success:'.t('Successfully removed content model %m_pid from collection policy of %c_pid.',array('%m_pid'=>$cm->pid,'%c_pid'=>$cp->pid)); + exit(); + } + } else + echo t('Error: Unknown collection policy or content model. Please try again.'); + } else + echo t('Error: Unknown collection policy or content model. Please try again.'); + break; + + case 'icm_collection_default_term': + $c_pid=$params[1]; + if (($cp = CollectionPolicy::loadFromCollection(trim($c_pid)))!== FALSE) + { + $field=isset($params[2])?$params[2]:''; + if (trim($field) != '') + { + if (!$cp->setDefaultTerm(htmlentities($field)) || !$cp->saveToFedora()) + { + echo t('Error: Unable to set default search term to %field in collection policy %cp_id.',array('%field'=>htmlentities($field),'%pc_pid'=>$cp->pid)); + } else + { + echo 'success:'.t('Successfully set default search term %field in collection policy %cp_id.',array('%field'=>htmlentities($field),'%cp_pid'=>$cp->pid)); + exit(); + } + } else + { + echo t('Error: Unknown search term %field selected. Please try again.',array('%field'=>htmlentities($field))); + } + } else + { + echo t('Error: Unknown collection policy. Please try again.'); + } + break; + + + case 'icm_collection_remove_term': + $c_pid=$params[1]; + if (($cp = CollectionPolicy::loadFromCollection(trim($c_pid)))!== FALSE) + { + $field=isset($params[2])?$params[2]:''; + if (trim($field) != '') + { + if (!$cp->removeTerm(htmlentities($field)) || !$cp->saveToFedora()) + { + echo t('Error: Unable to remove search term %field from collection policy %cp_id.',array('%field'=>htmlentities($field),'%c_pid'=>$cp->pid)); + } else + { + echo 'success:'.t('Successfully removed search term %field from collection policy %cp_id.',array('%field'=>htmlentities($field),'%c_pid'=>$cp->pid)); + exit(); + } + } else + { + echo t('Error: Unknown search term %field selected for removal. Please try again.',array('%field'=>htmlentities($field))); + } + } else + { + echo t('Error: Unknown collection policy. Please try again.'); + } + break; + + case 'icm_model_remove_mime': + $m_pid =$params[1]; + if (($cm = ContentModel::loadFromModel(trim($m_pid))) !== FALSE) + { + $type = isset($params[2])?trim($params[2]):''; + if ($type == '') + { + echo t('Error: You must specify a mimetype to remove. Please try again.'); + } else + { + if ($cm->removeMimetype($type) && $cm->saveToFedora()) + { + echo 'success:'.t('Successfully removed mime type %type from content model %m_pid.',array('%type'=>htmlentities($type),'%m_pid'=>htmlentities($m_pid))); + exit(); + } else + { + echo t('Error: Unable to remove mime type %type from content model %m_pid. Please make sure that it isnt the only term left in the content model.',array('%type'=>htmlentities($type),'%m_pid'=>htmlentities($m_pid))); + } + } + } else + { + echo t('Error: Unknown content model. Please try again.'); + } + break; + + default: + echo t('Error: Unknown action %action Please try again',array('%action'=>$params[0])); + } + +} + + + +function icm_model_add_mime_validate($form,&$form_state) +{ + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + if (trim($form_state['values']['addMime']['model_pid'])=='' || ($cm = ContentModel::loadFromModel(trim($form_state['values']['addMime']['model_pid']))) === FALSE) + { + form_set_error('',t('Error: Specified model could not be found.')); + } else if ($cm->addMimetype(trim($form_state['values']['addMime']['type'])) === FALSE || $cm->saveToFedora() === FALSE) + { + form_set_error('',t('Error: Unable to add mimetype to specified model. Please make sure that the mimetype is not already listed in the model.')); + } else + { + echo 'success:'.t('Successfully added mimetype %mimetype to model %model_name',array('%model_name'=>$cm->name,'%mimetype'=>trim($form_state['values']['addMime']['type']))); + exit(); + } +} + +function icm_model_add_mime(&$form_state,$params=null) +{ + + if (is_array($params) && isset($params[0])) + { + $model_pid = $params[0]; + } else if (isset($form_state['post']['addMime']['model_pid'])) + { + $model_pid = $form_state['post']['addMime']['model_pid']; + } + + + $form['addMime'] = array( + '#type'=>'fieldset', + '#title'=> t('Add Mimetype to Model %model_pid', array('%model_pid'=>$model_pid)), + '#tree'=>TRUE + ); + + $form['addMime']['type'] = array( + '#type' => 'textfield', + '#title' => t('Mimetype'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('A content mimetype that can be ingested using this model.'), + ); + + $form['addMime']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['addMime']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['addMime']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + + return $form; + +} + +function icm_collection_add_cmodel_validate($form,&$form_state) +{ + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc','fedora_repository','CollectionPolicy'); + module_load_include('inc','fedora_repository','ContentModel'); + + if (!ContentModel::validPid($form_state['values']['form']['namespace'])) + { + form_set_error('form][namespace',t('Error: Invalid namespace format.')); + } else + { + $c_pid = $form_state['values']['form']['collection_pid']; + $m_pid = $form_state['values']['form']['model_pid']; + + + if (($cm = ContentModel::loadFromModel($m_pid))!== FALSE && + ($cp = CollectionPolicy::loadFromCollection($c_pid)) !== FALSE) + { + if ($cp->addModel($cm,trim($form_state['values']['form']['namespace'])) && $cp->saveToFedora()) + { + echo 'success:'.t('Successfully added content model %cm_pid% to collection policy of %cp_pid%.',array('%cm_pid%'=>$cm->pid,'%cp_pid%'=>$cp->pid)); + exit(); + + } else + { + form_set_error('form][model_pid',t('Error: Unable to add content model %cm_pid% to collection policy of %cp_pid%. Please make sure that the model is not already listed in the collection policy.',array('%cm_pid%'=>$cm->pid,'%cp_pid%'=>$cp->pid))); + } + + } else + { + form_set_error('',t('Error: Unable to load specified content model or collection policy. Please try again.')); + } + } +} + +function icm_collection_add_cmodel(&$form_state,$params=null) +{ + + if (is_array($params) && isset($params[0])) + { + $collection_pid = $params[0]; + } else if (isset($form_state['post']['form']['collection_pid'])) + { + $collection_pid = $form_state['post']['form']['collection_pid']; + } + + module_load_include('inc','fedora_repository','CollectionClass'); + module_load_include('inc','fedora_repository','ContentModel'); + $collectionHelper = new CollectionClass(); + + $options=array(); + $items = new SimpleXMLElement( $collectionHelper->getRelatedItems(variable_get('fedora_content_model_collection_pid','islandora:ContentModelCollection') ,null,null)); + for ($i = 0; $i < count($items->results->result); $i++) + { + list(,$pid)=preg_split('/\//',$items->results->result[$i]->object['uri']); + + $cm = ContentModel::loadFromModel($pid); + if ($cm !== false) + { + $options[$pid] = $items->results->result[$i]->title .' ('.$pid.')'; + } + } + + $form['form'] = array( + '#type'=>'fieldset', + '#title'=> t('Add content model to collection %collection_pid', array('%collection_pid'=>$collection_pid)), + '#tree'=>TRUE + ); + + $form['form']['model_pid'] = array( + '#type' => 'select', + '#title' => t('Content Model'), + '#required' => TRUE, + '#options' => $options, + '#description' => t('A descriptive label for the field displayed on the search form.'), + ); + + $form['form']['namespace'] = array( + '#type'=> 'textfield', + '#title'=> t('Namespace'), + '#description' => t('The base namespace for objects of this type injested into the collection. eg islandora:collection '), + '#size' => 30, + '#maxSize' => 60, + '#required' => TRUE + ); + + $form['form']['collection_pid'] = array('#type' => 'hidden', '#value'=> $collection_pid); + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + + return $form; + +} + +function icm_collection_add_term_validate($form,&$form_state) +{ + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'CollectionPolicy'); + if (trim($form_state['values']['form']['collection_pid'])=='' || ($cp = CollectionPolicy::loadFromCollection(trim($form_state['values']['form']['collection_pid']))) === FALSE) + { + form_set_error('',t('Error: Specified collection policy could not be found.')); + } else if ($cp->addTerm(trim($form_state['values']['form']['field']),trim($form_state['values']['form']['value'])) === FALSE || $cp->saveToFedora() === FALSE) + { + form_set_error('',t('Error: Unable to add search term to specified collection policy. Please make sure that the field is not already listed in the search terms.')); + } else + { + echo 'success:'.t('Successfully added term %field to collection policy %cp_pid',array('%cp_pid'=>$cp->pid,'%field'=>trim($form_state['values']['form']['field']))); + exit(); + } +} + +function icm_collection_add_term(&$form_state,$params=null) +{ + + if (is_array($params) && isset($params[0])) + { + $collection_pid = $params[0]; + } else if (isset($form_state['post']['collection_pid'])) + { + $collection_pid = $form_state['post']['collection_pid']; + } + + + $form['form'] = array( + '#type'=>'fieldset', + '#title'=> t('Add search term to collection %collection_pid', array('%collection_pid'=>$collection_pid)), + '#tree'=>TRUE + ); + + $form['form']['field'] = array( + '#type' => 'textfield', + '#title' => t('Field'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('The name of the field in the DC to search.'), + ); + + $form['form']['value'] = array( + '#type' => 'textfield', + '#title' => t('Label'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('A descriptive label for the field displayed on the search form.'), + ); + + $form['form']['collection_pid'] = array('#type' => 'hidden', '#value'=> $collection_pid); + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + + return $form; + +} + + +function icm_collection_rollback_validate($form,&$form_state) +{ + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'CollectionPolicy'); + + if (($cp = CollectionPolicy::loadFromCollection($form_state['values']['form']['c_pid']))!==FALSE) + { + + $history = $cp->getHistory(); + $found = false; + foreach ($history as $key=>$ver) + { + if ($ver['versionID'] == $form_state['values']['form']['version']) + { + $found = $key; + break; + } + } + + if ($found === false) + { + form_set_error('',t('Error: Selected version was not found. Please try again.')); + } else if ($found == 0 && $form_state['values']['form']['removeOlder'] == 0) + { + form_set_error('',t('Error: Selected version is the current version. Nothing changed.')); + } else + { + $success=true; + if ($found > 0) + { + $startDate = $history[$found-1]['createDate']; + $success = $cp->purgeVersions($history[$found-1]['createDate'],null); + } + + if ($form_state['values']['form']['removeOlder'] == 1 && isset($history[$found+1])) + { + $endDate = $history[$found+1]['createDate']; + $success = $success && $cp->purgeVersions(null,$endDate); + } + + if ($success) + { + echo 'success:'.t('Successfully rolled back version of collection policy %c_pid.',array('%c_pid'=>$cp->pid)); + exit(); + + } else + { + form_set_error('',t('Error: Unable to roll back version. Check watchdog logs.')); + } + } + + } else + { + form_set_error('',t('Error: Unable to load collection policy %c_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['c_pid'])))); + + } +} + +function icm_collection_rollback(&$form_state,$params=null) +{ + + if (is_array($params)) + { + $c_pid = $params[0]; + } else + { + $c_pid = $form_state['post']['form']['model_pid']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Rollback Collection Policy %$c_pid',array('%$c_pid'=>$c_pid)), + '#tree' => TRUE, + ); + + module_load_include('inc', 'fedora_repository', 'CollectionPolicy'); + + if (($cp = CollectionPolicy::loadFromCollection($c_pid))!==false) + { + + $history = $cp->getHistory(); + + $options = array(); + foreach ($history as $ver) + { + $options[$ver['versionID']] = date(DATE_RFC822,strtotime($ver['createDate'])); + } + + if ($history !== false && count($history) > 0) + { + + + $form['form']['warning'] = array('#value' => 'Warning: Rolling back a datastream will purge all versions up-to the selected datastream.'); + + + $form['form']['cur'] = array( + '#type'=> 'item', + '#title'=> t('Current Version:'), + '#value'=> date(DATE_RFC822,strtotime($history[0]['createDate'])) + ); + + $form['form']['version'] = array( + '#type' => 'select', + '#title' => t('Version'), + '#options' => $options + ); + + $form['form']['removeOlder'] = array( + '#type' => 'checkbox', + '#title' => 'Purge Older Versions', + '#description' => 'If enabled, also purges versions of the datastream that are OLDER than the selected, effectively leaving only the selected version.' + ); + + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + + } else + { + $form['form']['version'] = array( + '#type'=> 'item', + '#title'=> t('Rollback to Version:'), + '#value'=> t('only one version available.') + ); + } + + + + } else + { + form_set_error('',t('Error: Unable to load collection policy %c_pid.',array('%c_pid'=>$c_pid))); + } + + $form['form']['c_pid'] = array('#type' => 'hidden', '#value'=> $c_pid); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + return $form; +} + +function icm_model_add_ingestMethodParam_validate($form,&$form_state) +{ + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + if (($cm = ContentModel::loadFromModel($form_state['values']['form']['model_pid']))!==FALSE) + { + if ($cm->addIngestMethodParam($form_state['values']['form']['rule_id'], + $form_state['values']['form']['module'], + $form_state['values']['form']['file'], + $form_state['values']['form']['class'], + $form_state['values']['form']['method'], + $form_state['values']['form']['name'], + $form_state['values']['form']['value']) && $cm->saveToFedora()) + { + echo 'success:'.t('Successfully added parameter to ingest method of Rule %rule_id for model %model_pid.',array('%rule_id'=>htmlentities($form_state['values']['form']['rule_id']),'%model_pid'=>$cm->pid)); + exit(); + } else + { + form_set_error('form][name',t('Error: Unable to add parameter to ingest method of Rule %rule_id for model %model_pid. Please make sure that the parameter is not already listed.',array('%rule_id'=>htmlentities($form_state['values']['form']['rule_id']),'%model_pid'=>$cm->pid))); + } + } else + { + form_set_error('',t('Error: Unable to load content model %cm_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['model_pid'])))); + } + +} + + +function icm_model_add_ingestMethodParam(&$form_state,$params=null) +{ + + if (is_array($params)) + { + $model_pid = $params[0]; + $rule_id = $params[1]; + $module = $params[2]; + $file = $params[3]; + $class = $params[4]; + $method = $params[5]; + } else + { + $model_pid = $form_state['post']['model_id']; + $rule_id = $form_state['post']['rule_id']; + $module = $form_state['post']['module']; + $file = $form_state['post']['file']; + $class = $form_state['post']['class']; + $method = $form_state['post']['method']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Add Ingest Method Parameter to ingest Rule %rule_id of model %model_pid.',array('%rule_id'=>$rule_id,'%model_pid'=>$model_pid)), + '#tree' => TRUE, + ); + + $form['form']['module_label'] = array( + '#type'=> 'item', + '#title'=> t('Module'), + '#value' => $module + ); + + $form['form']['file_label'] = array( + '#type'=> 'item', + '#title'=> t('Filename'), + '#value' => $file + ); + + $form['form']['class_label'] = array( + '#type'=> 'item', + '#title'=> t('Class'), + '#value' => $class + ); + + $form['form']['method_label'] = array( + '#type'=> 'item', + '#title'=> t('Method'), + '#value' => $method + ); + + $form['form']['name'] = array( + '#type' => 'textfield', + '#title' => t('Name'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('The name of the parameter to pass along to the ingest method above.'), + ); + + $form['form']['value'] = array( + '#type' => 'textfield', + '#title' => t('Value'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('The value of the parameter to pass along to the ingest method above'), + ); + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['rule_id'] = array('#type' => 'hidden', '#value'=> $rule_id); + $form['form']['module'] = array('#type' => 'hidden', '#value'=> $module); + $form['form']['file'] = array('#type' => 'hidden', '#value'=> $file); + $form['form']['class'] = array('#type' => 'hidden', '#value' => $class); + $form['form']['method'] = array('#type' => 'hidden', '#value'=> $method); + + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + + + return $form; +} + + +function icm_model_add_ingestElementParam_validate($form,&$form_state) +{ + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + if (($cm = ContentModel::loadFromModel($form_state['values']['form']['model_pid']))!==FALSE) + { + if ($cm->setIngestFormElementParam($form_state['values']['form']['element_name'], + $form_state['values']['form']['name'], + $form_state['values']['form']['value']) && $cm->saveToFedora()) + { + echo 'success:'.t('Successfully added parameter to form element %element_name for model %model_pid.',array('%element_name'=>htmlentities($form_state['values']['form']['element_name']),'%model_pid'=>$cm->pid)); + exit(); + } else + { + form_set_error('form][name',t('Error: Unable to add parameter to form element %element_name for model %model_pid. Please make sure that the parameter is not already listed.',array('%element_name'=>htmlentities($form_state['values']['form']['element_name']),'%model_pid'=>$cm->pid))); + } + } else + { + form_set_error('',t('Error: Unable to load content model %cm_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['model_pid'])))); + } + +} + + +function icm_model_add_ingestElementParam(&$form_state,$params=null) +{ + if (is_array($params)) + { + $model_pid = $params[0]; + $element_name = $params[1]; + } else + { + $model_pid = $form_state['post']['model_id']; + $element_name = $form_state['post']['element_name']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Add Ingest Form Element Parameter to element %element_name of model %model_pid.',array('%element_name'=>$element_name,'%model_pid'=>$model_pid)), + '#tree' => TRUE, + ); + + $form['form']['name'] = array( + '#type' => 'textfield', + '#title' => t('Name'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('The name of the parameter to pass along to the form element above.'), + ); + + $form['form']['value'] = array( + '#type' => 'textarea', + '#title' => t('Value'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('The value of the parameter to pass along to the form element above'), + ); + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['element_name'] = array('#type' => 'hidden', '#value'=> $element_name); + + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + + + return $form; +} + +function icm_model_add_ingestMethod_validate($form,&$form_state) +{ + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + + $module = $form_state['values']['form']['module']; + $file = $form_state['values']['form']['filename']; + $class= $form_state['values']['form']['class']; + $method=$form_state['values']['form']['method']; + + $path = drupal_get_path('module',$module); + + if (empty($path) || !file_exists($path.'/'.$file)) + { + form_set_error('form][filename',t('Error: Selected plugin file not found. Please try again.')); + } else + { + require_once ($path.'/'.$file); + if (!class_exists($class)) + { + form_set_error('form][class',t('Error: Specified class does not exist in the plugin. Please try again.')); + } else + { + $obj = new $class; + if (!method_exists($obj,$method)) + { + form_set_error('form][method',t('Error: Specified method does not exist in the specified class/plugin. Please try again.')); + } + } + } + + if (!ContentModel::validDsid($form_state['values']['form']['dsid'])) + { + form_set_error('form][dsid',t('Error: Invalid datastream identifier. Please try again.')); + } + + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + if (($cm = ContentModel::loadFromModel($form_state['values']['form']['model_pid']))!==FALSE) + { + if ($cm->addIngestMethod($form_state['values']['form']['rule_id'],$module,$module,$file,$class,$method,$form_state['values']['form']['dsid'],$form_state['values']['form']['modified_files_ext']) && $cm->saveToFedora()) + { + echo 'success:'.t('Successfully added ingest method to Rule %rule_id for model %model_pid.',array('%rule_id'=>htmlentities($form_state['values']['form']['rule_id']),'%model_pid'=>$cm->pid)); + exit(); + } else + { + form_set_error('form][name',t('Error: Unable to add ingest method to Rule %rule_id for model %model_pid.',array('%rule_id'=>htmlentities($form_state['values']['form']['rule_id']),'%model_pid'=>$cm->pid))); + } + } else + { + form_set_error('',t('Error: Unable to load content model %cm_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['model_pid'])))); + } + +} + +function icm_model_add_ingestMethod(&$form_state,$params=null) +{ + + if (is_array($params)) + { + $model_pid = $params[0]; + $rule_id = $params[1]; + } else + { + $model_pid = $form_state['post']['form']['model_pid']; + $rule_id = $form_state['post']['form']['rule_id']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Add Ingest Method to ingest Rule %rule_id of model %model_pid',array('%rule_id'=>$rule_id,'%model_pid'=>$model_pid)), + '#tree' => TRUE, + ); + + $form['form']['module'] = array( + '#type' => 'select', + '#title' => t('Module'), + '#element_validate' => array('icm_module_validate'), + '#options'=>icm_get_modules(), + '#required' => TRUE, + '#description' => t('The name of the module containing the plugin file.'), + ); + + $form['form']['filename'] = array( + '#type' => 'textfield', + '#title' => t('File'), + '#element_validate' => array('icm_filename_validate'), + '#required' => TRUE, + '#description' => t('The relative path of the file containing the ingest method class/method.'), + ); + $form['form']['class'] = array( + '#type' => 'textfield', + '#title' => t('Class'), + '#element_validate' => array('icm_class_validate'), + '#required' => TRUE, + '#size'=>30, + '#description' => t('The name of the ingest method class.'), + ); + $form['form']['method'] = array( + '#type' => 'textfield', + '#title' => t('Method'), + '#element_validate' => array('icm_method_validate'), + '#size'=>30, + '#required' => TRUE, + '#description' => t('The name of the class method to call when ingesting a file.'), + ); + + $form['form']['dsid'] = array( + '#type' => 'textfield', + '#title' => t('Datastream Identifier'), + '#size' => 30, + '#maxlength' => 64, + '#required' => TRUE, + '#description' => t('The datastream ID that will store the output from this method.'), + ); + + $form['form']['modified_files_ext'] = array( + '#type' => 'textfield', + '#title' => t('Modified Files Extension'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('The file extension that will be appended to the modified file.'), + ); + + + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['rule_id'] = array('#type' => 'hidden', '#value'=> $rule_id); + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + return $form; +} + +function icm_model_add_appliesTo_validate($form,&$form_state) +{ + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + if (($cm = ContentModel::loadFromModel($form_state['values']['form']['model_pid']))!==FALSE) + { + if ($cm->addAppliesTo($form_state['values']['form']['rule_id'],$form_state['values']['form']['appliesTo']) && $cm->saveToFedora()) + { + echo 'success:'.t('Successfully added application mimetype to Rule %rule_id for model %model_pid.',array('%rule_id'=>htmlentities($form_state['values']['form']['rule_id']),'%model_pid'=>$cm->pid)); + exit(); + } else + { + form_set_error('form][name',t('Error: Unable to add application mimetype to Rule %rule_id for model %model_pid.',array('%rule_id'=>htmlentities($form_state['values']['form']['rule_id']),'%model_pid'=>$cm->pid))); + } + } else + { + form_set_error('',t('Error: Unable to load content model %cm_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['model_pid'])))); + } +} + +function icm_model_add_appliesTo(&$form_state,$params=null) +{ + + if (is_array($params)) + { + $model_pid = $params[0]; + $rule_id = $params[1]; + } else + { + $model_pid = $form_state['post']['form']['model_pid']; + $rule_id = $form_state['post']['form']['rule_id']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Add application mimetype to ingest Rule %rule_id of model %model_pid',array('%rule_id'=>$rule_id,'%model_pid'=>$model_pid)), + '#tree' => TRUE, + ); + + $form['form']['appliesTo'] = array( + '#type' => 'textfield', + '#title' => t('Applies To'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('A content mimetype that this ingest rule will be applied to.'), + ); + + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['rule_id'] = array('#type' => 'hidden', '#value'=> $rule_id); + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + return $form; +} + + +function icm_model_add_ingestRule_validate($form,&$form_state) +{ + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + $module = $form_state['values']['form']['module']; + $file = $form_state['values']['form']['filename']; + $class= $form_state['values']['form']['class']; + $method=$form_state['values']['form']['method']; + $path = drupal_get_path('module',$module); + + if (empty($path) || !file_exists($path.'/'.$file)) + { + form_set_error('form][filename',t('Error: Selected plugin file not found. Please try again.')); + } else + { + require_once ($path.'/'.$file); + if (!class_exists($class)) + { + form_set_error('form][class',t('Error: Specified class does not exist in the plugin. Please try again.')); + } else + { + $obj = new $class; + if (!method_exists($obj,$method)) + { + form_set_error('form][method',t('Error: Specified method does not exist in the specified class/plugin. Please try again.')); + } + } + } + + if (!ContentModel::validDsid($form_state['values']['form']['dsid'])) + { + form_set_error('form][dsid',t('Error: Invalid datastream identifier. Please try again.')); + } + + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + if (($cm = ContentModel::loadFromModel($form_state['values']['form']['model_pid']))!==FALSE) + { + if ($cm->addIngestRule($form_state['values']['form']['appliesTo'],$module,$file,$class,$method,$form_state['values']['form']['dsid'],$form_state['values']['form']['modified_files_ext']) && $cm->saveToFedora()) + { + echo 'success:'.t('Successfully added ingest method to Rule %rule_id for model %model_pid.',array('%rule_id'=>htmlentities($form_state['values']['form']['rule_id']),'%model_pid'=>$cm->pid)); + exit(); + } else + { + form_set_error('form][name',t('Error: Unable to add ingest rule for model %model_pid.',array('%model_pid'=>$cm->pid))); + } + } else + { + form_set_error('',t('Error: Unable to load content model %cm_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['model_pid'])))); + } + +} + + + +function icm_model_add_ingestRule(&$form_state,$params=null) +{ + + if (is_array($params)) + { + $model_pid = $params[0]; + } else + { + $model_pid = $form_state['post']['form']['model_pid']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Add Ingest Rule to model %model_pid',array('%model_pid'=>$model_pid)), + '#tree' => TRUE, + ); + + $form['form']['appliesTo'] = array( + '#type' => 'textfield', + '#title' => t('Applies To'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('A content mimetype that this ingest rule will be applied to.'), + ); + + $form['form']['module'] = array( + '#type' => 'select', + '#title' => t('Module'), + '#element_validate' => array('icm_module_validate'), + '#options'=>icm_get_modules(), + '#required' => TRUE, + '#description' => t('The name of the module containing the plugin file.'), + ); + + $form['form']['filename'] = array( + '#type' => 'textfield', + '#title' => t('File'), + '#element_validate' => array('icm_filename_validate'), + '#required' => TRUE, + '#description' => t('The relative path of the file containing the ingest method class/method.'), + ); + + $form['form']['class'] = array( + '#type' => 'textfield', + '#title' => t('Class'), + '#element_validate' => array('icm_class_validate'), + '#required' => TRUE, + '#size'=>30, + '#description' => t('The name of the ingest method class.'), + ); + $form['form']['method'] = array( + '#type' => 'textfield', + '#title' => t('Method'), + '#element_validate' => array('icm_method_validate'), + '#size'=>30, + '#required' => TRUE, + '#description' => t('The name of the class method to call when ingesting a file.'), + ); + + $form['form']['dsid'] = array( + '#type' => 'textfield', + '#title' => t('Datastream Identifier'), + '#size' => 30, + '#maxlength' => 64, + '#required' => TRUE, + '#description' => t('The datastream ID that will store the output from this method.'), + ); + + $form['form']['modified_files_ext'] = array( + '#type' => 'textfield', + '#title' => t('Modified Files Extension'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('The file extension that will be appended to the modified file.'), + ); + + + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + return $form; +} + + +function icm_model_add_ds_validate($form,&$form_state) +{ + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + if (!ContentModel::validDsid($form_state['values']['form']['dsid'])) + { + form_set_error('form][dsid',t('Error: Invalid datastream identifier. Please try again.')); + } + + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + + if (($cm = ContentModel::loadFromModel($form_state['values']['form']['model_pid']))!==FALSE) + { + if ($cm->addDs($form_state['values']['form']['dsid'],$form_state['values']['form']['display_in_fieldset']==1) && $cm->saveToFedora()) + { + echo 'success:'.t('Successfully added datastream to model %model_pid.',array('%model_pid'=>$cm->pid)); + exit(); + } else + { + form_set_error('form][name',t('Error: Unable to add datastream to model %model_pid.',array('%model_pid'=>$cm->pid))); + } + } else + { + form_set_error('',t('Error: Unable to load content model %cm_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['model_pid'])))); + } + +} + +function icm_model_add_ds(&$form_state,$params=null) +{ + + if (is_array($params)) + { + $model_pid = $params[0]; + } else + { + $model_pid = $form_state['post']['form']['model_pid']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Add Datastream to model %model_pid',array('%model_pid'=>$model_pid)), + '#tree' => TRUE, + ); + + $form['form']['dsid'] = array( + '#type' => 'textfield', + '#title' => t('Datastream Identifier'), + '#size' => 30, + '#maxlength' => 64, + '#required' => TRUE, + '#description' => t('The datastream ID that will store the output from this method.'), + ); + + $form['form']['display_in_fieldset'] = array( + '#type' => 'checkbox', + '#title' => t('Display in Fieldset'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('Display this datastream in the fieldset for this model?'), + ); + + + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + return $form; +} + + +function icm_model_add_dispmeth_validate($form,&$form_state) +{ + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + $module = $form_state['values']['form']['module']; + $file = $form_state['values']['form']['filename']; + $class= $form_state['values']['form']['class']; + $method=$form_state['values']['form']['method']; + $path = drupal_get_path('module',$module); + + if (empty($path) || !file_exists($path.'/'.$file)) + { + form_set_error('form][filename',t('Error: Selected plugin file not found. Please try again.')); + } else + { + require_once ($path.'/'.$file); + if (!class_exists($class)) + { + form_set_error('form][class',t('Error: Specified class does not exist in the plugin. Please try again.')); + } else + { + $obj = @new $class; + if (!method_exists($obj,$method)) + { + form_set_error('form][method',t('Error: Specified method does not exist in the specified class/plugin. Please try again.')); + } + } + } + + if (!ContentModel::validDsid($form_state['values']['form']['dsid'])) + { + form_set_error('form][dsid',t('Error: Invalid datastream identifier. Please try again.')); + } + + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + + if (($cm = ContentModel::loadFromModel($form_state['values']['form']['model_pid']))!==FALSE) + { + if ($cm->addDispMeth($form_state['values']['form']['dsid'],$module,$file,$class,$method) && $cm->saveToFedora()) + { + echo 'success:'.t('Successfully added display method to datastream %dsid of model %model_pid.',array('%dsid'=>htmlentities($form_state['values']['form']['dsid']),'%model_pid'=>$cm->pid)); + exit(); + } else + { + form_set_error('form][name',t('Error: Unable to add display method to datastream %dsid of model %model_pid.',array('%dsid'=>htmlentities($form_state['values']['form']['dsid']),'%model_pid'=>$cm->pid))); + } + } else + { + form_set_error('',t('Error: Unable to load content model %cm_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['model_pid'])))); + } +} + +function icm_model_add_dispmeth(&$form_state,$params=null) +{ + + if (is_array($params)) + { + $model_pid = $params[0]; + $dsid = $params[1]; + } else + { + $model_pid = $form_state['post']['form']['model_pid']; + $dsid = $form_state['post']['form']['dsid']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Add Display Method to datastream %dsid of model %model_pid',array('%dsid'=>$dsid,'%model_pid'=>$model_pid)), + '#tree' => TRUE, + ); + + $form['form']['module'] = array( + '#type' => 'select', + '#title' => t('Module'), + '#element_validate' => array('icm_module_validate'), + '#options'=>icm_get_modules(), + '#required' => TRUE, + '#description' => t('The name of the module containing the plugin file.'), + ); + + $form['form']['filename'] = array( + '#type' => 'textfield', + '#title' => t('File'), + '#element_validate' => array('icm_filename_validate'), + '#required' => TRUE, + '#description' => t('The relative path of the file containing the ingest method class/method.'), + ); + + $form['form']['class'] = array( + '#type' => 'textfield', + '#title' => t('Class'), + '#element_validate' => array('icm_class_validate'), + '#required' => TRUE, + '#size'=>30, + '#description' => t('The name of the ingest method class.'), + ); + $form['form']['method'] = array( + '#type' => 'textfield', + '#title' => t('Method'), + '#element_validate' => array('icm_method_validate'), + '#size'=>30, + '#required' => TRUE, + '#description' => t('The name of the class method to call when displaying the datastream.'), + ); + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['dsid'] = array('#type' => 'hidden', '#value'=> $dsid); + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + return $form; +} + + +function icm_model_edit_ingestForm_validate($form,&$form_state) +{ + module_load_include('inc', 'fedora_repository', 'ContentModel'); + if (!ContentModel::validDsid($form_state['values']['form']['dsid'])) + { + form_set_error('form][dsid',t('Error: Invalid datastream identifier. Please try again.')); + } + + $module=$form_state['values']['form']['module']; + $file = $form_state['values']['form']['filename']; + $class= $form_state['values']['form']['class']; + $method=$form_state['values']['form']['method']; + $handler=$form_state['values']['form']['handler']; + $path = drupal_get_path('module',$module); + + if (empty($path) || !file_exists($path.'/'.$file)) + { + form_set_error('form][filename',t('Error: Selected plugin file not found. Please try again.')); + } else + { + require_once ($path.'/'.$file); + if (!class_exists($class)) + { + form_set_error('form][class',t('Error: Specified class does not exist in the plugin. Please try again.')); + } else + { + $obj = @new $class; + if (!method_exists($obj,$method)) + { + form_set_error('form][method',t('Error: Specified builder method does not exist in the specified class/plugin. Please try again.')); + } + if (!method_exists($obj,$handler)) + { + form_set_error('form][handler',t('Error: Specified handler method does not exist in the specified class/plugin. Please try again.')); + } + } + } + + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + + if (($cm = ContentModel::loadFromModel($form_state['values']['form']['model_pid']))!==FALSE) + { + if ($cm->editIngestFormAttributes(htmlentities($form_state['values']['form']['dsid']),htmlentities($form_state['values']['form']['page']),$form_state['values']['form']['hide_file_chooser']==1,$form_state['values']['form']['redirect']==1) + && $cm->editIngestFormBuilderMethod($module,$file,$class,$method,$handler) + && $cm->saveToFedora()) + { + echo 'success:'.t('Successfully updated ingest form of model %model_pid.',array('%model_pid'=>$cm->pid)); + exit(); + } else + { + form_set_error('form][name',t('Error: Unable to update ingest form of model %model_pid.',array('%model_pid'=>$cm->pid))); + } + } else + { + form_set_error('',t('Error: Unable to load content model %cm_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['model_pid'])))); + } +} + +function icm_model_edit_ingestForm(&$form_state,$params=null) +{ + + if (is_array($params)) + { + $model_pid = $params[1]; + } else + { + $model_pid = $form_state['post']['form']['model_pid']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Edit Ingest Form of model %model_pid',array('%model_pid'=>$model_pid)), + '#tree' => TRUE, + ); + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + if (($cm = ContentModel::loadFromModel($model_pid))!==false) + { + + $attr = $cm->getIngestFormAttributes(); + $builderMethod = $cm->getIngestFormBuilderMethod(); + + $form['form']['dsid'] = array( + '#type' => 'textfield', + '#title' => t('Datastream Identifier'), + '#size' => 30, + '#maxlength' => 64, + '#default_value'=>$attr['dsid'], + '#required' => TRUE, + + '#description' => t('The datastream ID that stores the collected metadata from the form.'), + ); + + $form['form']['page'] = array( + '#type' => 'textfield', + '#title' => t('Page'), + '#size' => 3, + '#required' => TRUE, + '#default_value' => $attr['page'], + '#description' => t('??? not sure what this field is for. Cant find a reference to it, candidate for removal. '), + ); + + $form['form']['hide_file_chooser'] = array( + '#type' => 'checkbox', + '#title' => t('Hide File Chooser'), + '#size' => 3, + '#required' => TRUE, + '#default_value' => $attr['hide_file_chooser']?1:0, + '#description' => t('If enabled, the file choose will not be displayed in the ingest form for this model.'), + ); + + $form['form']['redirect'] = array( + '#type' => 'checkbox', + '#title' => t('Redirect on Ingest'), + '#size' => 3, + '#required' => TRUE, + '#default_value' => $attr['redirect']?1:0, + '#description' => t('If enabled, the user will be redirected to the collection view on successful ingest.'), + ); + + $form['form']['module'] = array( + '#type' => 'select', + '#title' => t('Module'), + '#element_validate' => array('icm_module_validate'), + '#options'=>icm_get_modules(), + '#required' => TRUE, + '#default_value'=> ($builderMethod['module']==''?'fedora_repository':$builderMethod['module']), + '#description' => t('The name of the module containing the plugin file.'), + ); + + $form['form']['filename'] = array( + '#type' => 'textfield', + '#title' => t('File'), + '#element_validate' => array('icm_filename_validate'), + '#required' => TRUE, + '#default_value'=> $builderMethod['file'], + '#description' => t('The relative path of the file containing the builder/handler method class/method.'), + ); + + $form['form']['class'] = array( + '#type' => 'textfield', + '#title' => t('Class'), + '#element_validate' => array('icm_class_validate'), + '#required' => TRUE, + '#default_value'=> $builderMethod['class'], + '#size'=>30, + '#description' => t('The name of the builder/handler class.'), + ); + $form['form']['method'] = array( + '#type' => 'textfield', + '#title' => t('Form Builder Method'), + '#element_validate' => array('icm_method_validate'), + '#size'=>30, + '#required' => TRUE, + '#default_value'=> $builderMethod['method'], + '#description' => t('The name of the class method to build the ingest form.'), + ); + + $form['form']['handler'] = array( + '#type' => 'textfield', + '#title' => t('Form Handler Method'), + '#element_validate' => array('icm_method_validate'), + '#size'=>30, + '#required' => TRUE, + '#default_value'=> $builderMethod['handler'], + '#description' => t('The name of the class method to handle the ingest form.'), + ); + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + + } else + { + form_set_error('',t('Error: Unable to load content model %model_pid.',array('%model_pid'=>$model_pid))); + } + + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + + return $form; +} + + +function icm_model_edit_adddsmeth_validate($form,&$form_state) +{ + module_load_include('inc', 'fedora_repository', 'ContentModel'); + if (!ContentModel::validDsid($form_state['values']['form']['dsid'])) + { + form_set_error('form][dsid',t('Error: Invalid datastream identifier. Please try again.')); + } + + $module = $form_state['values']['form']['module']; + $file = $form_state['values']['form']['filename']; + $class= $form_state['values']['form']['class']; + $method=$form_state['values']['form']['method']; + $handler=$form_state['values']['form']['handler']; + $path = drupal_get_path('module',$module); + + if (empty($path) || !file_exists($path.'/'.$file)) + { + form_set_error('form][filename',t('Error: Selected plugin file not found. Please try again.')); + } else + { + require_once ($path.'/'.$file); + if (!class_exists($class)) + { + form_set_error('form][class',t('Error: Specified class does not exist in the plugin. Please try again.')); + } else + { + $obj = @new $class; + if (!method_exists($obj,$method)) + { + form_set_error('form][method',t('Error: Specified builder method does not exist in the specified class/plugin. Please try again.')); + } + if (!method_exists($obj,$handler)) + { + form_set_error('form][handler',t('Error: Specified handler method does not exist in the specified class/plugin. Please try again.')); + } + } + } + + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + + if (($cm = ContentModel::loadFromModel($form_state['values']['form']['model_pid']))!==FALSE) + { + if ($cm->editIngestFormAttributes(htmlentities($form_state['values']['form']['dsid']),htmlentities($form_state['values']['form']['page']),$form_state['values']['form']['hide_file_chooser']==1,$form_state['values']['form']['redirect']==1) + && $cm->editIngestFormBuilderMethod($module,$file,$class,$method,$handler) + && $cm->saveToFedora()) + { + echo 'success:'.t('Successfully updated ingest form of model %model_pid.',array('%model_pid'=>$cm->pid)); + exit(); + } else + { + form_set_error('form][name',t('Error: Unable to update ingest form of model %model_pid.',array('%model_pid'=>$cm->pid))); + } + } else + { + form_set_error('',t('Error: Unable to load content model %cm_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['model_pid'])))); + } +} + +function icm_model_edit_adddsmeth(&$form_state,$params=null) +{ + + if (is_array($params)) + { + $model_pid = $params[0]; + $dsid = $params[1]; + } else + { + $model_pid = $form_state['post']['form']['model_pid']; + $dsid = $form_state['post']['form']['dsid']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Edit Add DataStream Method of model %model_pid datastream %dsid',array('%model_pid'=>$model_pid,'%dsid'=>$dsid)), + '#tree' => TRUE, + ); + + $form['form']['label'] = array('#value'=> t('This method will be called when a datastream is ingested into %dsid. The resulting file generated by the method is then ingested into the specified datastream.')); + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + if (($cm = ContentModel::loadFromModel($model_pid))!==false) + { + + $method = $cm->getIngestFormBuilderMethod(); + + + $form['form']['module'] = array( + '#type' => 'select', + '#title' => t('Module'), + '#element_validate' => array('icm_module_validate'), + '#options'=>icm_get_modules(), + '#default_value'=>($method['module']==''?'fedora_repository':$method['module']), + '#required' => TRUE, + '#description' => t('The name of the module containing the plugin file.'), + ); + + $form['form']['filename'] = array( + '#type' => 'textfield', + '#title' => t('File'), + '#element_validate' => array('icm_filename_validate'), + '#default_value' =>$method['file'], + '#required' => TRUE, + '#description' => t('The relative path of the file containing the ingest method class/method.'), + ); + + $form['form']['class'] = array( + '#type' => 'textfield', + '#title' => t('Class'), + '#element_validate' => array('icm_class_validate'), + '#required' => TRUE, + '#default_value'=> $method['class'], + '#size'=>30, + '#description' => t('The name of the class.'), + ); + $form['form']['method'] = array( + '#type' => 'textfield', + '#title' => t('Form Builder Method'), + '#element_validate' => array('icm_method_validate'), + '#size'=>30, + '#required' => TRUE, + '#default_value'=> $method['method'], + '#description' => t('The name of the class method called.'), + ); + + + $form['form']['out_dsid'] = array( + '#type' => 'textfield', + '#title' => t('Datastream Identifier'), + '#size' => 30, + '#maxlength' => 64, + '#default_value'=>$attr['dsid'], + '#required' => TRUE, + '#description' => t('The datastream ID that stores the resulting file from the specified method.'), + ); + + $form['form']['modified_files_ext'] = array( + '#type' => 'textfield', + '#title' => t('Modified Files Extension'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('The file extension that will be appended to the modified file.'), + ); + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['dsid'] = array('#type' => 'hidden', '#value'=> $dsid); + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + + } else + { + form_set_error('',t('Error: Unable to load content model %model_pid.',array('%model_pid'=>$model_pid))); + } + + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + + return $form; +} + + +function icm_model_add_ingestFormElement_validate($form,&$form_state) +{ + + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + if (($cm = ContentModel::loadFromModel($form_state['values']['form']['model_pid']))!==FALSE) + { + + $elements = $cm->getIngestFormElements(); + $found = false; + foreach ($elements as $el) + { + if ($el['name']==$form_state['values']['form']['name']) + { + $found=true; + break; + } + } + + if ($found) + { + form_set_error('form][name',t('Error: The specified form element name is already listed in the ingest form. Please edit or delete the existing form element instead.')); + } + else if ($cm->addIngestFormElement(htmlentities($form_state['values']['form']['name']), + htmlentities($form_state['values']['form']['label']), + $form_state['values']['form']['type'], + $form_state['values']['form']['required']==1, + htmlentities($form_state['values']['form']['description'])) && + + $cm->setIngestFormElementParam(htmlentities($form_state['values']['form']['name']), + '#sticky', + $form_state['values']['form']['sticky']==1?'TRUE':false) && + + $cm->setIngestFormElementParam(htmlentities($form_state['values']['form']['name']), + '#autocomplete_path', + trim($form_state['values']['form']['autocomplete'])!=''?trim($form_state['values']['form']['autocomplete']):false) + && $cm->saveToFedora()) + { + echo 'success:'.t('Successfully added ingest form element to model %model_pid.',array('%model_pid'=>$cm->pid)); + exit(); + } else + { + form_set_error('form][name',t('Error: Unable to add ingest form element to model %model_pid.',array('%model_pid'=>$cm->pid))); + } + } else + { + form_set_error('',t('Error: Unable to load content model %cm_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['model_pid'])))); + } +} + +function icm_model_add_ingestFormElement(&$form_state,$params=null) +{ + if (is_array($params)) + { + $model_pid = $params[0]; + } else + { + $model_pid = $form_state['post']['form']['model_pid']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Add element to ingest form of %model_pid',array('%model_pid'=>$model_pid)), + '#tree' => TRUE, + ); + + $form['form']['name'] = array( + '#type' => 'textfield', + '#title' => t('Name'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('The name of the form element.'), + ); + + $form['form']['label'] = array( + '#type' => 'textfield', + '#title' => t('Label'), + '#size' => 30, + '#description' => t('The label that will be displayed for the form element. If left blank, defaults to the element name.'), + ); + + $form['form']['type'] = array( + '#type' => 'select', + '#title' => t('Type'), + '#required'=> TRUE, + '#options'=> array('textfield'=>t('Textfield'), + 'select'=>t('Select'), + 'checkbox'=>t('Checkbox'), + 'radio'=>t('Radio'), + 'textarea'=>t('Textarea'), + 'filechooser'=>t('Ingest File Chooser'), + 'list'=>t('List/Tag Editor'), + 'fieldset'=>t('Fieldset'), + 'people'=>t('People List (incl. name, title, organization, conference, role)'), + 'other_select'=>t('Select (with \'other\' option)'), + 'datepicker'=>t('Datepicker'), + 'copyright'=>t('Creative-Commons Copyright Chooser'), + 'hidden'=>t('Hidden'), + 'file'=>t('File Upload (browse)'), + ), + '#description'=> t('The type of form element to display.') + ); + + $form['form']['required'] = array( + '#type' => 'checkbox', + '#title' => t('Required'), + '#description' => t('If enabled, the form element will be required.'), + ); + + $form['form']['sticky'] = array( + '#type' => 'checkbox', + '#title' => t('Sticky'), + '#description' => t('If enabled, the entered value will be carried over to the next ingest.'), + ); + + + $form['form']['autocomplete'] = array( + '#type' => 'textfield', + '#title' => t('Autocomplete Path'), + '#description' => t('Filled in, the field will suggest values from the current collection as the user types as listed by the specified drupal path. Only available for textfields.'), + ); + + $form['form']['description'] = array( + '#type' => 'textarea', + '#title' => t('Description'), + '#size'=>30, + '#description' => t('A brief description that will appear next to the form element.'), + ); + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + + return $form; +} + + +function icm_model_edit_ingestFormElement_validate($form,&$form_state) +{ + + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + if (($cm = ContentModel::loadFromModel($form_state['values']['form']['model_pid']))!==FALSE) + { + + $elements = $cm->getIngestFormElements(); + $found = false; + foreach ($elements as $el) + { + if ($el['name']==$form_state['values']['form']['name']) + { + $found=true; + break; + } + } + + if (!$found) + { + form_set_error('form][name',t('Error: The specified form element was not found in the ingest form.')); + } + else if ($cm->editIngestFormElement(htmlentities($form_state['values']['form']['name']), + htmlentities($form_state['values']['form']['label']), + $form_state['values']['form']['type'], + $form_state['values']['form']['required']==1, + htmlentities($form_state['values']['form']['description'])) && + + $cm->setIngestFormElementParam(htmlentities($form_state['values']['form']['name']), + '#sticky', + $form_state['values']['form']['sticky']==1?'TRUE':false) && + + $cm->setIngestFormElementParam(htmlentities($form_state['values']['form']['name']), + '#autocomplete_path', + trim($form_state['values']['form']['autocomplete'])!=''?trim($form_state['values']['form']['autocomplete']):false) && + + $cm->saveToFedora()) + { + echo 'success:'.t('Successfully updated ingest form element to model %model_pid.',array('%model_pid'=>$cm->pid)); + exit(); + } else + { + form_set_error('form][name',t('Error: Unable to updated ingest form element to model %model_pid.',array('%model_pid'=>$cm->pid))); + } + } else + { + form_set_error('',t('Error: Unable to load content model %cm_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['model_pid'])))); + } +} + +function icm_model_edit_ingestFormElement(&$form_state,$params=null) +{ + if (is_array($params)) + { + $model_pid = array_shift($params); + $name = join(' ',$params); + } else + { + $model_pid = $form_state['post']['form']['model_pid']; + $name = $form_state['post']['form']['name']; + } + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + if (($cm = ContentModel::loadFromModel($model_pid))!==false) + { + $elements = $cm->getIngestFormElements(); + $element = false; + foreach ($elements as $el) + { + if ($el['name'] == $name) + { + $element=$el; + break; + } + } + + if ($element === false) + { + form_set_error('',t('Error: Specified ingest form element "%name" does not exist. Please try again.',array('%name'=>$name))); + } else + { + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Edit element "%name" in ingest form of %model_pid',array('%name'=>$name,'%model_pid'=>$model_pid)), + '#tree' => TRUE, + ); + + $form['form']['nameDisp'] = array( + '#type' => 'item', + '#title' => t('Name'), + '#description' => t('The name of the form element.'), + '#value'=>$name + ); + + $form['form']['label'] = array( + '#type' => 'textfield', + '#title' => t('Label'), + '#size' => 30, + '#default_value' => ($element['label']==$name)?'':$element['label'], + '#description' => t('The label that will be displayed for the form element. If left blank, defaults to the element name.'), + ); + + $form['form']['type'] = array( + '#type' => 'select', + '#title' => t('Type'), + '#required'=> TRUE, + '#default_value'=>$element['type'], + '#options'=> array('textfield'=>t('Textfield'), + 'select'=>t('Select'), + 'checkbox'=>t('Checkbox'), + 'radio'=>t('Radio'), + 'textarea'=>t('Textarea'), + 'filechooser'=>t('Ingest File Chooser'), + 'list'=>t('List/Tag Editor'), + 'fieldset'=>t('Fieldset'), + 'people'=>t('People List (incl. name, title, organization, conference, role)'), + 'other_select'=>t('Select (with \'other\' option)'), + 'datepicker'=>t('Datepicker'), + 'copyright'=>t('Creative-Commons Copyright Chooser'), + 'hidden'=>t('Hidden'), + 'file'=>t('File Upload (browse)'), + ), + '#description'=> t('The type of form element to display.
Warning: Changing the type from "Select" or "Radio" to anything else will cause any authoritative list to be permanently removed.') + ); + + $form['form']['required'] = array( + '#type' => 'checkbox', + '#title' => t('Required'), + '#default_value'=> $element['required']?1:0, + '#description' => t('If enabled, the form element will be required.'), + ); + + $form['form']['sticky'] = array( + '#type' => 'checkbox', + '#title' => t('Sticky'), + '#default_value'=> isset($element['parameters']['#sticky']) && $element['parameters']['#sticky']?1:0, + '#description' => t('If enabled, the entered value will be carried over to the next ingest.'), + ); + + $form['form']['autocomplete'] = array( + '#type' => 'textfield', + '#title' => t('Autocomplete Path'), + '#default_value'=> isset($element['parameters']['#autocomplete_path'])?$element['parameters']['#autocomplete_path']:'', + '#description' => t('Filled in, the field will suggest values from the current collection as the user types as listed by the specified drupal path. Only available for textfields.'), + ); + + + $form['form']['description'] = array( + '#type' => 'textarea', + '#title' => t('Description'), + '#default_value' => $element['description'], + '#description' => t('A brief description that will appear next to the form element.'), + ); + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['name'] = array('#type' => 'hidden', '#value'=> $name); + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + } + } else { + form_set_error('',t('Error: Unable to load content model %model_pid.',array('%model_pid'=>$model_pid))); + } + + + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + + return $form; +} + + +function icm_model_add_authListItem_validate($form,&$form_state) +{ + + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + if (($cm = ContentModel::loadFromModel($form_state['values']['form']['model_pid']))!==FALSE) + { + + $elements = $cm->getIngestFormElements(); + $found = false; + foreach ($elements as $el) + { + if ($el['name']==$form_state['values']['form']['name']) + { + $found=true; + break; + } + } + + if (!$found) + { + form_set_error('form][name',t('Error: The specified form element was not found in the ingest form.')); + } + else if ($cm->addAuthListItem($form_state['values']['form']['name'],htmlentities($form_state['values']['form']['authValue']),htmlentities($form_state['values']['form']['authLabel'])) + && $cm->saveToFedora()) + { + echo 'success:'.t('Successfully added authoritative list item to ingest form element to model %model_pid.',array('%model_pid'=>$cm->pid)); + exit(); + } else + { + form_set_error('form][name',t('Error: Unable to add authoritative list item to ingest form of model %model_pid.',array('%model_pid'=>$cm->pid))); + } + } else + { + form_set_error('',t('Error: Unable to load content model %cm_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['model_pid'])))); + } +} + + + +function icm_model_add_authListItem(&$form_state,$params=null) +{ + if (is_array($params)) + { + $model_pid = array_shift($params); + $name = join(' ',$params); + } else + { + $model_pid = $form_state['post']['form']['model_pid']; + $name = $form_state['post']['form']['name']; + } + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + if (($cm = ContentModel::loadFromModel($model_pid))!==false) + { + $elements = $cm->getIngestFormElements(); + $element = false; + foreach ($elements as $el) + { + if ($el['name'] == $name) + { + $element=$el; + break; + } + } + + if ($element === false) + { + form_set_error('',t('Error: Specified ingest form element "%name" does not exist. Please try again.',array('%name'=>$name))); + } else + { + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Add item to the authoritative list of element "%name" of ingest form for %model_pid',array('%name'=>$name,'%model_pid'=>$model_pid)), + '#tree' => TRUE, + ); + + $form['form']['nameDisp'] = array( + '#type' => 'item', + '#title' => t('Element Name'), + '#description' => t('The name of the form element.'), + '#value'=>$name + ); + + $form['form']['labelDisp'] = array( + '#type' => 'item', + '#title' => t('Element Label'), + '#value' => $element['label'], + '#description' => t('The label that will be displayed for the form element.'), + ); + + + $form['form']['authValue'] = array( + '#type' => 'textfield', + '#title' => t('Value'), + '#size'=> 30, + '#required' => TRUE, + '#description' => t('Authoritative list value.'), + ); + + $form['form']['authLabel'] = array( + '#type' => 'textfield', + '#title' => t('Label'), + '#size'=> 30, + '#description' => t('Authoritative list label. If left blank the item\'s value will also be used as the label.'), + ); + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['name'] = array('#type' => 'hidden', '#value'=> $name); + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + } + } else { + form_set_error('',t('Error: Unable to load content model %model_pid.',array('%model_pid'=>$model_pid))); + } + + + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + + return $form; +} + + + +function icm_model_update_editMetadataMethod_validate($form,&$form_state) +{ + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + $module = $form_state['values']['form']['module']; + $file = $form_state['values']['form']['filename']; + $class= $form_state['values']['form']['class']; + $method=$form_state['values']['form']['method']; + $handler=$form_state['values']['form']['handler']; + $path = drupal_get_path('module',$module); + + if (empty($path) || !file_exists($path.'/'.$file)) + { + form_set_error('form][filename',t('Error: Selected plugin file not found. Please try again.')); + } else + { + require_once ($path.'/'.$file); + if (!class_exists($class)) + { + form_set_error('form][class',t('Error: Specified class does not exist in the plugin. Please try again.')); + } else + { + $obj = new $class; + if (!method_exists($obj,$method)) + { + form_set_error('form][method',t('Error: Specified builder method does not exist in the specified class/plugin. Please try again.')); + } + if (!method_exists($obj,$handler)) + { + form_set_error('form][handler',t('Error: Specified handler method does not exist in the specified class/plugin. Please try again.')); + } + } + } + + if (!ContentModel::validDsid($form_state['values']['form']['dsid'])) + { + form_set_error('form][dsid',t('Error: Invalid datastream identifier. Please try again.')); + } + + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + if (($cm = ContentModel::loadFromModel($form_state['values']['form']['model_pid']))!==FALSE) + { + if ($cm->updateEditMetadataMethod($module,$file,$class,$method,$handler,$form_state['values']['form']['dsid']) && $cm->saveToFedora()) + { + echo 'success:'.t('Successfully updated edit metadata method to model %model_pid.',array('%model_pid'=>$cm->pid)); + exit(); + } else + { + form_set_error('form][name',t('Error: Unable to update edit metadata method to model %model_pid.',array('%model_pid'=>$cm->pid))); + } + } else + { + form_set_error('',t('Error: Unable to load content model %cm_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['model_pid'])))); + } + +} + +function icm_model_update_editMetadataMethod(&$form_state,$params=null) +{ + + if (is_array($params)) + { + $model_pid = $params[0]; + } else + { + $model_pid = $form_state['post']['form']['model_pid']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Update Edit Metadata Method to model %model_pid',array('%model_pid'=>$model_pid)), + '#tree' => TRUE, + ); + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + if (($cm = ContentModel::loadFromModel($model_pid))!==false) + { + + $method = $cm->getEditMetadataMethod(); + + $form['form']['dsid'] = array( + '#type' => 'textfield', + '#title' => t('Datastream Identifier'), + '#size' => 30, + '#maxlength' => 64, + '#default_value'=>isset($method['dsid'])?$method['dsid']:'', + '#required' => TRUE, + '#description' => t('The datastream ID that stores the collected metadata from the form.'), + ); + + $form['form']['module'] = array( + '#type' => 'select', + '#title' => t('Module'), + '#element_validate' => array('icm_module_validate'), + '#options'=>icm_get_modules(), + '#required' => TRUE, + '#default_value'=>isset($method['module'])?$method['module']:'', + '#description' => t('The name of the module containing the plugin file.'), + ); + + $form['form']['filename'] = array( + '#type' => 'textfield', + '#title' => t('File'), + '#element_validate' => array('icm_filename_validate'), + '#default_value'=>isset($method['file'])?$method['file']:'', + '#required' => TRUE, + '#description' => t('The relative path of the file containing the ingest method class/method.'), + ); + + $form['form']['class'] = array( + '#type' => 'textfield', + '#title' => t('Class'), + '#element_validate' => array('icm_class_validate'), + '#required' => TRUE, + '#size'=>30, + '#default_value'=>isset($method['class'])?$method['class']:'', + '#description' => t('The name of the ingest method class.'), + ); + $form['form']['method'] = array( + '#type' => 'textfield', + '#title' => t('Builder Method'), + '#element_validate' => array('icm_method_validate'), + '#size'=>30, + '#required' => TRUE, + '#default_value'=>isset($method['method'])?$method['method']:'', + '#description' => t('The name of the class method to call when building the edit metadata form.'), + ); + + $form['form']['handler'] = array( + '#type' => 'textfield', + '#title' => t('Handler Method'), + '#element_validate' => array('icm_method_validate'), + '#size'=>30, + '#required' => TRUE, + '#default_value'=>isset($method['handler'])?$method['handler']:'', + '#description' => t('The name of the class method to call to handle the edit metadata form.'), + ); + + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + } else + { + form_set_error('',t('Error: Unable to load content model %model_pid.',array('%model_pid'=>$model_pid))); + } + + + + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + return $form; +} + + +function icm_model_rollback_validate($form,&$form_state) +{ + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + if (($cm = ContentModel::loadFromModel($form_state['values']['form']['model_pid']))!==FALSE) + { + + $history = $cm->getHistory(); + $found = false; + foreach ($history as $key=>$ver) + { + if ($ver['versionID'] == $form_state['values']['form']['version']) + { + $found = $key; + break; + } + } + + if ($found === false) + { + form_set_error('',t('Error: Selected version was not found. Please try again.')); + } else if ($found == 0 && $form_state['values']['form']['removeOlder'] == 0) + { + form_set_error('',t('Error: Selected version is the current version. Nothing changed.')); + } else + { + $success=true; + if ($found > 0) + { + $startDate = $history[$found-1]['createDate']; + $success = $cm->purgeVersions($history[$found-1]['createDate'],null); + } + + if ($form_state['values']['form']['removeOlder'] == 1 && isset($history[$found+1])) + { + $endDate = $history[$found+1]['createDate']; + $success = $success && $cm->purgeVersions(null,$endDate); + } + + if ($success) + { + echo 'success:'.t('Successfully rolled back version of content model %cm_pid',array('%cm_pid'=>$cm->pid)); + exit(); + + } else + { + form_set_error('',t('Error: Unable to roll back version. Check watchdog logs.')); + } + } + + } else + { + form_set_error('',t('Error: Unable to load content model %cm_pid.',array('%cm_pid'=>htmlentities($form_state['values']['form']['model_pid'])))); + + } +} + +function icm_model_rollback(&$form_state,$params=null) +{ + + if (is_array($params)) + { + $model_pid = $params[0]; + } else + { + $model_pid = $form_state['post']['form']['model_pid']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Rollback Content Model %model_pid',array('%model_pid'=>$model_pid)), + '#tree' => TRUE, + ); + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + if (($cm = ContentModel::loadFromModel($model_pid))!==false) + { + + $history = $cm->getHistory(); + + $options = array(); + foreach ($history as $ver) + { + $options[$ver['versionID']] = date(DATE_RFC822,strtotime($ver['createDate'])); + } + + if ($history !== false && count($history) > 0) + { + + + $form['form']['warning'] = array('#value' => 'Warning: Rolling back a datastream will purge all versions up-to the selected datastream.'); + + + $form['form']['cur'] = array( + '#type'=> 'item', + '#title'=> t('Current Version:'), + '#value'=> date(DATE_RFC822,strtotime($history[0]['createDate'])) + ); + + $form['form']['version'] = array( + '#type' => 'select', + '#title' => t('Version'), + '#options' => $options + ); + + $form['form']['removeOlder'] = array( + '#type' => 'checkbox', + '#title' => 'Purge Older Versions', + '#description' => 'If enabled, also purges versions of the datastream that are OLDER than the selected, effectively leaving only the selected version.' + ); + + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + + } else + { + $form['form']['version'] = array( + '#type'=> 'item', + '#title'=> t('Rollback to Version:'), + '#value'=> t('only one version available.') + ); + } + + + + } else + { + form_set_error('',t('Error: Unable to load content model %model_pid.',array('%model_pid'=>$model_pid))); + } + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + return $form; +} + +function icm_model_purge_validate($form,&$form_state) +{ + + if ($form_state['values']['form']['confirm'] == 0) + { + form_set_error('form][confirm',t('If you would like to purge the selected content model, please check the confirmation checkbox and try again.')); + } else + { + $model_pid = $form_state['values']['form']['model_pid']; + + module_load_include('inc','fedora_repository','api/fedora_item'); + module_load_include('inc','fedora_repository','ContentModel'); + + if (($cm = ContentModel::loadFromModel($model_pid))!==FALSE) + { + $fedoraItem = new Fedora_Item($model_pid); + + if ($fedoraItem->purge(t('Purged using Islandora Content Modeller.'))) + { + echo 'success:'.t('Successfully purged content model %model_pid.',array('%model_pid'=>$model_pid)); + exit(); + + } else + { + form_set_error('',t('Error: Purge failed. Please contact an administrator for assistance.')); + } + + } else + { + form_set_error('',t('Error: Unable to load specified content model.')); + } + + } +} + +function icm_model_purge(&$form_state,$params=null) +{ + + if (is_array($params)) + { + $model_pid = $params[0]; + } else + { + $model_pid = $form_state['post']['form']['model_pid']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Purge Content Model %model_pid',array('%model_pid'=>$model_pid)), + '#tree' => TRUE, + ); + + module_load_include('inc', 'fedora_repository', 'ContentModel'); + + if (($cm = ContentModel::loadFromModel($model_pid))!==false) + { + $form['form']['warning'] = array('#value' => 'Warning: Purging a content model will affect any objects and collections that reference the model. Once purged, the entire model will be permanently deleted.'); + + $form['form']['confirm'] = array('#type' => 'checkbox', + '#title' => t('Confirm purge of model %model_pid',array('%model_pid'=>$model_pid)), + '#default_vale' => 0, + '#required' => TRUE); + + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + + } else + { + form_set_error('',t('Error: Unable to load content model %model_pid.',array('%model_pid'=>$model_pid))); + } + + $form['form']['model_pid'] = array('#type' => 'hidden', '#value'=> $model_pid); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + return $form; +} + +function icm_model_new_submit($form,&$form_state) +{ + + //save the values for the current step into the storage array + $form_state['storage']['values'][$form_state['storage']['step']] = $form_state['values']; + + + if ($form_state['storage']['step'] == 2) + { + + module_load_include('inc','fedora_repository','api/fedora_item'); + module_load_include('inc','fedora_repository','ContentModel'); + + $item = Fedora_Item::ingest_new_item($form_state['storage']['values'][1]['form']['pid'],'A',$form_state['storage']['values'][1]['form']['name']); + $item->add_relationship('fedora-model:hasModel','info:fedora/fedora-system:ContentModel-3.0',FEDORA_MODEL_URI); + + switch ($form_state['storage']['values'][1]['form']['initialize']) + { + case 'blank': + //($pid,$name,$modelDsid, $defaultMimetype, $ingestFormDsid, $ingestFormPage, $ingestFormHideChooser, $ingestFormFile, $ingestFormClass, $ingestFormMethod, $ingestFormHandler) + $cm = ContentModel::ingestBlankModel($form_state['storage']['values'][1]['form']['pid'], + $form_state['storage']['values'][1]['form']['name'], + ContentModel::getDefaultDSID(), + $form_state['storage']['values'][2]['form']['type'], + $form_state['storage']['values'][2]['form']['dsid'], + $form_state['storage']['values'][2]['form']['page'], + $form_state['storage']['values'][2]['form']['hide_file_chooser']==1, + $form_state['storage']['values'][2]['from']['module'], + $form_state['storage']['values'][2]['form']['filename'], + $form_state['storage']['values'][2]['form']['class'], + $form_state['storage']['values'][2]['form']['method'], + $form_state['storage']['values'][2]['form']['handler']); + break; + + case 'model': + $cm = ContentModel::ingestFromModel($form_state['storage']['values'][1]['form']['pid'], + $form_state['storage']['values'][1]['form']['name'], + ContentModel::getDefaultDSID(), + $form_state['storage']['values'][2]['form']['model_pid']); + break; + + case 'file': + $cm = ContentModel::ingestFromFile($form_state['storage']['values'][1]['form']['pid'], + $form_state['storage']['values'][1]['form']['name'], + ContentModel::getDefaultDSID(), + drupal_get_path('module','fedora_repository').'/'.$form_state['storage']['values'][2]['form']['xml_filename']); + + + break; + } + + + + if ($cm !== false) + { + echo 'success:'.t('Successfully ingested new content model.'); + exit(); + }else + { + echo t('Error: Unable to ingest new content model. Please try again or contact an administrator.'); + + } + + } else + { + // check the button that was clicked and action the step chagne + $form_state['storage']['step']++; + + //tell Drupal we are redrawing the same form + $form_state['rebuild'] = TRUE; + + } +} + +function icm_model_new_validate($form, &$form_state) +{ + module_load_include('inc', 'fedora_repository', 'ContentModel'); + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + + switch ($form_state['storage']['step']) + { + case 1: + if (!ContentModel::validPid($form_state['values']['form']['pid'])) + { + form_set_error('form][pid', t('Error: Invalid persistant identifier. Please try again.')); + } else if (Fedora_Item::fedora_item_exists($form_state['values']['form']['pid'])) + { + form_set_error('form][pid', t('Error: Specified PID already exists. Please choose a different PID and try again.')); + } + break; + + + case 2: + if ($form_state['storage']['values'][1]['form']['initialize'] == 'blank') + { + if (!ContentModel::validDsid($form_state['values']['form']['dsid'])) + { + form_set_error('form][dsid', t('Error: Invalid datastream identifier. Please try again.')); + } + if (intval($form_state['values']['form']['page']) <= 0) + { + form_set_error('form][page', t('Error: Page must be a positive integer. Please try again.')); + } + + $module=$form_state['values']['form']['module']; + $file = $form_state['values']['form']['filename']; + $class= $form_state['values']['form']['class']; + $method=$form_state['values']['form']['method']; + $handler=$form_state['values']['form']['handler']; + $path = drupal_get_path('module',$module); + + if (empty($path) || !file_exists($path.'/'.$file)) + { + form_set_error('form][filename',t('Error: Selected plugin file not found. Please try again.')); + } else + { + require_once ($path.'/'.$file); + if (!class_exists($class)) + { + form_set_error('form][class',t('Error: Specified class does not exist in the plugin. Please try again.')); + } else + { + $obj = @new $class; + if (!method_exists($obj,$method)) + { + form_set_error('form][method',t('Error: Specified builder method does not exist in the specified class/plugin. Please try again.')); + } + if (!method_exists($obj,$handler)) + { + form_set_error('form][handler',t('Error: Specified handler method does not exist in the specified class/plugin. Please try again.')); + } + } + } + } + break; + + } + +} + +function icm_model_new(&$form_state,$params=null) +{ + $form['#multistep']= TRUE; + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Create/Install New Content Model'), + '#tree' => TRUE, + ); + + if (empty($form_state['storage']['step'])) + { + // we are coming in without a step, so default to step 1 + $form_state['storage']['step'] = 1; + } + + switch ($form_state['storage']['step']) + { + case 1: + $form['form']['pid'] = array( + '#type' => 'textfield', + '#title' => t('Persistent Identifier'), + '#size' => 30, + '#required' => TRUE, + '#maxsize' => 64, + '#description' => t('The persistent identifier that will be used to identify the new content model. Make sure to choose a namespace that you will have access to from islandora.'), + ); + + $form['form']['name'] = array( + '#type' => 'textfield', + '#title' => t('Name'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('A short human readable name for the content model.'), + ); + + $form['form']['initialize'] = array( + '#type' => 'select', + '#title' => t('Initialize Content Model'), + '#options' => array('blank'=>t('Start with a blank model'), + 'model'=>t('Clone an existing installed model'), + 'file'=>t('Load from a content model XML file')), + '#description' => t('Determines how the new content model is initialized.') + ); + + break; + + case 2: + + + $form['form']['#description'] = 'PID: '.$form_state['storage']['values'][1]['form']['pid'].'
'. + 'Name: '.$form_state['storage']['values'][1]['form']['name'].'

'; + + switch ($form_state['storage']['values'][1]['form']['initialize']) + { + case 'model': + $form['form']['#description'] .= t('Please select an content model below that will be used as the base for this new content model. The model name will be updated to the value entered in the previous step.'); + module_load_include('inc','fedora_repository','CollectionClass'); + module_load_include('inc','fedora_repository','ContentModel'); + $collectionHelper = new CollectionClass(); + + $options=array(); + $items = new SimpleXMLElement( $collectionHelper->getRelatedItems(variable_get('fedora_content_model_collection_pid','islandora:ContentModelCollection') ,null,null)); + for ($i = 0; $i < count($items->results->result); $i++) + { + list(,$pid)=preg_split('/\//',$items->results->result[$i]->object['uri']); + + $cm = ContentModel::loadFromModel($pid); + if ($cm !== false) + { + $options[$pid] = $items->results->result[$i]->title .' ('.$pid.')'; + } + } + + $form['form']['model_pid'] = array( + '#type' => 'select', + '#title' => t('Content Model'), + '#required' => TRUE, + '#options' => $options, + '#description' => t('The currently installed content model to clone.'), + ); + break; + + case 'file': + $form['form']['#description'] .= t('Please select an ISLANDORACM XML file that will be used as the base for this new content model.'); + + $plugin_path = drupal_get_path('module','fedora_repository').'/content_models'; + + $files = array(''); + if ( ($dir = opendir($plugin_path)) !== false) + { + while (($file = readdir($dir)) !== false) + { + if (preg_match('/\.xml$/',$file)) + { + $files['content_models/'.$file]='content_models/'.$file; + } + } + } + + $form['form']['xml_filename'] = array( + '#type' => 'select', + '#title' => t('File'), + '#options'=>$files, + '#required' => TRUE, + '#description' => t('The relative path of the file containing the desired content model datastream.'), + ); + + break; + + case 'blank': + default: + + $form['form']['#description'] .= t('The only additional required information that a model must have is the ingest form method/handler. Once the model has been added, please go in and add any additional mimetypes, datastreams and ingest rules and ingest form elements that are neccessary. '); + + + $form['form']['type'] = array( + '#type' => 'textfield', + '#title' => t('Mimetype'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('A content mimetype that can be ingested using this model.'), + ); + + $form['form']['dsid'] = array( + '#type' => 'textfield', + '#title' => t('Datastream Identifier'), + '#size' => 30, + '#maxlength' => 64, + '#default_value'=>$attr['dsid'], + '#required' => TRUE, + + '#description' => t('The datastream ID that stores the collected metadata from the form.'), + ); + + $form['form']['page'] = array( + '#type' => 'textfield', + '#title' => t('Page'), + '#size' => 3, + '#required' => TRUE, + '#default_value' => $attr['page'], + '#description' => t('??? not sure what this field is for. Cant find a reference to it, candidate for removal. '), + ); + + $form['form']['hide_file_chooser'] = array( + '#type' => 'checkbox', + '#title' => t('Hide File Chooser'), + '#size' => 3, + '#required' => TRUE, + '#default_value' => $attr['hide_file_chooser']?1:0, + '#description' => t('If enabled, the file choose will not be displayed in the ingest form for this model.'), + ); + + $form['form']['redirect'] = array( + '#type' => 'checkbox', + '#title' => t('Redirect on Ingest'), + '#size' => 3, + '#required' => TRUE, + '#default_value' => $attr['redirect']?1:0, + '#description' => t('If enabled, the user will be redirected to the collection view on successful ingest.'), + ); + + $form['form']['module'] = array( + '#type' => 'select', + '#title' => t('Module'), + '#element_validate' => array('icm_module_validate'), + '#options'=>icm_get_modules(), + '#required' => TRUE, + '#default_value'=>$builderMethod['module'], + '#description' => t('The name of the module containing the form builder plugin file.'), + ); + + $form['form']['filename'] = array( + '#type' => 'textfield', + '#title' => t('File'), + '#element_validate' => array('icm_filename_validate'), + '#required' => TRUE, + '#default_value'=>$builderMethod['file'], + '#description' => t('The relative path of the file containing the form builder/handler class/method.'), + ); + + $form['form']['class'] = array( + '#type' => 'textfield', + '#title' => t('Class'), + '#element_validate' => array('icm_class_validate'), + '#required' => TRUE, + '#default_value'=> $builderMethod['class'], + '#size'=>30, + '#description' => t('The name of the builder/handler class.'), + ); + $form['form']['method'] = array( + '#type' => 'textfield', + '#title' => t('Form Builder Method'), + '#element_validate' => array('icm_method_validate'), + '#size'=>30, + '#required' => TRUE, + '#default_value'=> $builderMethod['method'], + '#description' => t('The name of the class method to build the ingest form.'), + ); + + $form['form']['handler'] = array( + '#type' => 'textfield', + '#title' => t('Form Handler Method'), + '#element_validate' => array('icm_method_validate'), + '#size'=>30, + '#required' => TRUE, + '#default_value'=> $builderMethod['handler'], + '#description' => t('The name of the class method to handle the ingest form.'), + ); + + break; + } + + break; + + } + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + + return $form; +} + + + +function icm_collection_purge_validate($form,&$form_state) +{ + + if ($form_state['values']['form']['confirm'] == 0) + { + form_set_error('form][confirm',t('If you would like to purge the selected collection, please check the confirmation checkbox and try again.')); + } else + { + $cp_pid = $form_state['values']['form']['cp_pid']; + + module_load_include('inc','fedora_repository','api/fedora_item'); + module_load_include('inc','fedora_repository','CollectionPolicy'); + + if (($cp = CollectionPolicy::loadFromCollection($cp_pid))!==FALSE) + { + $fedoraItem = new Fedora_Item($cp_pid); + + if ($fedoraItem->purge(t('Purged using Islandora Content Modeller.'))) + { + echo 'success:'.t('Successfully purged collection %cp_pid.',array('%cp_pid'=>$cp_pid)); + exit(); + } else + { + form_set_error('',t('Error: Purge failed. Please contact an administrator for assistance.')); + } + + } else + { + form_set_error('',t('Error: Unable to load specified collection policy.')); + } + + } +} + +function icm_collection_purge(&$form_state,$params=null) +{ + + if (is_array($params)) + { + $cp_pid = $params[0]; + } else + { + $cp_pid = $form_state['post']['form']['cp_pid']; + } + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Purge Collection %cp_pid',array('%cp_pid'=>$cp_pid)), + '#tree' => TRUE, + ); + + module_load_include('inc', 'fedora_repository', 'CollectionPolicy'); + + if (($cm = CollectionPolicy::loadFromCollection($cp_pid))!==false) + { + $form['form']['warning'] = array('#value' => 'Warning: Purging a collection will orphan any objects contained in the collection. This can not be undone.'); + + $form['form']['confirm'] = array('#type' => 'checkbox', + '#title' => t('Confirm purge of collection %cp_pid',array('%cp_pid'=>$cp_pid)), + '#default_vale' => 0, + '#required' => TRUE); + + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + + } else + { + form_set_error('',t('Error: Unable to load collection %cp_pid.',array('%cp_pid'=>$cp_pid))); + } + + $form['form']['cp_pid'] = array('#type' => 'hidden', '#value'=> $cp_pid); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + return $form; +} + +function icm_collection_new_submit($form,&$form_state) +{ + + //save the values for the current step into the storage array + $form_state['storage']['values'][$form_state['storage']['step']] = $form_state['values']; + + + + if ($form_state['storage']['step'] == 2) + { + + module_load_include('inc','fedora_repository','api/fedora_item'); + module_load_include('inc','fedora_repository','CollectionPolicy'); + + $cp = false; + if (($parent_cp = CollectionPolicy::loadFromCollection($form_state['storage']['parent'])) !== FALSE) + { + $item = Fedora_Item::ingest_new_item($form_state['storage']['values'][1]['form']['pid'],'A',$form_state['storage']['values'][1]['form']['name']); + $item->add_relationship('fedora-model:hasModel','info:fedora/'.variable_get('fedora_collection_model_pid','islandora:collectionCModel') ,FEDORA_MODEL_URI); + $item->add_relationship($parent_cp->getRelationship(),$parent_cp->pid,RELS_EXT_URI); + + switch ($form_state['storage']['values'][1]['form']['initialize']) + { + case 'blank': + //($pid,$name,$modelDsid, $defaultMimetype, $ingestFormDsid, $ingestFormPage, $ingestFormHideChooser, $ingestFormFile, $ingestFormClass, $ingestFormMethod, $ingestFormHandler) + $cp = CollectionPolicy::ingestBlankPolicy($form_state['storage']['values'][1]['form']['pid'], + $form_state['storage']['values'][1]['form']['name'], + CollectionPolicy::getDefaultDSID(), + $form_state['storage']['values'][2]['form']['model']['pid'], + $form_state['storage']['values'][2]['form']['model']['namespace'], + $form_state['storage']['values'][2]['form']['relationship'], + $form_state['storage']['values'][2]['form']['term']['field'], + $form_state['storage']['values'][2]['form']['term']['value']); + break; + + case 'collection': + $cp = CollectionPolicy::ingestFromCollection($form_state['storage']['values'][1]['form']['pid'], + $form_state['storage']['values'][1]['form']['name'], + CollectionPolicy::getDefaultDSID(), + $form_state['storage']['values'][2]['form']['collection_pid']); + break; + + case 'file': + $cp = CollectionPolicy::ingestFromFile($form_state['storage']['values'][1]['form']['pid'], + $form_state['storage']['values'][1]['form']['name'], + CollectionPolicy::getDefaultDSID(), + drupal_get_path('module','fedora_repository').'/'.$form_state['storage']['values'][2]['form']['policy_filename']); + + + break; + } + } + + + if ($cp !== false) + { + echo 'success:'.t('Successfully ingested new collection.'); + exit(); + }else + { + echo t('Error: Unable to ingest new collection. Please try again or contact an administrator.'); + + } + + } else + { + // check the button that was clicked and action the step chagne + $form_state['storage']['step']++; + + //tell Drupal we are redrawing the same form + $form_state['rebuild'] = TRUE; + + } +} + +function icm_collection_new_validate($form, &$form_state) +{ + module_load_include('inc', 'fedora_repository', 'CollectionPolicy'); + module_load_include('inc', 'fedora_repository', 'ContentModel'); + module_load_include('inc', 'fedora_repository', 'api/fedora_item'); + + if ($cp = CollectionPolicy::loadFromCollection($form_state['storage']['parent']) === FALSE) + { + form_set_error('',t('Error: Unable to load parent collection %cp_pid. Please try adding to a different collection or contact an administrator.',array('%cp_pid',$form_state['storage']['parent']))); + } + + switch ($form_state['storage']['step']) + { + case 1: + if (!CollectionPolicy::validPid($form_state['values']['form']['pid'])) + { + form_set_error('form][pid', t('Error: Invalid persistant identifier. Please try again.')); + } else if (Fedora_Item::fedora_item_exists($form_state['values']['form']['pid'])) + { + form_set_error('form][pid', t('Error: Specified PID already exists. Please choose a different PID and try again.')); + } + break; + + + case 2: + switch ($form_state['storage']['values'][1]['form']['initialize'] ) + { + case 'blank': + if ($form_state['values']['form']['model']['pid'] != $form_state['storage']['values'][1]['form']['pid'] && ($cm = ContentModel::loadFromModel($form_state['values']['form']['model']['pid'])) === FALSE) + { + form_set_error('form][model][pid', t('Error: Specified content model could not be loaded. Please choose a different model or contact an administrator.')); + } + if (!ContentModel::validPid($form_state['values']['form']['model']['namespace'])) + { + form_set_error('form][model][namespace',t('Error: Invalid namespace format.')); + } + break; + + case 'collection': + if (!CollectionPolicy::validPid($form_state['values']['form']['collection_pid']) || ($cp = CollectionPolicy::loadFromCollection($form_state['values']['form']['collection_pid'])) === FALSE) + { + form_set_error('form][collection_pid', t('Error: Specified collection could not be loaded. Please choose a different collection or contact an administrator.')); + } + break; + + } + break; + + } + +} + +function icm_collection_new(&$form_state,$params=null) +{ + $form['#multistep']= TRUE; + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Create/Install New Collection'), + '#tree' => TRUE, + ); + + if (empty($form_state['storage']['step'])) + { + // we are coming in without a step, so default to step 1 + $form_state['storage']['step'] = 1; + $form_state['storage']['parent'] = $params[0]; + } + + switch ($form_state['storage']['step']) + { + case 1: + $form['form']['pid'] = array( + '#type' => 'textfield', + '#title' => t('Persistent Identifier'), + '#size' => 30, + '#required' => TRUE, + '#maxsize' => 64, + '#description' => t('The persistent identifier that will be used to identify the new collection. Make sure to choose a namespace that you will have access to from islandora.'), + ); + + $form['form']['name'] = array( + '#type' => 'textfield', + '#title' => t('Name'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('A short human readable name for the collection.'), + ); + + $form['form']['initialize'] = array( + '#type' => 'select', + '#title' => t('Initialize Collection Policy'), + '#options' => array('blank'=>t('Start with a blank policy'), + 'collection'=>t('Clone the policy from an existing collection'), + 'file'=>t('Load from a collection policy XML file')), + '#description' => t('Determines how the new collection policy is initialized.') + ); + + break; + + case 2: + + + $form['form']['#description'] = 'PID: '.$form_state['storage']['values'][1]['form']['pid'].'
'. + 'Name: '.$form_state['storage']['values'][1]['form']['name'].'

'; + + switch ($form_state['storage']['values'][1]['form']['initialize']) + { + case 'collection': + $form['form']['#description'] .= t('Please select a collection below whose collection policy will be used as the basis for the new collection. The collections name will be updated to the value entered in the previous step.'); + module_load_include('inc','fedora_repository','CollectionClass'); + module_load_include('inc','fedora_repository','ContentModel'); + + $form['form']['collection_pid'] = array( + '#type' => 'textfield', + '#title' => t('Collection'), + '#required' => TRUE, + '#size' => 30, + '#maxsize' => 64, + '#description' => t('The currently installed collection to clone.'), + ); + break; + + case 'file': + $form['form']['#description'] .= t('Please select an COLLECTION_POLICY XML file that will be used as the base for this new collection.'); + + $plugin_path = drupal_get_path('module','fedora_repository').'/collection_policies'; + $files = array(''); + if ( ($dir = opendir($plugin_path)) !== false) + { + while (($file = readdir($dir)) !== false) + { + if (preg_match('/\.xml$/',$file)) + { + $files['collection_policies/'.$file]='collection_policies/'.$file; + } + } + } + + $form['form']['policy_filename'] = array( + '#type' => 'select', + '#title' => t('File'), + '#options'=>$files, + '#required' => TRUE, + '#description' => t('The relative path of the file containing the desired collection policy datastream.'), + ); + + break; + + case 'blank': + default: + + $form['form']['#description'] .= t('The only additional required information that a collection must have is an allowed content model, relationship and default search term. Once the collection has been added, please go in and add any content models and search terms that are neccessary. '); + + module_load_include('inc','fedora_repository','CollectionClass'); + module_load_include('inc','fedora_repository','ContentModel'); + $collectionHelper = new CollectionClass(); + + $options=array($form_state['storage']['values'][1]['form']['pid']=> 'Self ('.$form_state['storage']['values'][1]['form']['pid'].')'); + $items = new SimpleXMLElement( $collectionHelper->getRelatedItems(variable_get('icm_model_collection_pid','islandora:ContentModelCollection') ,null,null)); + for ($i = 0; $i < count($items->results->result); $i++) + { + list(,$pid)=preg_split('/\//',$items->results->result[$i]->object['uri']); + + $cm = ContentModel::loadFromModel($pid); + if ($cm !== false) + { + $options[$pid] = $items->results->result[$i]->title .' ('.$pid.')'; + } + } + + $form['form']['relationship'] = array( + '#type' => 'textfield', + '#title'=> t('Relationship'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('The relationship to use for objects in this collection.'), + '#default_value' => 'isMemberOfCollection' + ); + + $form['form']['model'] = array( + '#type' => 'fieldset', + '#title' => t('Initial Content Model'), + '#tree' => TRUE, + ); + + $form['form']['model']['pid'] = array( + '#type' => 'select', + '#title' => t('Persistent Identifier'), + '#required' => TRUE, + '#options' => $options, + '#description' => t('The PID of the content model that can be ingested into this collection.'), + ); + + + $form['form']['model']['namespace'] = array( + '#type'=> 'textfield', + '#title'=> t('Namespace'), + '#description' => t('The base namespace for objects of this type injested into the collection. eg islandora:collection '), + '#size' => 30, + '#maxSize' => 60, + '#required' => TRUE + ); + + $form['form']['term'] = array( + '#type' => 'fieldset', + '#title' => t('Search Term'), + '#tree' => TRUE, + ); + + $form['form']['term']['field'] = array( + '#type' => 'textfield', + '#title' => t('Field'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('The name of the field in the DC to search.'), + ); + + $form['form']['term']['value'] = array( + '#type' => 'textfield', + '#title' => t('Label'), + '#size' => 30, + '#required' => TRUE, + '#description' => t('A descriptive label for the field displayed on the search form.'), + ); + + + break; + } + + break; + + } + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + + return $form; +} + + +function icm_collection_edit_validate($form,&$form_state) +{ + //only proceed if no errors have been found. + if (form_get_errors() !== NULL) + return; + + module_load_include('inc','fedora_repository','CollectionPolicy'); + + $c_pid = $form_state['values']['form']['collection_pid']; + + if (($cp = CollectionPolicy::loadFromCollection($c_pid)) !== FALSE) + { + if ($cp->setRelationship(trim($form_state['values']['form']['relationship'])) && $cp->setStagingArea(trim($form_state['values']['form']['staging_area'])) && $cp->saveToFedora()) + { + echo 'success:'.t('Successfully updated collection policy of %cp_pid%.',array('%cp_pid%'=>$cp->pid)); + exit(); + + } else + { + form_set_error('',t('Error: Unable to update collection policy of %cp_pid%.',array('%cp_pid%'=>$cp->pid))); + } + } +} + + +function icm_collection_edit(&$form_state,$params=null) +{ + + if (is_array($params) && isset($params[0])) + { + $collection_pid = $params[0]; + } else if (isset($form_state['post']['form']['collection_pid'])) + { + $collection_pid = $form_state['post']['form']['collection_pid']; + } + + $form['form'] = array( + '#type'=>'fieldset', + '#title'=> t('Edit collection %collection_pid', array('%collection_pid'=>$collection_pid)), + '#tree'=>TRUE + ); + + module_load_include('inc','fedora_repository','CollectionPolicy'); + if (($cp = CollectionPolicy::loadFromCollection($collection_pid))!==FALSE) + { + + $staging_area = $cp->getStagingArea(false); + if ($staging_area == false) + { + $staging_area = ''; + } + + $form['form']['relationship'] = array( + '#type' => 'textfield', + '#title' => t('Relationship'), + '#required' => TRUE, + '#default_value' => $cp->getRelationship(), + '#size' => 30, + '#maxSize' => 60, + '#description' => t('The relationship to use for members of this collection.'), + ); + + $form['form']['staging_area'] = array( + '#type'=> 'textfield', + '#title'=> t('Staging Area'), + '#default_value'=> $staging_area, + '#description' => t('The path to the staging area to use when ingesting files into this collection. If left blank, the staging area of the parent collection will be used. Please do not include a trailing slash.'), + '#size' => 30, + '#maxSize' => 60 + ); + + $form['form']['collection_pid'] = array('#type' => 'hidden', '#value'=> $collection_pid); + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + } else + { + form_set_error('',t('Error: Unable to load requested collection_policy.')); + } + + + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + + return $form; +} + +function /*icm*/_model_service_add(&$form_state,$params=null) +{ + + if (empty($form_state['storage']['step'])) + { + // we are coming in without a step, so default to step 1 + $form_state['storage']['step'] = 1; + $form_state['storage']['cm_pid'] = $params[0]; + } + + $cm_pid = $form_state['storage']['cm_pid']; + + $form['#multistep']= TRUE; + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Add Service to Content Model %cm_pid.',array('%cm_pid'=>$cm_pid)), + '#tree' => TRUE, + ); + + + switch ($form_state['storage']['step']) + { + case 1: + + module_load_include('inc','fedora_repository','CollectionClass'); + $collectionHelper = new CollectionClass(); + + $options=array(); + $items = new SimpleXMLElement( $collectionHelper->getRelatedItems(variable_get('fedora_service_def_collection_pid','uofm:serviceDefCollection') ,null,null)); + for ($i = 0; $i < count($items->results->result); $i++) + { + list(,$pid)=preg_split('/\//',$items->results->result[$i]->object['uri']); + + $cm = ContentModel::loadFromModel($pid); + if ($cm !== false) + { + $options[$pid] = $items->results->result[$i]->title .' ('.$pid.')'; + } + } + + $form['form'] = array( + '#type'=>'fieldset', + '#title'=> t('Add content model to collection %collection_pid', array('%collection_pid'=>$collection_pid)), + '#tree'=>TRUE + ); + + $form['form']['model_pid'] = array( + '#type' => 'select', + '#title' => t('Content Model'), + '#required' => TRUE, + '#options' => $options, + '#description' => t('A descriptive label for the field displayed on the search form.'), + ); + + $form['form']['namespace'] = array( + '#type'=> 'textfield', + '#title'=> t('Namespace'), + '#description' => t('The base namespace for objects of this type injested into the collection. eg islandora:collection '), + '#size' => 30, + '#maxSize' => 60, + '#required' => TRUE + ); + break; + + } + $form['form']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Cancel'), '#id'=>'cancel'); + + return $form; +} + +function icm_display_rawXml(&$form_state,$params=null) +{ + + $form['form'] = array( + '#type' => 'fieldset', + '#title' => t('Display Raw XML'), + '#tree' => TRUE, + ); + + if (is_array($params)) + { + $type = $params[0]; + $pid = $params[1]; + + switch (strtolower($type)) + { + case 'model': + module_load_include('inc', 'fedora_repository', 'ContentModel'); + if (($cm = ContentModel::loadFromModel($pid))!==false) + { + $form['form']['pid'] = array( + '#type'=>'item', + '#title'=>t('PID'), + '#value'=>$pid + ); + $form['form']['pre'] = array( + '#value'=>'
'.htmlentities($cm->dumpXml()).'
' + ); + } else + { + form_set_error('',t('Error: Unable to load requested content model.')); + } + break; + + case 'collection': + module_load_include('inc', 'fedora_repository', 'CollectionPolicy'); + if (($cp = CollectionPolicy::loadFromCollection($pid))!==false) + { + $form['form']['pid'] = array( + '#type'=>'item', + '#title'=>t('PID'), + '#value'=>$pid + ); + $form['form']['pre'] = array( + '#value'=>'
'.htmlentities($cp->dumpXml()).'
' + ); + } else + { + form_set_error('',t('Error: Unable to load requested collection policy.')); + } + break; + } + + } else + { + form_set_error('',t('Error: Unknown XML Datastream Specified.')); + } + $form['form']['cancel'] = array('#type' => 'button', '#value' => t('Close'), '#id'=>'cancel'); + + return $form; +} + diff --git a/content_modeller/js/content_modeller.js b/content_modeller/js/content_modeller.js new file mode 100644 index 00000000..3d815111 --- /dev/null +++ b/content_modeller/js/content_modeller.js @@ -0,0 +1,271 @@ + var collection = new Array('Root'); + var model = false; + +$(document).ready(function () { + + $(document).ajaxStart(function(){ + $('#ajaxBusy').show(); + }).ajaxStop(function(){ + $('#ajaxBusy').hide(); + }); + + $('#Notification').jnotifyInizialize({ + oneAtTime: false, + appendType: 'append' + }) + .css({ + 'marginTop': '20px', + 'left': '20px', + 'width': '500px', + 'z-index': '9999' + }); + + updateCollectionTree(); + updateModelList(); + + $('.refresh').click(function () { + $('#Notification').jnotifyAddMessage({text: 'Refreshed collection '+collection[collection.length-1]+' and model '+(model==false?'list':model)+'.', permanent: false}); + updateCollectionTree(); + if (model == false) + updateModelList(); + else + updateModelTree(); + return false; + }); + + $('.list').click(function () { + $('#Notification').jnotifyAddMessage({text: 'Refreshed model list.', permanent: false}); + model = false; + updateModelList(); + return false; + }); + + + function updateBreadcrumbs() + { + var content = ''; + for (var i = 0; i < collection.length; i++) + { + content += ''+collection[i]+' /'; + } + $('#collection_crumbs').html(content); + } + + function handleForm() + { + $('#edit-form-module').change(function () { + $.get('modeller/ajax/getFiles/', { module: $('#edit-form-module').val() }, function (data) { + $('#edit-form-filename-select').html(data); + }); + $('#edit-form-class-select, #edit-form-method-select, #edit-form-handler-select').html(''); + }); + + $('.form-item #edit-form-filename').hide(); + $('.form-item #edit-form-filename').before(''); + $('#edit-form-filename-select').change(function () { + $('#edit-form-filename').val($('#edit-form-filename-select').val()); + $.get('modeller/ajax/getClasses/', { module: $('#edit-form-module').val(), + file: $('#edit-form-filename').val(), + className: $('#edit-form-class').val() }, + function (data) { $('#edit-form-class-select').html(data); }); + }); + + + $('.form-item #edit-form-class').hide(); + $('.form-item #edit-form-class').before(''); + $('#edit-form-class-select').change(function () { + $('#edit-form-class').val($('#edit-form-class-select').val()); + $.get('modeller/ajax/getMethods/', { module: $('#edit-form-module').val(), + file: $('#edit-form-filename').val(), + className: $('#edit-form-class').val() }, + function (data) { + $('#edit-form-method-select').html(data); + $('#edit-form-handler-select').html(data); + }); + }); + + if ($('#edit-form-file').val() || $('#edit-form-class').val() || $('#edit-form-filename').val()) + { + $.get('modeller/ajax/getFiles/', { module: $('#edit-form-module').val(), file: $('#edit-form-filename').val() }, function (data) { + $('#edit-form-filename-select').html(data); + }); + $.get('modeller/ajax/getClasses/', { module: $('#edit-form-module').val(), file: $('#edit-form-filename').val(), className: $('#edit-form-class').val() }, function (data) { + $('#edit-form-class-select').html(data); + }); + } + + $('.form-item #edit-form-method, .form-item #edit-form-handler').hide(); + $('.form-item #edit-form-method').before(''); + $('.form-item #edit-form-handler').before(''); + $('#edit-form-method-select').change(function () { + $('#edit-form-method').val($('#edit-form-method-select').val()); + }); + $('#edit-form-handler-select').change(function () { + $('#edit-form-handler').val($('#edit-form-handler-select').val()); + }); + + + if ( $('#edit-form-class').val()) + { + $.get('modeller/ajax/getMethods/', { module: $('#edit-form-module').val(), file: $('#edit-form-filename').val(), className: $('#edit-form-class').val(), method: $('#edit-form-method').val() }, function (data) { + $('#edit-form-method-select').html(data); + }); + $.get('modeller/ajax/getMethods/', { module: $('#edit-form-module').val(), file: $('#edit-form-filename').val(), className: $('#edit-form-class').val(), method: $('#edit-form-handler').val() }, function (data) { + $('#edit-form-handler-select').html(data); + }); + } + + $('#ajaxForm #cancel').click(function (d) { + $('#ajaxForm').fadeOut(); + return false; + }); + + $('#ajaxForm form').submit(function (d) { + $.post(d.target.action, + $('#'+d.target.id).serialize()+'&op=Save', + function (data) + { + lines = data.split(':'); + if (lines.shift() == 'success') + { + $('#Notification').jnotifyAddMessage({text: lines.join(':'), permanent: false}); + $('#ajaxForm').fadeOut(); + if (model == false) + updateModelList(); + else + updateModelTree(); + updateCollectionTree(); + } else + { + $('#ajaxForm').html(data); + handleForm(); + } + }); + return false; + }); + } + + function buttonIcons() + { + $('.ajaxButtonIcon').unbind(); + $('.ajaxButtonIcon').click(function () { + $.get('modeller/ajax/button', { formReq: this.id}, function (data) { + lines = data.split(':'); + if (lines.shift() == 'success') + { + $('#Notification').jnotifyAddMessage({text: lines.join(':'), permanent: false}); + if (model == false) + updateModelList(); + else + updateModelTree(); + updateCollectionTree(); + } else + { + $('#Notification').jnotifyAddMessage({text: data, permanent: false, type: 'error'}); + } + }); + + }); + } + + function formIcons() + { + $('.ajaxFormIcon').unbind(); + $('.ajaxFormIcon').click(function () { + var params=this.id.split(' '); + var formName=params.shift(); + $.get('modeller/ajax/processForm/'+formName, { formReq: params.join(' ')}, function (data) { + if (data == '') + { + $('#Notification').jnotifyAddMessage({text: 'Error: Unable to load requested form \''+formName+'\'.', permanent: false, type: 'error'}); + } else + { + $('#ajaxForm').html(data).fadeIn(); + handleForm(); + } + }); + + return false; + }); + } + + function updateModelList() { + $.get('modeller/ajax/listModels' , function (j) { + $('#model_tree').html(j); + $('#model_tree ul').treeview({ animated: "fast", + collapsed: true, + unique: false, + persist: "cookie", + cookieId: "modelTree"}); + + $(".list_model").click( function () { + model = this.id; + updateModelTree(); + $('#Notification').jnotifyAddMessage({text: 'Displayed model '+this.id, permanent: false}); + return false; + }); + + buttonIcons(); + formIcons(); + + }); + } + + function updateModelTree() { + $.get('modeller/ajax/model', { model_pid: model} , function (j) { + $('#model_tree').html(j); + $('#model_tree ul').treeview({ animated: "fast", + collapsed: true, + unique: false, + persist: "cookie", + cookieId: "modelTree"}); + + buttonIcons(); + formIcons(); + + }); + } + + function updateCollectionTree() { + collection_pid = collection[collection.length-1]; + if (collection_pid == 'Root') + collection_pid = false; + + $.get('modeller/ajax/collection', { collection_pid: collection_pid} , function (j) { + updateBreadcrumbs(); + + $('#collection_tree').html(j); + $('#collection_tree ul').treeview({ animated: "fast", + collapsed: true, + unique: false, + persist: "cookie", + cookieId: "collectionTree" }); + + buttonIcons(); + formIcons(); + + $(".collection_model").click( function () { + model = this.id; + updateModelTree(); + $('#Notification').jnotifyAddMessage({text: 'Displayed model '+this.id, permanent: false}); + return false; + }); + + $(".collection_child").click( function () { + collection.push(this.id); + updateCollectionTree(); + $('#Notification').jnotifyAddMessage({text: 'Switched to collection '+this.id, permanent: false}); + return false; + }); + + $(".collection_crumb").click( function () { + var pop_no = collection.length-this.id-1; + for (var i =0; i < pop_no; i++) + collection.pop(); + updateCollectionTree(); + $('#Notification').jnotifyAddMessage({text: 'Switched to collection '+collection[collection.length-1], permanent: false}); + return false; + }); + }); + } + }); diff --git a/content_modeller/js/jquery.cookie.js b/content_modeller/js/jquery.cookie.js new file mode 100644 index 00000000..8e8e1d9e --- /dev/null +++ b/content_modeller/js/jquery.cookie.js @@ -0,0 +1,92 @@ +/** + * Cookie plugin + * + * Copyright (c) 2006 Klaus Hartl (stilbuero.de) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ + +/** + * Create a cookie with the given name and value and other optional parameters. + * + * @example $.cookie('the_cookie', 'the_value'); + * @desc Set the value of a cookie. + * @example $.cookie('the_cookie', 'the_value', {expires: 7, path: '/', domain: 'jquery.com', secure: true}); + * @desc Create a cookie with all available options. + * @example $.cookie('the_cookie', 'the_value'); + * @desc Create a session cookie. + * @example $.cookie('the_cookie', null); + * @desc Delete a cookie by passing null as value. + * + * @param String name The name of the cookie. + * @param String value The value of the cookie. + * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. + * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. + * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. + * If set to null or omitted, the cookie will be a session cookie and will not be retained + * when the the browser exits. + * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). + * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). + * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will + * require a secure protocol (like HTTPS). + * @type undefined + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ + +/** + * Get the value of a cookie with the given name. + * + * @example $.cookie('the_cookie'); + * @desc Get the value of a cookie. + * + * @param String name The name of the cookie. + * @return The value of the cookie. + * @type String + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ +jQuery.cookie = function(name, value, options) { + if (typeof value != 'undefined') { // name and value given, set cookie + options = options || {}; + if (value === null) { + value = ''; + options.expires = -1; + } + var expires = ''; + if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { + var date; + if (typeof options.expires == 'number') { + date = new Date(); + date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); + } else { + date = options.expires; + } + expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE + } + var path = options.path ? '; path=' + options.path : ''; + var domain = options.domain ? '; domain=' + options.domain : ''; + var secure = options.secure ? '; secure' : ''; + document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); + } else { // only name given, get cookie + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } +}; \ No newline at end of file diff --git a/content_modeller/js/jquery.jnotify.js b/content_modeller/js/jquery.jnotify.js new file mode 100644 index 00000000..0366e791 --- /dev/null +++ b/content_modeller/js/jquery.jnotify.js @@ -0,0 +1,143 @@ +/** +* jQuery.jNotify +* jQuery Notification Engine +* +* Copyright (c) 2010 Fabio Franzini +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notify and this permission notify shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +* +* @author Fabio Franzini +* @copyright 2010 www.fabiofranzini.com +* @version 1 +**/ + +(function(jQuery) { + jQuery.fn.jnotifyInizialize = function(options) { + var element = this; + + var defaults = { + oneAtTime: false, + appendType: 'append' + }; + + var options = jQuery.extend({}, defaults, options); + + this.addClass('notify-wrapper'); + + if (options.oneAtTime) + this.addClass('notify-wrapper-oneattime'); + + if (options.appendType == 'prepend' && options.oneAtTime == false) + this.addClass('notify-wrapper-prepend'); + + return this; + }; + jQuery.fn.jnotifyAddMessage = function(options) { + + var notifyWrapper = this; + + if (notifyWrapper.hasClass('notify-wrapper')) { + + var defaults = { + text: '', + type: 'message', + showIcon: true, + permanent: false, + disappearTime: 3000 + }; + + var options = jQuery.extend({}, defaults, options); + var styleClass; + var iconClass; + + switch (options.type) { + case 'message': + { + styleClass = 'ui-state-highlight'; + iconClass = 'ui-icon-info'; + } + break; + case 'error': + { + styleClass = 'ui-state-error'; + iconClass = 'ui-icon-alert'; + } + break; + default: + { + styleClass = 'ui-state-highlight'; + iconClass = 'ui-icon-info'; + } + break; + } + + if (notifyWrapper.hasClass('notify-wrapper-oneattime')) { + this.children().remove(); + } + + var notifyItemWrapper = jQuery('
'); + var notifyItem = jQuery('
') + .addClass(styleClass); + + if (notifyWrapper.hasClass('notify-wrapper-prepend')) + notifyItem.prependTo(notifyWrapper); + else + notifyItem.appendTo(notifyWrapper); + + notifyItem.wrap(notifyItemWrapper); + + if (options.showIcon) + jQuery('') + .addClass(iconClass) + .appendTo(notifyItem); + + jQuery('').html(options.text).appendTo(notifyItem); + jQuery('
') + .prependTo(notifyItem) + .click(function() { remove(notifyItem) }); + + // IEsucks + if (navigator.userAgent.match(/MSIE (\d+\.\d+);/)) { + notifyWrapper.css({ top: document.documentElement.scrollTop }); + //http://groups.google.com/group/jquery-dev/browse_thread/thread/ba38e6474e3e9a41 + notifyWrapper.removeClass('IEsucks'); + } + // ------ + + if (!options.permanent) { + setTimeout(function() { remove(notifyItem); }, options.disappearTime); + } + } + + function remove(obj) { + obj.animate({ opacity: '0' }, 600, function() { + obj.parent().animate({ height: '0px' }, 300, + function() { + obj.parent().remove(); + // IEsucks + if (navigator.userAgent.match(/MSIE (\d+\.\d+);/)) { + //http://groups.google.com/group/jquery-dev/browse_thread/thread/ba38e6474e3e9a41 + obj.parent().parent().removeClass('IEsucks'); + } + // ------- + }); + }); + } + }; +})(jQuery); \ No newline at end of file diff --git a/content_modeller/js/jquery.treeview.min.js b/content_modeller/js/jquery.treeview.min.js new file mode 100644 index 00000000..be38dfee --- /dev/null +++ b/content_modeller/js/jquery.treeview.min.js @@ -0,0 +1,254 @@ +/* +* Treeview 1.4 - jQuery plugin to hide and show branches of a tree +* +* http://bassistance.de/jquery-plugins/jquery-plugin-treeview/ +* http://docs.jquery.com/Plugins/Treeview +* +* Copyright (c) 2007 Jörn Zaefferer +* +* Dual licensed under the MIT and GPL licenses: +* http://www.opensource.org/licenses/mit-license.php +* http://www.gnu.org/licenses/gpl.html +* +* Revision: $Id: jquery.treeview.js 4684 2008-02-07 19:08:06Z joern.zaefferer $ +* +*/ + +;(function($) { + + $.extend($.fn, { + swapClass: function(c1, c2) { + var c1Elements = this.filter('.' + c1); + this.filter('.' + c2).removeClass(c2).addClass(c1); + c1Elements.removeClass(c1).addClass(c2); + return this; + }, + replaceClass: function(c1, c2) { + return this.filter('.' + c1).removeClass(c1).addClass(c2).end(); + }, + hoverClass: function(className) { + className = className || "hover"; + return this.hover(function() { + $(this).addClass(className); + }, function() { + $(this).removeClass(className); + }); + }, + heightToggle: function(animated, callback) { + animated ? + this.animate({ height: "toggle" }, animated, callback) : + this.each(function(){ + jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ](); + if(callback) + callback.apply(this, arguments); + }); + }, + heightHide: function(animated, callback) { + if (animated) { + this.animate({ height: "hide" }, animated, callback); + } else { + this.hide(); + if (callback) + this.each(callback); + } + }, + prepareBranches: function(settings) { + if (!settings.prerendered) { + // mark last tree items + this.filter(":last-child:not(ul)").addClass(CLASSES.last); + // collapse whole tree, or only those marked as closed, anyway except those marked as open + this.filter((settings.collapsed ? "" : "." + CLASSES.closed) + ":not(." + CLASSES.open + ")").find(">ul").hide(); + } + // return all items with sublists + return this.filter(":has(>ul)"); + }, + applyClasses: function(settings, toggler) { + + this.filter(":has(>ul):not(:has(>a))").find(">span").click(function(event) { + toggler.apply($(this).next()); + }).add( $("a", this) ).hoverClass(); + + if (!settings.prerendered) { + // handle closed ones first + this.filter(":has(>ul:hidden)") + .addClass(CLASSES.expandable) + .replaceClass(CLASSES.last, CLASSES.lastExpandable); + + // handle open ones + this.not(":has(>ul:hidden)") + .addClass(CLASSES.collapsable) + .replaceClass(CLASSES.last, CLASSES.lastCollapsable); + + // create hitarea + this.prepend("
").find("div." + CLASSES.hitarea).each(function() { + var classes = ""; + $.each($(this).parent().attr("class").split(" "), function() { + classes += this + "-hitarea "; + }); + $(this).addClass( classes ); + }); + } + + // apply event to hitarea + this.find("div." + CLASSES.hitarea).click( toggler ); + }, + treeview: function(settings) { + + if (!settings.cookieId) { + settings = $.extend({ + cookieId: "treeview" + }, settings); + } + + if (settings.add) { + return this.trigger("add", [settings.add]); + } + + if ( settings.toggle ) { + var callback = settings.toggle; + settings.toggle = function() { + return callback.apply($(this).parent()[0], arguments); + }; + } + + // factory for treecontroller + function treeController(tree, control) { + // factory for click handlers + function handler(filter) { + return function() { + // reuse toggle event handler, applying the elements to toggle + // start searching for all hitareas + toggler.apply( $("div." + CLASSES.hitarea, tree).filter(function() { + // for plain toggle, no filter is provided, otherwise we need to check the parent element + return filter ? $(this).parent("." + filter).length : true; + }) ); + return false; + }; + } + // click on first element to collapse tree + $("a:eq(0)", control).click( handler(CLASSES.collapsable) ); + // click on second to expand tree + $("a:eq(1)", control).click( handler(CLASSES.expandable) ); + // click on third to toggle tree + $("a:eq(2)", control).click( handler() ); + } + + // handle toggle event + function toggler() { + $(this) + .parent() + // swap classes for hitarea + .find(">.hitarea") + .swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea ) + .swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea ) + .end() + // swap classes for parent li + .swapClass( CLASSES.collapsable, CLASSES.expandable ) + .swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable ) + // find child lists + .find( ">ul" ) + // toggle them + .heightToggle( settings.animated, settings.toggle ); + if ( settings.unique ) { + $(this).parent() + .siblings() + // swap classes for hitarea + .find(">.hitarea") + .replaceClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea ) + .replaceClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea ) + .end() + .replaceClass( CLASSES.collapsable, CLASSES.expandable ) + .replaceClass( CLASSES.lastCollapsable, CLASSES.lastExpandable ) + .find( ">ul" ) + .heightHide( settings.animated, settings.toggle ); + } + } + + function serialize() { + function binary(arg) { + return arg ? 1 : 0; + } + var data = []; + branches.each(function(i, e) { + data[i] = $(e).is(":has(>ul:visible)") ? 1 : 0; + }); + $.cookie(settings.cookieId, data.join("") ); + } + + function deserialize() { + var stored = $.cookie(settings.cookieId); + if ( stored ) { + var data = stored.split(""); + branches.each(function(i, e) { + $(e).find(">ul")[ parseInt(data[i]) ? "show" : "hide" ](); + }); + } + } + + // add treeview class to activate styles + this.addClass("treeview"); + + // prepare branches and find all tree items with child lists + var branches = this.find("li").prepareBranches(settings); + + switch(settings.persist) { + case "cookie": + var toggleCallback = settings.toggle; + settings.toggle = function() { + serialize(); + if (toggleCallback) { + toggleCallback.apply(this, arguments); + } + }; + deserialize(); + break; + case "location": + var current = this.find("a").filter(function() { return this.href.toLowerCase() == location.href.toLowerCase(); }); + if ( current.length ) { + current.addClass("selected").parents("ul, li").add( current.next() ).show(); + } + break; + } + + branches.applyClasses(settings, toggler); + + // if control option is set, create the treecontroller and show it + if ( settings.control ) { + treeController(this, settings.control); + $(settings.control).show(); + } + + return this.bind("add", function(event, branches) { + $(branches).prev() + .removeClass(CLASSES.last) + .removeClass(CLASSES.lastCollapsable) + .removeClass(CLASSES.lastExpandable) + .find(">.hitarea") + .removeClass(CLASSES.lastCollapsableHitarea) + .removeClass(CLASSES.lastExpandableHitarea); + $(branches).find("li").andSelf().prepareBranches(settings).applyClasses(settings, toggler); + }); + } + }); + + // classes used by the plugin + // need to be styled via external stylesheet, see first example + var CLASSES = $.fn.treeview.classes = { + open: "open", + closed: "closed", + expandable: "expandable", + expandableHitarea: "expandable-hitarea", + lastExpandableHitarea: "lastExpandable-hitarea", + collapsable: "collapsable", + collapsableHitarea: "collapsable-hitarea", + lastCollapsableHitarea: "lastCollapsable-hitarea", + lastCollapsable: "lastCollapsable", + lastExpandable: "lastExpandable", + last: "last", + hitarea: "hitarea" + }; + + // provide backwards compability + $.fn.Treeview = $.fn.treeview; + +})(jQuery); \ No newline at end of file diff --git a/content_modeller/treeview.inc b/content_modeller/treeview.inc new file mode 100644 index 00000000..f5fa751b --- /dev/null +++ b/content_modeller/treeview.inc @@ -0,0 +1,56 @@ +type = $type; + $this->content = $content; + $this->id = $id; + $this->icons = $icons; + } + + function buildTree($class = NULL, $includeul = TRUE) { + $ret = ''; + if ($includeul) + $ret .= '