Index: site-manage-tool/tool/pom.xml =================================================================== --- site-manage-tool/tool/pom.xml (revision 308016) +++ site-manage-tool/tool/pom.xml (working copy) @@ -98,6 +98,10 @@ servlet-api + commons-io + commons-io + + org.jmock jmock-legacy 2.5.1 Index: site-manage-tool/tool/src/bundle/sitesetupgeneric.properties =================================================================== --- site-manage-tool/tool/src/bundle/sitesetupgeneric.properties (revision 308016) +++ site-manage-tool/tool/src/bundle/sitesetupgeneric.properties (working copy) @@ -1170,6 +1170,22 @@ importQueued=Your import process has been queued. You should receive an email when the import has completed. The email should appear within 15 minutes. Until you receive this email no imported material will appear in this course. importQueuedNoEmail=Your import process has been queued. Please allow up to 15 minutes for the site import process to finish. +# for creating site from archive +archive.createsite = Create site from archive +archive.createsite.info = Creating a site from Archive allows you to upload an existing site archive as a base for your new site. +archive.createsite.info.2 = Content from the site archive will be imported into the new site. +archive.createsite.type = New site type +archive.createsite.term = New site term +archive.createsite.choose = Choose archive +archive.createsite.upload = Upload archive +archive.createsite.missingfile = You must upload a site archive zip file +archive.createsite.invalidfile = Invalid file +archive.createsite.missingtype = You must select the new site type +archive.createsite.failedupload = An error occurred uploading the archive. +archive.createsite.failedmerge = An error occurred merging the archive contents. +archive.createsite.confirm = Creating site from archive +archive.createsite.confirm.none = None + ## bjones86 - SAK-24423 - joinable site settings ediacc.joinNotification = Notify site maintainers by email when a user joins this site ediacc.joinExcludeFromPublic = Exclude this site from the publicly available list of joinable sites Index: site-manage-tool/tool/src/java/org/sakaiproject/site/tool/SiteAction.java =================================================================== --- site-manage-tool/tool/src/java/org/sakaiproject/site/tool/SiteAction.java (revision 308016) +++ site-manage-tool/tool/src/java/org/sakaiproject/site/tool/SiteAction.java (working copy) @@ -20,7 +20,6 @@ **********************************************************************************/ package org.sakaiproject.site.tool; -import java.util.LinkedHashMap; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -36,6 +35,7 @@ import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; import java.util.Locale; @@ -51,6 +51,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.WildcardFileFilter; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; @@ -69,6 +70,7 @@ import org.sakaiproject.authz.api.PermissionsHelper; import org.sakaiproject.authz.api.Role; import org.sakaiproject.authz.api.SecurityAdvisor; +import org.sakaiproject.authz.api.SecurityAdvisor.SecurityAdvice; import org.sakaiproject.authz.cover.AuthzGroupService; import org.sakaiproject.authz.cover.SecurityService; import org.sakaiproject.cheftool.Context; @@ -111,6 +113,7 @@ import org.sakaiproject.id.cover.IdManager; import org.sakaiproject.importer.api.ImportDataSource; import org.sakaiproject.importer.api.ImportService; +import org.sakaiproject.importer.api.ResetOnCloseInputStream; import org.sakaiproject.importer.api.SakaiArchive; import org.sakaiproject.importer.api.ResetOnCloseInputStream; import org.sakaiproject.javax.PagingPosition; @@ -127,8 +130,8 @@ import org.sakaiproject.site.util.SiteParticipantHelper; import org.sakaiproject.site.util.SiteSetupQuestionFileParser; import org.sakaiproject.site.util.SiteTextEditUtil; +import org.sakaiproject.site.util.SiteTypeUtil; import org.sakaiproject.site.util.ToolComparator; -import org.sakaiproject.site.util.SiteTypeUtil; import org.sakaiproject.sitemanage.api.SectionField; import org.sakaiproject.sitemanage.api.SiteHelper; import org.sakaiproject.sitemanage.api.model.SiteSetupQuestion; @@ -151,9 +154,7 @@ import org.sakaiproject.userauditservice.api.UserAuditRegistration; import org.sakaiproject.userauditservice.api.UserAuditService; import org.sakaiproject.util.*; - // for basiclti integration -import org.sakaiproject.lti.api.LTIService; /** @@ -195,6 +196,9 @@ private org.sakaiproject.authz.api.AuthzGroupService authzGroupService = (org.sakaiproject.authz.api.AuthzGroupService) ComponentManager .get(org.sakaiproject.authz.api.AuthzGroupService.class); + private org.sakaiproject.archive.api.ArchiveService archiveService = (org.sakaiproject.archive.api.ArchiveService) ComponentManager + .get(org.sakaiproject.archive.api.ArchiveService.class); + private org.sakaiproject.sitemanage.api.SectionFieldProvider sectionFieldProvider = (org.sakaiproject.sitemanage.api.SectionFieldProvider) ComponentManager .get(org.sakaiproject.sitemanage.api.SectionFieldProvider.class); @@ -285,7 +289,8 @@ "-siteInfo-importSelection", //58 "-siteInfo-importMigrate", //59 "-importSitesMigrate", //60 - "-siteInfo-importUser" + "-siteInfo-importUser", + "-uploadArchive" }; /** Name of state attribute for Site instance id */ @@ -384,6 +389,8 @@ private static final String STATE_PROJECT_SITE_TYPE = "project"; + private static final String STATE_SITE_IMPORT_ARCHIVE = "canImportArchive"; + // SAK-23468 private static final String STATE_NEW_SITE_STATUS_ISPUBLISHED = "newSiteStatusIsPublished"; private static final String STATE_NEW_SITE_STATUS_TITLE = "newSiteStatusTitle"; @@ -758,6 +765,10 @@ private String moreInfoPath; private String libraryPath; + private static final String STATE_CREATE_FROM_ARCHIVE = "createFromArchive"; + private static final String STATE_UPLOADED_ARCHIVE_PATH = "uploadedArchivePath"; + private static final String STATE_UPLOADED_ARCHIVE_NAME = "uploadedArchiveNAme"; + private static UserAuditRegistration userAuditRegistration = (UserAuditRegistration) ComponentManager.get("org.sakaiproject.userauditservice.api.UserAuditRegistration.sitemanage"); private static UserAuditService userAuditService = (UserAuditService) ComponentManager.get(UserAuditService.class); @@ -1400,6 +1411,9 @@ context.put("projectSiteType", STATE_PROJECT_SITE_TYPE); context.put(STATE_SITE_ADD_PROJECT, SiteService.allowAddProjectSite()); + // can the user user create sites from archives? + context.put(STATE_SITE_IMPORT_ARCHIVE, SiteService.allowImportArchiveSite()); + Site site = getStateSite(state); @@ -1808,6 +1822,8 @@ .getRequiredFields()); context.put("fieldValues", state .getAttribute(STATE_MANUAL_ADD_COURSE_FIELDS)); + + context.put("fromArchive", state.getAttribute(STATE_UPLOADED_ARCHIVE_NAME)); return (String) getContext(data).get("template") + TEMPLATE[10]; case 12: @@ -3409,7 +3425,19 @@ // only show those sites with same site type putImportSitesInfoIntoContext(context, site, state, true); return (String) getContext(data).get("template") + TEMPLATE[61]; + + case 62: + /* + * build context for chef_site-uploadArchive.vm + */ + + //back to access, continue to confirm + context.put("back", "18"); + + //now go to uploadArchive template + return (String) getContext(data).get("template") + TEMPLATE[62]; } + // should never be reached return (String) getContext(data).get("template") + TEMPLATE[0]; @@ -4802,13 +4830,13 @@ if (params.getStrings("selectedMembers") == null) { M_log .warn("SiteAction.doSite_delete_confirmed selectedMembers null"); - state.setAttribute(STATE_TEMPLATE_INDEX, "0"); // return to the + state.setAttribute(STATE_TEMPLATE_INDEX, "0"); // return to the // site list return; } List chosenList = new ArrayList(Arrays.asList(params - .getStrings("selectedMembers"))); // Site id's of checked - // sites + .getStrings("selectedMembers"))); // Site id's of checked sites + if (!chosenList.isEmpty()) { for (ListIterator i = chosenList.listIterator(); i.hasNext();) { @@ -4818,6 +4846,8 @@ try { Site site = SiteService.getSite(id); site_title = site.getTitle(); + + //now delete the site SiteService.removeSite(site); M_log.debug("Removed site: " + site.getId()); } catch (IdUnusedException e) { @@ -5021,7 +5051,7 @@ List pSiteTypes = siteTypeProvider.getTypesForSiteCreation(); String type = StringUtils.trimToNull(params.getString("itemType")); - + if (type == null) { addAlert(state, rb.getString("java.select") + " "); } else { @@ -6151,7 +6181,7 @@ SecurityService.popAdvisor(); } } - + Site templateSite = (Site) state.getAttribute(STATE_TEMPLATE_SITE); if (templateSite == null) { @@ -6274,6 +6304,11 @@ // commit site commitSite(site); + + //merge uploaded archive if required + if(state.getAttribute(STATE_CREATE_FROM_ARCHIVE) == Boolean.TRUE) { + doMergeArchiveIntoNewSite(site.getId(), state); + } if (templateSite == null) { @@ -8439,9 +8474,15 @@ state.setAttribute(STATE_SITE_INFO, siteInfo); } - + + //if creating a site from an archive, go to that template + //otherwise go to confirm page if (state.getAttribute(STATE_MESSAGE) == null) { - state.setAttribute(STATE_TEMPLATE_INDEX, "10"); + if (state.getAttribute(STATE_CREATE_FROM_ARCHIVE) == Boolean.TRUE) { + state.setAttribute(STATE_TEMPLATE_INDEX, "62"); + } else { + state.setAttribute(STATE_TEMPLATE_INDEX, "10"); + } } } @@ -14226,6 +14267,16 @@ { doSite_copyFromCourseTemplate(data); } + else if ("createCourseOnTemplate".equals(option)) + { + doSite_copyFromCourseTemplate(data); + } + else if ("createFromArchive".equals(option)) + { + state.setAttribute(STATE_CREATE_FROM_ARCHIVE, Boolean.TRUE); + //continue with normal workflow + doSite_type(data); + } } } @@ -14304,7 +14355,9 @@ M_log.warn(this + "readCreateSiteTemplateInformation: problem of getting template site: " + templateSiteId); } } - + + + /** * redirect course creation process after the term selection step * @param params @@ -14569,5 +14622,81 @@ } } + + + /** + * Handles uploading an archive file as part of the site creation workflow + * @param data + */ + public void doUploadArchive(RunData data) + { + ParameterParser params = data.getParameters(); + SessionState state = ((JetspeedRunData) data).getPortletSessionState(((JetspeedRunData) data).getJs_peid()); + + //get params + FileItem fi = data.getParameters().getFileItem ("importFile"); + + //get uploaded file into a location we can process + String archiveUnzipBase = ServerConfigurationService.getString("archive.storage.path", FileUtils.getTempDirectoryPath()); + + //convert inputstream into actual file so we can unzip it + String zipFilePath = archiveUnzipBase + File.separator + fi.getFileName(); + + //rudimentary check that the file is a zip file + if(!StringUtils.endsWith(fi.getFileName(), ".zip")){ + addAlert(state, rb.getString("archive.createsite.failedupload")); + return; + } + + + + File tempZipFile = new File(zipFilePath); + if(tempZipFile.exists()) { + tempZipFile.delete(); + } + + try { + //copy contents into this file + IOUtils.copyLarge(fi.getInputStream(), new FileOutputStream(tempZipFile)); + + //set path into state so we can process it later + state.setAttribute(STATE_UPLOADED_ARCHIVE_PATH, tempZipFile.getAbsolutePath()); + state.setAttribute(STATE_UPLOADED_ARCHIVE_NAME, tempZipFile.getName()); + + } catch (Exception e) { + M_log.error(e.getMessage(), e); //general catch all for the various exceptions that occur above. all are failures. + addAlert(state, rb.getString("archive.createsite.failedupload")); + } + + //go to confirm screen + state.setAttribute(STATE_TEMPLATE_INDEX, "10"); + } + + /** + * Handles merging an uploaded archive into the newly created site + * This is done after the site has been created in the site creation workflow + * + * @param siteId + * @param state + */ + private void doMergeArchiveIntoNewSite(String siteId, SessionState state) { + + String currentUserId = userDirectoryService.getCurrentUser().getId(); + + try { + + String archivePath = (String)state.getAttribute(STATE_UPLOADED_ARCHIVE_PATH); + + //merge the zip into our new site + //we ignore the return because its not very useful. See ArchiveService for more details + archiveService.mergeFromZip(archivePath, siteId, currentUserId); + + } catch (Exception e) { + M_log.error(e.getMessage(), e); //general catch all for the various exceptions that occur above. all are failures. + addAlert(state, rb.getString("archive.createsite.failedmerge")); + } + + } + } Index: site-manage-tool/tool/src/webapp/css/site-manage.css =================================================================== --- site-manage-tool/tool/src/webapp/css/site-manage.css (revision 308016) +++ site-manage-tool/tool/src/webapp/css/site-manage.css (working copy) @@ -17,7 +17,7 @@ border-top: none; padding: .5em } -#templateSettings, #siteTypeList{ +#templateSettings, #siteTypeList, #archiveSettings{ padding:.5em; background:#eee; width:50em; @@ -47,7 +47,7 @@ min-width: 30em; } -#templateSettings, #siteTypeList{ +#templateSettings, #siteTypeList, #archiveSettings{ padding:.5em; background:#eee; width:60em; Index: site-manage-tool/tool/src/webapp/js/site-manage.js =================================================================== --- site-manage-tool/tool/src/webapp/js/site-manage.js (revision 308016) +++ site-manage-tool/tool/src/webapp/js/site-manage.js (working copy) @@ -182,7 +182,6 @@ $('#siteTypeList').hide(); //hide the term select used when selecting a course when building own $('#termList').hide(); - utils.resizeFrame('grow'); //show submit button used when using templates $('#submitFromTemplate').show(); //show submit button used when building own, disable it @@ -190,6 +189,15 @@ $('#submitBuildOwn').prop('disabled', true); //TODO: why? commenting out for now //$('#copyContent').prop('checked', true); + + //hide/reset archive parts + $('#archiveSettings').hide(); + $('#archive').prop('checked', false); + $('#submitBuildOwn').prop('disabled', true); + $('#submitFromArchive').hide(); + + utils.resizeFrame('grow'); + }); $('#buildOwn').click(function(e){ @@ -214,8 +222,49 @@ $('#submitFromTemplateCourse').hide(); //show the submit for build own $('#submitBuildOwn').show(); + + //hide/reset archive parts + $('#archiveSettings').hide(); + $('#archive').prop('checked', false); + $('#submitBuildOwn').prop('disabled', true); + $('#submitFromArchive').hide(); + + utils.resizeFrame('grow'); }); + + // handles display of "create site from archive" + $('#archive').click(function(e){ + + //show archive settings + $('#archiveSettings').show(); + $('#submitFromArchive').show(); + $('#submitBuildOwn').prop('disabled', false); + toggleArchiveTermList(); + + //hide and disable buildOwn section + $('#buildOwn').prop('checked', false); + $('#siteTypeList').hide(); + $('#termList').hide(); + $('#submitBuildOwn').hide(); + $('#submitBuildOwn').prop('disabled', true); + + //hide create from template section + $('#copy').prop('checked', false); + $('#templateSettings').hide(); + $('#templateSettings input:checked').prop('checked', false); + $('#allTemplateSettings').hide(); + $('#siteTitleField').prop('value', ''); + $('#templateSettings select').prop('selectedIndex', 0); + $('#templateSettingsTitleTerm span.templateTitleTerm').hide(); + $('#submitFromTemplate').hide().prop('disabled', true); + $('#submitFromTemplateCourse').hide(); + + utils.resizeFrame('grow'); + + }); + + // check for a value in the create from template non-course title // field and either enable or disable the submit, also check onblur below $('#siteTitleField').keyup(function(e){ @@ -410,6 +459,7 @@ $('#submitBuildOwn').prop('disabled', false); }); + }; sakai.setupToggleAreas = function(toggler, togglee, openInit, speed){ @@ -929,3 +979,15 @@ return -1; } } + + +var toggleArchiveTermList = function() { + + if ($('#archiveSiteType').val() == 'course') { + $('#archiveTermList').show(); + } else { + $('#archiveTermList').hide(); + } + +} + Index: site-manage-tool/tool/src/webapp/vm/sitesetup/chef_site-editToolGroupFeatures.vm =================================================================== --- site-manage-tool/tool/src/webapp/vm/sitesetup/chef_site-editToolGroupFeatures.vm (revision 308016) +++ site-manage-tool/tool/src/webapp/vm/sitesetup/chef_site-editToolGroupFeatures.vm (working copy) @@ -36,7 +36,7 @@ #parse("/vm/sitesetup/toolGroupMultipleDisplay.vm") - #if (!$existSite && $sites.size() > 0) + #if (!$existSite && !$createFromArchive && $sites.size() > 0)

$tlang.getString("feat.reuse")

Index: site-manage-tool/tool/src/webapp/vm/sitesetup/chef_site-newSiteConfirm.vm =================================================================== --- site-manage-tool/tool/src/webapp/vm/sitesetup/chef_site-newSiteConfirm.vm (revision 308016) +++ site-manage-tool/tool/src/webapp/vm/sitesetup/chef_site-newSiteConfirm.vm (working copy) @@ -302,7 +302,19 @@ $description #end - + + + + $tlang.getString("archive.createsite.confirm") + + + #if ($fromArchive =="") + $tlang.getString("archive.createsite.confirm.none") + #else + $fromArchive + #end + + Index: site-manage-tool/tool/src/webapp/vm/sitesetup/chef_site-type.vm =================================================================== --- site-manage-tool/tool/src/webapp/vm/sitesetup/chef_site-type.vm (revision 308016) +++ site-manage-tool/tool/src/webapp/vm/sitesetup/chef_site-type.vm (working copy) @@ -35,19 +35,14 @@ ## $tlang.getString("sitetype.chothetyp.withtemplate") ## #end

-
- #if ($templateSites.size() > 0) + +

## only show the radio button when there is any template site defined -

- ## only show the radio button when there is any template site defined - - -

-

$tlang.getString("sitetype.chothetyp.withtemplate.build.own.info")

- #else - ## otherwise, default to the choice - - #end + + +

+

$tlang.getString("sitetype.chothetyp.withtemplate.build.own.info")

+ ## NOTE: end if create from template enabled #* following block should show if "build own" has been selected, a site type has been picked, and then the user (from the next screen) hits "back" - the "Build own" radio @@ -233,6 +228,65 @@ ## end of child of div#templateSettings #end ## end templates enabled + + ## create site from archive + #if ($canImportArchive) +

+ + +

+

$tlang.getString("archive.createsite.info")

+

$tlang.getString("archive.createsite.info.2")

+ + + #end + + + ## end create site from archive @@ -244,7 +298,8 @@ *# - ## this submit should be ther normal one + + ## this submit should be ther normal one Index: site-manage-tool/tool/src/webapp/vm/sitesetup/chef_site-uploadArchive.vm =================================================================== --- site-manage-tool/tool/src/webapp/vm/sitesetup/chef_site-uploadArchive.vm (revision 0) +++ site-manage-tool/tool/src/webapp/vm/sitesetup/chef_site-uploadArchive.vm (working copy) @@ -0,0 +1,29 @@ +
+ #if($menu)#toolbar($menu)#end + +

$tlang.getString("archive.createsite.upload")

+ +

+ $tlang.getString("archive.createsite.info.2") +

+ + #if ($alertMessage)
$tlang.getString("gen.alert") $alertMessage
#end + + + * + + + + + +

+ + + +

+ + +
+