diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index 12f7076fc4..fb1fb42044 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -73,6 +73,11 @@ /// public const string DataTypes = "dataTypes"; + /// + /// alias for the packages tree + /// + public const string Packages = "packager"; + /// /// alias for the dictionary tree. /// diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/ourpackagerrepository.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/ourpackagerrepository.resource.js new file mode 100644 index 0000000000..d9307c7c28 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/resources/ourpackagerrepository.resource.js @@ -0,0 +1,64 @@ +/** + * @ngdoc service + * @name umbraco.resources.ourPackageRepositoryResource + * @description handles data for package installations + **/ +function ourPackageRepositoryResource($q, $http, umbDataFormatter, umbRequestHelper) { + + var baseurl = "https://our.umbraco.org/webapi/packages/v1"; + + return { + + getDetails: function (packageId) { + + return umbRequestHelper.resourcePromise( + $http.get(baseurl + "/" + packageId), + 'Failed to get package details'); + }, + + getCategories: function () { + + return umbRequestHelper.resourcePromise( + $http.get(baseurl), + 'Failed to query packages'); + }, + + getPopular: function (maxResults, category) { + + if (maxResults === undefined) { + maxResults = 10; + } + if (category === undefined) { + category = ""; + } + + return umbRequestHelper.resourcePromise( + $http.get(baseurl + "?pageIndex=0&pageSize=" + maxResults + "&category=" + category + "&order=Popular"), + 'Failed to query packages'); + }, + + search: function (pageIndex, pageSize, category, query, canceler) { + + var httpConfig = {}; + if (canceler) { + httpConfig["timeout"] = canceler; + } + + if (category === undefined) { + category = ""; + } + if (query === undefined) { + query = ""; + } + + return umbRequestHelper.resourcePromise( + $http.get(baseurl + "?pageIndex=" + pageIndex + "&pageSize=" + pageSize + "&category=" + category + "&query=" + query), + httpConfig, + 'Failed to query packages'); + } + + + }; +} + +angular.module('umbraco.resources').factory('ourPackageRepositoryResource', ourPackageRepositoryResource); diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js index c1d95861eb..33889e35e4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/package.resource.js @@ -7,6 +7,31 @@ function packageResource($q, $http, umbDataFormatter, umbRequestHelper) { return { + /** + * @ngdoc method + * @name umbraco.resources.packageInstallResource#getInstalled + * @methodOf umbraco.resources.packageInstallResource + * + * @description + * Gets a list of installed packages + */ + getInstalled: function() { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "packageInstallApiBaseUrl", + "GetInstalled")), + 'Failed to get installed packages'); + }, + + uninstall: function(packageId) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "packageInstallApiBaseUrl", + "Uninstall", { packageId: packageId })), + 'Failed to uninstall package'); + }, /** * @ngdoc method diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 9fbf2947af..30815027b0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -1,4 +1,62 @@ /*Contains multiple services for various helper tasks */ +function versionHelper() { + + return { + + //see: https://gist.github.com/TheDistantSea/8021359 + versionCompare: function(v1, v2, options) { + var lexicographical = options && options.lexicographical, + zeroExtend = options && options.zeroExtend, + v1parts = v1.split('.'), + v2parts = v2.split('.'); + + function isValidPart(x) { + return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x); + } + + if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) { + return NaN; + } + + if (zeroExtend) { + while (v1parts.length < v2parts.length) { + v1parts.push("0"); + } + while (v2parts.length < v1parts.length) { + v2parts.push("0"); + } + } + + if (!lexicographical) { + v1parts = v1parts.map(Number); + v2parts = v2parts.map(Number); + } + + for (var i = 0; i < v1parts.length; ++i) { + if (v2parts.length === i) { + return 1; + } + + if (v1parts[i] === v2parts[i]) { + continue; + } + else if (v1parts[i] > v2parts[i]) { + return 1; + } + else { + return -1; + } + } + + if (v1parts.length !== v2parts.length) { + return -1; + } + + return 0; + } + }; +} +angular.module('umbraco.services').factory('versionHelper', versionHelper); function packageHelper(assetsService, treeService, eventsService, $templateCache) { diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 27bceeeb58..0399fe8f8f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -108,6 +108,8 @@ @import "components/umb-empty-state.less"; @import "components/umb-property-editor.less"; @import "components/umb-iconpicker.less"; +@import "components/umb-packages.less"; +@import "components/umb-package-local-install.less"; @import "components/buttons/umb-button.less"; @import "components/buttons/umb-button-group.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less new file mode 100644 index 0000000000..a272a40c90 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-package-local-install.less @@ -0,0 +1,153 @@ +// +// Install local package +// + +// Helpers +.faded { + color: @grayMed; +} + +.inline-flex { + display: inline-flex; +} + +.mt-3 { + margin-top: 30px; +} + +// Upload state + +.umb-upload-local, +.umb-info-local { + display: flex; + justify-content: center; + align-items: center; + text-align: center; +} + +.umb-upload-local form, +.umb-info-local-items { + display: flex; + flex-direction: column; + + justify-content: center; + align-items: center; + + margin: 0; + + max-width: 640px; +} + +.umb-upload-local__dropzone { + position: relative; + width: 500px; + height: 300px; + border: 2px dashed @grayLight; + border-radius: 3px; + background: @grayLighter; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + margin-bottom: 30px; + + transition: 100ms box-shadow ease, 100ms border ease; + + &:hover, + &.drag-over { + border-color: @blue; + border-style: solid; + box-shadow: 0 3px 8px rgba(0,0,0, .1); + transition: 100ms box-shadow ease, 100ms border ease; + } +} + +.umb-upload-local__dropzone i { + display: block; + color: @grayLight; + font-size: 110px; + line-height: 1; +} + +.umb-upload-local__select-file { + font-weight: bold; + color: @blue; + cursor: pointer; +} + + +// Accept terms +.umb-accept-terms { + display: flex; + align-items: center; + + font-size: 13px; +} + +.umb-package-installer-label .label-text { + margin-left: 5px; +} + +.umb-package-installer-label input[type="radio"], +.umb-package-installer-label input[type="checkbox"] { + margin-top: 0px; +} + +.umb-package-installer-label { + display: inline-flex; + align-items: center; + + font-size: 13px; + user-select: none; +} + + +// Info state +.umb-info-local-items { + border: 2px solid @grayLight; + border-radius: 3px; + background: @grayLighter; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + margin: 0 20px; + max-width: 540px; +} + +.umb-info-local-items a { + text-decoration: underline; + + &:hover { + text-decoration: none; + } +} + +.umb-info-local-item { + margin-bottom: 20px; +} + +.umb-info-local-items .umb-package-icon { + width: 100%; + box-sizing: border-box; + min-height: 150px; + font-size: 60px; +} + +.umb-info-local-items .umb-package-icon img { + max-width: 100px; +} + +.umb-info-local-items .umb-package-info { + width: 100%; + box-sizing: border-box; + padding: 20px 40px; +} + +.umb-info-local-items .umb-package-installer-label { + margin-left: 10px; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less new file mode 100644 index 0000000000..41ea5e5fd1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-packages.less @@ -0,0 +1,520 @@ +.umb-packages-view-title { + font-size: 20px; + font-weight: bold; + color: @black; + margin-bottom: 30px; +} + +.umb-packages-view-wrapper { + padding: 20px 60px; +} + +.umb-packages-section { + margin-bottom: 40px; +} + +.umb-packages-search { + width: 100%; + background: @grayLighter; + border-radius: 3px; + padding: 30px; + box-sizing: border-box; +} + +.umb-packages-search input { + border-width: 2px; + border-radius: 3px; + min-height: 44px; + + padding: 4px 10px; + font-size: 16px; + border-color: #ececec; + margin-bottom: 0; + border-color: @grayLight; + + &:hover, &:focus { + border-color: @grayLight; + } +} + +.umb-packages__pagination { + display: flex; + justify-content: center; +} + +.umb-packages { + margin: 0 -10px; + display: flex; + flex-wrap: wrap; +} + +// Cards +.umb-package { + padding: 10px; + box-sizing: border-box; + width: 25%; +} + +.umb-package-link { + display: flex; + flex-wrap: wrap; + flex-direction: column; + justify-content: center; + + position: relative; + box-sizing: border-box; + + height: 100%; + width: 100%; + + border: 1px solid #ececec; + border-radius: 3px; + + text-decoration: none !important; + + transition: border-color 100ms ease; + + &:hover { + border-color: @blue; + } +} + + + +// Icon +.umb-package-icon { + display: flex; + + justify-content: center; + align-items: center; + + padding-top: 10px; + padding-right: 10px; + padding-left: 10px; + padding-bottom: 10px; + + text-align: center; + background-color: white; + + min-height: 60px; +} + +.umb-package-icon img { + max-width: 70px; + height: auto; +} + + +// Info +.umb-package-info { + padding-right: 15px; + padding-bottom: 15px; + padding-left: 15px; + padding-top: 15px; + text-align: center; + background: @grayLighter; + border-top: 1px solid #ececec; +} + + +// Name +.umb-package-name { + font-size: 14px; + max-width: 250px; + margin-bottom: 5px; + + font-weight: bold; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + line-height: normal; + margin-left: auto; + margin-right: auto; +} + +.umb-package-description { + font-size: 11px; + color: @grayMed; + word-wrap: break-word; + line-height: 1.1rem; +} + +// Numbers +.umb-package-numbers { + display: flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: center; + + opacity: .6; + + margin-top: 10px; +} + +.umb-package-numbers small { + padding: 0 5px; + + display: flex; + align-items: center; + justify-content: center; +} + +.umb-package-numbers i { + font-size: 14px; +} + +.umb-package-link:hover .umb-package-numbers { + opacity: 1; +} + +.umb-package-link:hover .umb-package-numbers .icon-hearts { + color: red !important; +} + +// Version +.umb-package-version { + display: inline-flex; + font-size: 11px; + font-weight: bold; + padding: 1px 5px; + background: #ececec; + border-radius: 3px; + color: black; +} + +/* umb-buttons-era */ +.umb-era-button { + display: flex; + justify-content: center; + align-items: center; + + font-size: 14px; + font-weight: bold; + + height: 38px; + line-height: 1; + + max-width: 100%; + padding: 0 18px; + + color: #484848; + background-color: #e0e0e0; + + text-decoration: none !important; + user-select: none; + + white-space: nowrap; + overflow: hidden; + + border-radius: 3px; + border: 0 none; + box-sizing: border-box; + + cursor: pointer; + + transition: background-color 80ms ease, color 80ms ease; +} + + +.umb-era-button:hover, +.umb-era-button:active { + color: #484848; + background-color: #d3d3d3; + outline: none; + text-decoration: none; +} + + +.umb-era-button:focus { + outline: none; +} + +.umb-era-button.-blue { + background: @blue; + color: white; +} + +.umb-era-button.-blue:hover { + background-color: @blueDark; +} + +.umb-era-button.-link { + padding: 0; + background: transparent; +} + +.umb-era-button.-link:hover { + background-color: transparent; + opacity: .6; +} + +.umb-era-button.-inactive { + cursor: not-allowed; + color: #BBB; + background: #EAE7E7; +} + +.umb-era-button.-inactive:hover { + color: #BBB; + background: #EAE7E7; +} + + +.umb-era-button.-full-width { + display: block; + width: 100%; +} + + +/* CATEGORIES */ + +.umb-packages-categories { + display: flex; + user-select: center; + flex-wrap: wrap; +} + +.umb-packages-category { + display: flex; + align-items: center; + flex: 1 0 auto; + justify-content: center; + max-width: 25%; + font-size: 14px; + font-weight: bold; + color: @black; + box-sizing: border-box; + justify-content: center; + border-top: 1px solid @grayLight; + border-bottom: 1px solid @grayLight; + border-right: 1px solid @grayLight; + padding: 10px 0; +} + +.umb-packages-category:hover, +.umb-packages-category.-active { + text-decoration: none; + color: @blue; +} + +.umb-packages-category.-first { + border-left: 1px solid @grayLight; + border-radius: 3px 0 0 3px; +} + +.umb-packages-category.-last { + border-right: 1px solid @grayLight; + border-radius: 0 3px 3px 0; +} + +/* PACKAGE DETAILS */ + +.umb-package-details { + display: flex; +} + +.umb-package-details__back-link { + font-weight: bold; + color: @grayMed; +} + +.umb-package-details__back-link:hover { + color: @black; + text-decoration: none; +} + +.umb-package-details__main-content { + flex: 1 1 auto; + margin-right: 40px; +} + +.umb-package-details__sidebar { + flex: 0 0 350px; +} + +.umb-package-details__section { + background: @grayLighter; + padding: 20px; + margin-bottom: 20px; + border-radius: 3px; +} + +.umb-package-details__section-title { + font-size: 17px; + font-weight: bold; + color: black; + margin-top: 0; + margin-bottom: 15px; +} + +.umb-package-details__section-description { + font-size: 12px; + line-height: 1.6em; + margin-bottom: 15px; +} + +.umb-package-details__information-item { + display: flex; + margin-bottom: 5px; + font-size: 13px; +} + +.umb-package-details__information-item-label { + color: black; + font-weight: bold; + margin-right: 3px; +} + +.umb-package-details__information-item-label-2 { + font-size: 12px; + color: @grayMed; +} + +.umb-package-details__compatability { + margin-bottom: 15px; +} + +.umb-package-details__compatability-label { + margin-bottom: 3px; +} + +.umb-package-details__description { + margin-bottom: 20px; + line-height: 1.6em; +} + +.umb-package-details__description p { + margin-bottom: 20px; +} + +/* Links */ + +.umb-package-details__link { + color: #DA3287; +} + +.umb-package-details__link:hover { + color: #B32D71; + text-decoration: none; +} + +/* Owner profile */ + +.umb-package-details__owner-profile { + display: flex; + align-items: center; +} +.umb-package-details__owner-profile-avatar { + margin-right: 15px; +} + +.umb-package-details__owner-profile-name { + font-size: 15px; + color: #000000; + font-weight: bold; +} + +.umb-package-details__owner-profile-karma { + font-size: 12px; + color: @grayMed; +} + +/* gallery */ + +.umb-gallery__thumbnails { + display: flex; + flex-wrap: wrap; +} + +.umb-gallery__thumbnail { + flex: 1 1 100px; + border: 1px solid @grayLight; + border-radius: 3px; + margin: 5px; + padding: 10px; + box-sizing: border-box; +} + +.umb-gallery__thumbnail:hover { + cursor: pointer; + border-color: @blue; +} + +/* Avatar */ + +.umb-avatar { + border-radius: 50%; + width: 50px; +} + +/* Progress bar */ + +.umb-progress-bar { + background: @grayLight; + width: 100%; + display: block; + height: 10px; + border-radius: 10px; + box-sizing: border-box; + position: relative; + overflow: hidden; +} + +.umb-progress-bar__progress { + background: #50C878; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 100%; + border-radius: 10px; +} + +/* PACKAGE LIST */ + +.umb-package-list { + display: flex; + flex-direction: column; +} + +.umb-package-list__item { + display: flex; + flex-direction: row; + background: @grayLighter; + margin-bottom: 10px; + border-radius: 3px; + padding: 20px; + align-items: center; +} + +.umb-package-list__item-icon { + flex: 0 0 50px; + margin-right: 20px; + font-size: 30px; + text-align: center; +} + +.umb-package-list__item-content { + flex: 1 1 auto; + margin-right: 20px; +} + +.umb-package-list__item-name { + font-size: 16px; + margin-bottom: 5px; + color: @black; + font-weight: bold; +} + +.umb-package-list__item-description { + font-size: 14px; + color: @grayMed; +} + +.umb-package-list__item-actions { + flex: 1 1 auto; + display: flex; + justify-content: flex-end; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index a1776bf12d..83b61e48a5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -366,17 +366,17 @@ flex: 1; } -.umb-panel-header-content.-top-position { - position: relative; - top: -12px; -} - .umb-panel-header-left-side { display: flex; flex: 1; flex-direction: row; } +.umb-panel-header-left-side.-top-position { + position: relative; + top: -12px; +} + .umb-panel-header-icon { cursor: pointer; margin-right: 5px; diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html index fd112a018a..46c666b8cc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html @@ -2,9 +2,9 @@
-
+
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js index b400185b02..561b9dd753 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js @@ -31,7 +31,7 @@ function FormsController($scope, $route, $cookieStore, packageResource) { return packageResource.installFiles(pack); }, $scope.error) .then(function(pack){ - $scope.state = "Restarting, please hold..."; + $scope.state = "Restarting, please wait..."; return packageResource.installData(pack); }, $scope.error) .then(function(pack){ diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/category.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/category.controller.js new file mode 100644 index 0000000000..61332e850f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/category.controller.js @@ -0,0 +1,100 @@ +(function () { + "use strict"; + + function PackagesCategoryController($scope, $routeParams) { + + var vm = this; + + vm.page = {}; + vm.page.name = "Category"; + + vm.selectCategory = selectCategory; + + vm.categories = [ + { + "icon": "icon-male-and-female", + "name": "All", + "active": false + }, + { + "icon": "icon-male-and-female", + "name": "Collaboration", + "active": true + }, + { + "icon": "icon-molecular-network", + "name": "Backoffice extensions" + }, + { + "icon": "icon-brackets", + "name": "Developer tools" + }, + { + "icon": "icon-wand", + "name": "Starter kits" + }, + { + "icon": "icon-medal", + "name": "Umbraco Pro" + }, + { + "icon": "icon-wrench", + "name": "Website utilities" + } + ]; + + vm.packages = [ + { + "name": "uSightly", + "description": "An HTML5 audio player based on jPlayer", + "karma": "1", + "downloads": "1672", + "icon":"https://our.umbraco.org/media/wiki/150283/635768313097111400_usightlylogopng.png?bgcolor=fff&height=154&width=281&format=png" + }, + { + "name": "Kill IE6", + "description": "A simple port of the IE6 warning script (http://code.google.com/p/ie6-upgrade-warning/) to use in your Umbraco websites.", + "karma": "11", + "downloads": "688", + "icon":"https://our.umbraco.org/media/wiki/9138/634697622367666000_offroadcode-100x100.png?bgcolor=fff&height=154&width=281&format=png" + }, + { + "name": "Examine Media Indexer", + "description": "CogUmbracoExamineMediaIndexer", + "karma": "3", + "downloads": "1329", + "icon":"https://our.umbraco.org/media/wiki/50703/634782902373558000_cogworks.jpg?bgcolor=fff&height=154&width=281&format=png" + }, + { + "name": "SVG Icon Picker", + "description": "A picker, for picking icons from an SVG spritesheet.", + "karma": "5", + "downloads": "8", + "icon":"https://our.umbraco.org/media/wiki/154472/635997115126742822_logopng.png?bgcolor=fff&height=154&width=281&format=png" + }, + { + "name": "Pipeline CRM", + "description": "Pipeline is a social CRM that lives in Umbraco back-office. It tracks opportunities and helps teams collaborate with timelines and tasks. It stores information about your customers and your interactions with them. It integrates with your website, capturing opportunities from forms and powering personal portals.", + "karma": "3", + "downloads": "105", + "icon":"https://our.umbraco.org/media/wiki/152476/635917291068518788_pipeline-crm-logopng.png?bgcolor=fff&height=154&width=281&format=png" + }, + { + "name": "CodeMirror", + "description": "CodeMirror Editor for Umbraco", + "karma": "1", + "downloads": "70", + "icon":"https://our.umbraco.org/media/wiki/151028/635810233171153461_logopng.png?bgcolor=fff&height=154&width=281&format=png" + } + ]; + + function selectCategory(category) { + + } + + + } + + angular.module("umbraco").controller("Umbraco.Editors.Packages.CategoryController", PackagesCategoryController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/category.html b/src/Umbraco.Web.UI.Client/src/views/packager/category.html new file mode 100644 index 0000000000..d408b21cd9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/category.html @@ -0,0 +1,109 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/details.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/details.controller.js new file mode 100644 index 0000000000..7f4a48ebb9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/details.controller.js @@ -0,0 +1,100 @@ +(function () { + "use strict"; + + function PackageDetailsController($scope, $routeParams) { + + var vm = this; + + vm.page = {}; + + vm.package = { + "name": "Merchello", + "description": "<p>Merchello is a high performance, designer friendly, open source Umbraco ecommerce package built for the store owner.</p> <p><strong>What Merchello does for you</strong></p> <p>In version 1, Merchello supports a large variety of products with options that can be attached to a single warehouse, processes orders, manages taxes and shipping, and sends out email notifications to your customers. The beauty of Merchello is that while it oversees all of your products, orders, and store settings, it allows Umbraco to maintain your content. This seamless integration gives you the flexibility to build your store in any way imagineable on a robust platform capable of handling a wide variety of store sizes.</p> <p><strong>Find out more on our website</strong></p> <p><strong><a href="https://merchello.com">https://merchello.com</a></strong></p> <p><strong>Contribute</strong></p> <p>We would love and need your help. If you want to contribute to Merchello's core, the easiest way to get started is to fork the project on https://github.com/merchello/Merchello and open src/Merchello.sln in Visual Studio. We're excited to see what you do!</p> <p><strong>Starter Kit</strong></p> <p>We have built a simple starter kit for Merchello called Bazaar, and you can download it below in the package files tab.</p>", + "compatibility": [ + { + "version": "7.4.x", + "percentage": "100" + }, + { + "version": "7.3.x", + "percentage": "86" + }, + { + "version": "7.2.x", + "percentage": "93" + }, + { + "version": "7.1.x", + "percentage": "100" + } + ], + "information": { + "owner": "Rusty Swayne", + "ownerAvatar": "https://our.umbraco.org/media/upload/d476d257-a494-46d9-9a00-56c2f94a55c8/our-profile.jpg?width=200&height=200&mode=crop", + "ownerKarma": "2673", + "contributors": [ + { + "name": "Lee" + }, + { + "name": "Jason Prothero" + } + ], + "created": "18/12/2013", + "currentVersion": "2.0.0", + "netVersion": "4.5", + "license": "MIT", + "downloads": "4198", + "karma": "53" + }, + "externalSources": [ + { + "name": "Source code", + "url": "https://github.com/merchello/Merchello" + }, + { + "name": "Issue tracker", + "url": "http://issues.merchello.com/youtrack/oauth?state=%2Fyoutrack%2FrootGo" + } + ], + "images": [ + { + "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", + "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" + }, + { + "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", + "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" + }, + { + "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", + "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" + }, + { + "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", + "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" + }, + { + "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", + "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" + }, + { + "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", + "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" + }, + { + "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", + "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" + }, + { + "thumbnail": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png?bgcolor=fff&height=154&width=281&format=png", + "source": "https://our.umbraco.org/media/wiki/104946/635591947547374885_Product-Listpng.png" + } + ] + }; + + } + + angular.module("umbraco").controller("Umbraco.Editors.Packages.DetailsController", PackageDetailsController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/details.html b/src/Umbraco.Web.UI.Client/src/views/packager/details.html new file mode 100644 index 0000000000..fa69f55407 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/details.html @@ -0,0 +1,141 @@ +
+ + + +
+ + + + + + + + +
+ +
+ +
+ + + +
+ +
+ +
+ +
+ +
+
+
+ +
+
+
{{ vm.package.information.owner }}
+
+ {{ vm.package.information.owner }} has {{ vm.package.information.ownerKarma }} karma points +
+
+
+
+ +
+
Information
+
+ +
+
Owner:
+
{{vm.package.information.owner}}
+
+ +
+
Contributors:
+ +
+ +
+
Created:
+
{{vm.package.information.created}}
+
+ +
+
Current version:
+
{{vm.package.information.created}}
+
+ +
+
.Net Version:
+
{{vm.package.information.netVersion}}
+
+ +
+
License:
+
{{vm.package.information.license}}
+
+ +
+
Downloads:
+
{{vm.package.information.downloads}}
+
+ +
+
Karma:
+
{{vm.package.information.karma}}
+
+ +
+
+ +
+
Compatibility
+
This project is compatible with the following versions as reported by community members who have downloaded this package:
+
+
+ {{compatibility.version}} + ({{compatibility.percentage}}%) +
+
+ +
+
+
+ +
+
External sources
+ + +
+ +
+ +
+ +
+ +
+ +
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/overview.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/overview.controller.js new file mode 100644 index 0000000000..c03b6cd230 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/overview.controller.js @@ -0,0 +1,50 @@ +(function () { + "use strict"; + + function PackagesOverviewController($scope, $route, $location, navigationService, $timeout, $cookieStore) { + + //Hack! + // if there is a cookie value for packageInstallUri then we need to redirect there, + // the issue is that we still have webforms and we cannot go to a hash location and then window.reload + // because it will double load it. + // we will refresh and then navigate there. + + var installPackageUri = window.localStorage.getItem("packageInstallUri"); + if (installPackageUri) { + window.localStorage.removeItem("packageInstallUri"); + $location.path(installPackageUri).search(""); + } + else { + var vm = this; + + vm.page = {}; + vm.page.name = "Packages"; + vm.page.navigation = [ + { + "name": "Packages", + "icon": "icon-cloud", + "view": "views/packager/views/repo.html", + "active": true + }, + { + "name": "Installed", + "icon": "icon-box", + "view": "views/packager/views/installed.html" + }, + { + "name": "Install local", + "icon": "icon-add", + "view": "views/packager/views/install-local.html" + } + ]; + + $timeout(function () { + navigationService.syncTree({ tree: "packager", path: "-1" }); + }); + } + + } + + angular.module("umbraco").controller("Umbraco.Editors.Packages.OverviewController", PackagesOverviewController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/overview.html b/src/Umbraco.Web.UI.Client/src/views/packager/overview.html new file mode 100644 index 0000000000..b9857ac53a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/overview.html @@ -0,0 +1,28 @@ +
+ +
+ + + + + + + + + + + + + + + +
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js new file mode 100644 index 0000000000..c68b31038f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js @@ -0,0 +1,146 @@ +(function () { + "use strict"; + + function PackagesInstallLocalController($scope, $route, $location, Upload, umbRequestHelper, packageResource, $cookieStore, $timeout) { + + var vm = this; + vm.state = "upload"; + + vm.localPackage = {}; + vm.installPackage = installPackage; + vm.installState = { + status: "" + }; + vm.zipFile = { + uploadStatus: "idle", + uploadProgress: 0, + serverErrorMessage: null + }; + + $scope.handleFiles = function (files, event) { + for (var i = 0; i < files.length; i++) { + upload(files[i]); + } + }; + + function upload(file) { + + Upload.upload({ + url: umbRequestHelper.getApiUrl("packageInstallApiBaseUrl", "UploadLocalPackage"), + fields: {}, + file: file + }).progress(function (evt) { + + // calculate progress in percentage + var progressPercentage = parseInt(100.0 * evt.loaded / evt.total, 10); + + // set percentage property on file + vm.zipFile.uploadProgress = progressPercentage; + + // set uploading status on file + vm.zipFile.uploadStatus = "uploading"; + + }).success(function (data, status, headers, config) { + + if (data.notifications && data.notifications.length > 0) { + + // set error status on file + vm.zipFile.uploadStatus = "error"; + + // Throw message back to user with the cause of the error + vm.zipFile.serverErrorMessage = data.notifications[0].message; + + //TODO: Handle the error in UI + + } else { + + // set done status on file + vm.zipFile.uploadStatus = "done"; + loadPackage(); + vm.localPackage = data; + } + + }).error(function (evt, status, headers, config) { + + //TODO: Handle the error in UI + + // set status done + vm.zipFile.uploadStatus = "error"; + + //if the service returns a detailed error + if (evt.InnerException) { + vm.zipFile.serverErrorMessage = evt.InnerException.ExceptionMessage; + + //Check if its the common "too large file" exception + if (evt.InnerException.StackTrace && evt.InnerException.StackTrace.indexOf("ValidateRequestEntityLength") > 0) { + vm.zipFile.serverErrorMessage = "File too large to upload"; + } + + } else if (evt.Message) { + file.serverErrorMessage = evt.Message; + } + + // If file not found, server will return a 404 and display this message + if (status === 404) { + vm.zipFile.serverErrorMessage = "File not found"; + } + + }); + } + + function loadPackage() { + if (vm.zipFile.uploadStatus === "done") { + vm.state = "packageDetails"; + } + } + + function installPackage() { + vm.installState.status = "Installing"; + + //TODO: If any of these fail, will they keep calling the next one? + packageResource + .installFiles(vm.localPackage) + .then(function(pack) { + vm.installState.status = "Importing..."; + return packageResource.import(pack); + }, + installError) + .then(function(pack) { + vm.installState.status = "Installing..."; + return packageResource.installFiles(pack); + }, + installError) + .then(function(pack) { + vm.installState.status = "Restarting, please wait..."; + return packageResource.installData(pack); + }, + installError) + .then(function(pack) { + vm.installState.status = "All done, your browser will now refresh"; + return packageResource.cleanUp(pack); + }, + installError) + .then(function(result) { + + if (result.postInstallationPath) { + //Put the redirect Uri in a cookie so we can use after reloading + window.localStorage.setItem("packageInstallUri", result.postInstallationPath); + } + //reload on next digest (after cookie) + $timeout(function() { + window.location.reload(true); + }); + + + }, + installError); + } + + function installError() { + //TODO: Need to do something about this? + } + } + + angular.module("umbraco").controller("Umbraco.Editors.Packages.InstallLocalController", PackagesInstallLocalController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html new file mode 100644 index 0000000000..b25facae7f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html @@ -0,0 +1,112 @@ +
+ + +
+
+ + +
+ + +
+ + + + Drop to upload + + +
+ - or click here to choose files +
+
+
+ + +

Upload package

+

+ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam +

+ +
+
+ + + +
+
+ +
+
+ + +
+ + +
+

{{ vm.localPackage.name }}

+ + + +
+ Version + {{ vm.localPackage.version }} +
+ + + +
+ Read me +
+ +
+ +
+ + +
+
+ This package cannot be installed, it requires a minimum Umbraco version of {{vm.localPackage.umbracoVersion}} +
+
+

{{vm.installState.status}}

+
+ +
+
+ +
+
+ + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.controller.js new file mode 100644 index 0000000000..e2b5fb4de8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.controller.js @@ -0,0 +1,52 @@ +(function () { + "use strict"; + + function PackagesInstalledController($scope, $route, $location, packageResource) { + + var vm = this; + + vm.confirmUninstall = confirmUninstall; + vm.uninstallPackage = uninstallPackage; + vm.state = "list"; + vm.installState = { + status: "" + }; + vm.package = {}; + + function init() { + packageResource.getInstalled() + .then(function (packs) { + vm.installedPackages = packs; + }); + vm.installState.status = ""; + vm.state = "list"; + } + + function confirmUninstall(pck) { + vm.state = "packageDetails"; + vm.package = pck; + } + + function uninstallPackage(installedPackage) { + vm.installState.status = "Uninstalling package..."; + packageResource.uninstall(installedPackage.id) + .then(function () { + if (installedPackage.files.length > 0) { + vm.installState.status = "All done, your browser will now refresh"; + + var url = window.location.href + "?uninstalled=" + vm.package.packageGuid; + window.location.reload(true); + } + else { + init(); + } + }); + } + + init(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.Packages.InstalledController", PackagesInstalledController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html new file mode 100644 index 0000000000..403406545c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/installed.html @@ -0,0 +1,102 @@ +
+ + +
+ +
Installed packages
+ +
+ +
+ +
+ + +
+ +
+
{{ installedPackage.name }}
+
+ {{ installedPackage.version }} | {{ installedPackage.url }}| {{ installedPackage.author }} +
+
+ +
+ +
+ +
+ +
+
+ + +
+ + + + ← Take me back + + + +
+ +
+ +
+
+ + +
+ + +
+

{{ vm.package.name }}

+ + + +
+ Version + {{ vm.package.version }} +
+ +
+ License + {{ vm.package.license }} +
+ +
+ Read me +
+ {{ vm.package.readme }} +
+ +
+ + +
+
+

{{vm.installState.status}}

+
+ +
+
+ +
+ +
+
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js new file mode 100644 index 0000000000..e87a4a2426 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js @@ -0,0 +1,240 @@ +(function () { + "use strict"; + + function PackagesRepoController($scope, $route, $location, $timeout, ourPackageRepositoryResource, $q, packageResource, $cookieStore) { + + var vm = this; + + vm.packageViewState = "packageList"; + vm.categories = []; + vm.loading = true; + vm.pagination = { + pageNumber: 1, + totalPages: 10, + pageSize: 8 + }; + vm.searchQuery = ""; + vm.installState = { + status: "" + }; + vm.selectCategory = selectCategory; + vm.showPackageDetails = showPackageDetails; + vm.setPackageViewState = setPackageViewState; + vm.nextPage = nextPage; + vm.prevPage = prevPage; + vm.goToPage = goToPage; + vm.installPackage = installPackage; + vm.downloadPackage = downloadPackage; + + //used to cancel any request in progress if another one needs to take it's place + var canceler = null; + + function getActiveCategory() { + if (vm.searchQuery !== "") { + return ""; + } + for (var i = 0; i < vm.categories.length; i++) { + if (vm.categories[i].active === true) { + return vm.categories[i].name; + } + } + return ""; + } + + function init() { + + vm.loading = true; + + $q.all([ + ourPackageRepositoryResource.getCategories() + .then(function(cats) { + vm.categories = cats; + }), + ourPackageRepositoryResource.getPopular(8) + .then(function(pack) { + vm.popular = pack.packages; + }), + ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize) + .then(function(pack) { + vm.packages = pack.packages; + vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); + }) + ]) + .then(function() { + vm.loading = false; + }); + + $scope.$watch(function() { + return vm.searchQuery; + }, _.debounce(function (newVal, oldVal) { + $scope.$apply(function () { + if (vm.searchQuery) { + if (newVal !== null && newVal !== undefined && newVal !== oldVal) { + vm.loading = true; + + //a canceler exists, so perform the cancelation operation and reset + if (canceler) { + canceler.resolve(); + canceler = $q.defer(); + } + else { + canceler = $q.defer(); + } + + ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, + vm.pagination.pageSize, + "", + vm.searchQuery, + canceler) + .then(function(pack) { + vm.packages = pack.packages; + vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); + vm.pagination.pageNumber = 1; + vm.loading = false; + //set back to null so it can be re-created + canceler = null; + }); + } + } + }); + }, 200)); + + } + + function selectCategory(selectedCategory, categories) { + var reset = false; + for (var i = 0; i < categories.length; i++) { + var category = categories[i]; + if (category.name === selectedCategory.name && category.active === true) { + //it's already selected, let's unselect to show all again + reset = true; + } + category.active = false; + } + + vm.loading = true; + vm.searchQuery = ""; + var searchCategory = selectedCategory.name; + if (reset === true) { + searchCategory = ""; + } + + $q.all([ + ourPackageRepositoryResource.getPopular(8, searchCategory) + .then(function(pack) { + vm.popular = pack.packages; + }), + ourPackageRepositoryResource.search(vm.pagination.pageNumber - 1, vm.pagination.pageSize, searchCategory, vm.searchQuery) + .then(function(pack) { + vm.packages = pack.packages; + vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); + vm.pagination.pageNumber = 1; + }) + ]) + .then(function() { + vm.loading = false; + selectedCategory.active = reset === false; + }); + } + + function showPackageDetails(selectedPackage) { + ourPackageRepositoryResource.getDetails(selectedPackage.id) + .then(function(pack) { + vm.package = pack; + vm.packageViewState = "packageDetails"; + }); + + } + + function setPackageViewState(state) { + if(state) { + vm.packageViewState = state; + } + } + + function nextPage(pageNumber) { + ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, getActiveCategory(), vm.searchQuery) + .then(function (pack) { + vm.packages = pack.packages; + vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); + }); + } + + function prevPage(pageNumber) { + ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, getActiveCategory(), vm.searchQuery) + .then(function (pack) { + vm.packages = pack.packages; + vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); + }); + } + + function goToPage(pageNumber) { + ourPackageRepositoryResource.search(pageNumber - 1, vm.pagination.pageSize, getActiveCategory(), vm.searchQuery) + .then(function (pack) { + vm.packages = pack.packages; + vm.pagination.totalPages = Math.ceil(pack.total / vm.pagination.pageSize); + }); + } + + function downloadPackage(selectedPackage) { + vm.loading = true; + + packageResource + .fetch(selectedPackage.id) + .then(function(pack) { + vm.packageViewState = "packageInstall"; + vm.loading = false; + vm.localPackage = pack; + vm.localPackage.allowed = true; + }, + error); + } + + function error(e, args) { + + } + + function installPackage(selectedPackage) { + + vm.installState.status = "importing..."; + + packageResource + .import(selectedPackage) + .then(function(pack) { + vm.installState.status = "Installing..."; + return packageResource.installFiles(pack); + }, + error) + .then(function(pack) { + vm.installState.status = "Restarting, please wait..."; + return packageResource.installData(pack); + }, + error) + .then(function(pack) { + vm.installState.status = "All done, your browser will now refresh"; + return packageResource.cleanUp(pack); + }, + error) + .then(function(result) { + + if (result.postInstallationPath) { + //Put the redirect Uri in a cookie so we can use after reloading + window.localStorage.setItem("packageInstallUri", result.postInstallationPath); + } + + //reload on next digest (after cookie) + $timeout(function () { + window.location.reload(true); + }); + + }, + error); + } + + init(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.Packages.RepoController", PackagesRepoController); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html new file mode 100644 index 0000000000..d42e955b4f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html @@ -0,0 +1,293 @@ +
+ + + + + + + + +
+ + + + ← Take me back + + + +
+ +
+ +
{{ vm.package.name }}
+ +
+ + + +
+ +
+ +
+ +
+ +
+
+
+ +
+
+
{{ vm.package.ownerInfo.owner }}
+
+ {{ vm.package.ownerInfo.owner }} has {{ vm.package.ownerInfo.karma }} karma points +
+
+
+
+ +
+
Information
+
+ +
+
Owner:
+
{{vm.package.ownerInfo.owner}}
+
+ +
+
Contributors:
+ +
+ +
+
Created:
+
{{vm.package.created}}
+
+ +
+
Current version:
+
{{vm.package.latestVersion}}
+
+ +
+
.Net Version:
+
{{vm.package.information.netVersion}}
+
+ +
+
License:
+
{{vm.package.licenseName}}
+
+ +
+
Downloads:
+
{{vm.package.downloads}}
+
+ +
+
Karma:
+
{{vm.package.ownerInfo.karma}}
+
+ +
+
+ +
+
Compatibility
+
This project is compatible with the following versions as reported by community members who have downloaded this package:
+
+
+ {{compatibility.version}} + ({{compatibility.percentage}}%) +
+
+ +
+
+
+ +
+
External sources
+ + +
+ +
+ +
+
+ + +
+
+ +
+
+ + +
+ + +
+

{{ vm.localPackage.name }}

+ + + +
+ Version + {{ vm.localPackage.version }} +
+ + + +
+ Read me +
+ +
+ +
+ + +
+
+ This package cannot be installed, it requires a minimum Umbraco version of {{vm.localPackage.umbracoVersion}} +
+
+

{{vm.installState.status}}

+
+ +
+
+ +
+
+ +
diff --git a/src/Umbraco.Web.UI/config/ClientDependency.config b/src/Umbraco.Web.UI/config/ClientDependency.config index 2115c540b7..8663cfeae6 100644 --- a/src/Umbraco.Web.UI/config/ClientDependency.config +++ b/src/Umbraco.Web.UI/config/ClientDependency.config @@ -10,7 +10,7 @@ NOTES: * Compression/Combination/Minification is not enabled unless debug="false" is specified on the 'compiliation' element in the web.config * A new version will invalidate both client and server cache and create new persisted files --> - + - - - - + + + diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index 91c216c727..81c0c03bc8 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -17,10 +17,9 @@ - + + - - @@ -42,4 +41,5 @@ + \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index d9941324be..c718f9ff38 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -594,25 +594,7 @@ namespace Umbraco.Web.Editors return Request.CreateResponse(HttpStatusCode.OK, tempFiles); } - - /// - /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the - /// temporary files that were created. - /// - [DataContract] - private class PostedFiles : IHaveUploadedFiles, INotificationModel - { - public PostedFiles() - { - UploadedFiles = new List(); - Notifications = new List(); - } - public List UploadedFiles { get; private set; } - - [DataMember(Name = "notifications")] - public List Notifications { get; private set; } - } - + /// /// Ensures the item can be moved/copied to the new location /// diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index 0b1c1a56dd..41f183eb8c 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -4,21 +4,39 @@ using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; +using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; using System.Web.Http; +using System.Web.UI.WebControls; using System.Xml; using System.Xml.Linq; +using umbraco; using Umbraco.Core.Auditing; using umbraco.BusinessLogic; +using umbraco.cms.businesslogic.packager; using umbraco.cms.businesslogic.packager.repositories; +using umbraco.cms.businesslogic.web; +using umbraco.cms.presentation.Trees; +using umbraco.presentation.developer.packages; +using umbraco.webservices; +using Umbraco.Core; +using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; using Umbraco.Core.Packaging.Models; using Umbraco.Core.Services; using Umbraco.Web.Models; +using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.UI; +using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; +using File = System.IO.File; +using Notification = Umbraco.Web.Models.ContentEditing.Notification; +using Settings = umbraco.cms.businesslogic.packager.Settings; namespace Umbraco.Web.Editors { @@ -26,9 +44,303 @@ namespace Umbraco.Web.Editors [UmbracoApplicationAuthorize(Core.Constants.Applications.Developer)] public class PackageInstallController : UmbracoAuthorizedJsonController { + [HttpPost] + public IHttpActionResult Uninstall(int packageId) + { + var pack = InstalledPackage.GetById(packageId); + if (pack == null) return NotFound(); + PerformUninstall(pack); + + return Ok(); + } + + /// + /// SORRY :( I didn't have time to put this in a service somewhere - the old packager did this all manually too + /// + /// + protected void PerformUninstall(InstalledPackage pack) + { + if (pack == null) throw new ArgumentNullException("pack"); + + var refreshCache = false; + + //Uninstall templates + foreach (var item in pack.Data.Templates.ToArray()) + { + int nId; + if (int.TryParse(item, out nId) == false) continue; + var found = Services.FileService.GetTemplate(nId); + if (found != null) + { + ApplicationContext.Services.FileService.DeleteTemplate(found.Alias, Security.GetUserId()); + } + pack.Data.Templates.Remove(nId.ToString()); + } + + //Uninstall macros + foreach (var item in pack.Data.Macros.ToArray()) + { + int nId; + if (int.TryParse(item, out nId) == false) continue; + var macro = Services.MacroService.GetById(nId); + if (macro != null) + { + Services.MacroService.Delete(macro); + } + pack.Data.Macros.Remove(nId.ToString()); + } + + //Remove Document Types + var contentTypes = new List(); + var contentTypeService = Services.ContentTypeService; + foreach (var item in pack.Data.Documenttypes.ToArray()) + { + int nId; + if (int.TryParse(item, out nId) == false) continue; + var contentType = contentTypeService.GetContentType(nId); + if (contentType == null) continue; + contentTypes.Add(contentType); + pack.Data.Documenttypes.Remove(nId.ToString(CultureInfo.InvariantCulture)); + // refresh content cache when document types are removed + refreshCache = true; + } + + //Order the DocumentTypes before removing them + if (contentTypes.Any()) + { + var orderedTypes = from contentType in contentTypes + orderby contentType.ParentId descending, contentType.Id descending + select contentType; + foreach (var contentType in orderedTypes) + { + contentTypeService.Delete(contentType); + } + } + + //Remove Dictionary items + foreach (var item in pack.Data.DictionaryItems.ToArray()) + { + int nId; + if (int.TryParse(item, out nId) == false) continue; + var di = Services.LocalizationService.GetDictionaryItemById(nId); + if (di != null) + { + Services.LocalizationService.Delete(di); + } + pack.Data.DictionaryItems.Remove(nId.ToString()); + } + + //Remove Data types + foreach (var item in pack.Data.DataTypes.ToArray()) + { + int nId; + if (int.TryParse(item, out nId) == false) continue; + var dtd = Services.DataTypeService.GetDataTypeDefinitionById(nId); + if (dtd != null) + { + Services.DataTypeService.Delete(dtd); + } + pack.Data.DataTypes.Remove(nId.ToString()); + } + + pack.Save(); + + // uninstall actions + //TODO: We should probably report errors to the UI!! + // This never happened before though, but we should do something now + if (pack.Data.Actions.IsNullOrWhiteSpace() == false) + { + try + { + var actionsXml = new XmlDocument(); + actionsXml.LoadXml("" + pack.Data.Actions + ""); + + LogHelper.Debug("executing undo actions: {0}", () => actionsXml.OuterXml); + + foreach (XmlNode n in actionsXml.DocumentElement.SelectNodes("//Action")) + { + try + { + global::umbraco.cms.businesslogic.packager.PackageAction + .UndoPackageAction(pack.Data.Name, n.Attributes["alias"].Value, n); + } + catch (Exception ex) + { + LogHelper.Error("An error occurred running undo actions", ex); + } + } + } + catch (Exception ex) + { + LogHelper.Error("An error occurred running undo actions", ex); + } + } + + //moved remove of files here so custom package actions can still undo + //Remove files + foreach (var item in pack.Data.Files.ToArray()) + { + //here we need to try to find the file in question as most packages does not support the tilde char + var file = IOHelper.FindFile(item); + if (file != null) + { + var filePath = IOHelper.MapPath(file); + if (File.Exists(filePath)) + { + File.Delete(filePath); + + } + } + pack.Data.Files.Remove(file); + } + pack.Save(); + pack.Delete(Security.GetUserId()); + + //TODO: Legacy - probably not needed + if (refreshCache) + { + library.RefreshContent(); + } + TreeDefinitionCollection.Instance.ReRegisterTrees(); + global::umbraco.BusinessLogic.Actions.Action.ReRegisterActionsAndHandlers(); + } + + + public IEnumerable GetInstalled() + { + return data.GetAllPackages(IOHelper.MapPath(Settings.InstalledPackagesSettings)) + .Select(pack => new InstalledPackageModel + { + Name = pack.Name, + Id = pack.Id, + Author = pack.Author, + Version = pack.Version, + Url = pack.Url, + License = pack.License, + LicenseUrl = pack.LicenseUrl, + Files = pack.Files + }).ToList(); + } + + private void PopulateFromPackageData(LocalPackageInstallModel model) + { + var ins = new global::umbraco.cms.businesslogic.packager.Installer(Security.CurrentUser.Id); + //this will load in all the metadata too + var tempDir = ins.Import(model.ZipFilePath, false); + + model.TemporaryDirectoryPath = Path.Combine(SystemDirectories.Data, tempDir); + model.Name = ins.Name; + model.Author = ins.Author; + model.AuthorUrl = ins.AuthorUrl; + model.License = ins.License; + model.LicenseUrl = ins.LicenseUrl; + model.ReadMe = ins.ReadMe; + model.ConflictingMacroAliases = ins.ConflictingMacroAliases; + model.ConflictingStyleSheetNames = ins.ConflictingStyleSheetNames; + model.ConflictingTemplateAliases = ins.ConflictingTemplateAliases; + model.ContainsBinaryFileErrors = ins.ContainsBinaryFileErrors; + model.ContainsLegacyPropertyEditors = ins.ContainsLegacyPropertyEditors; + model.ContainsMacroConflict = ins.ContainsMacroConflict; + model.ContainsStyleSheetConflicts = ins.ContainsStyleSheeConflicts; + model.ContainsTemplateConflicts = ins.ContainsTemplateConflicts; + model.ContainsUnsecureFiles = ins.ContainsUnsecureFiles; + model.Url = ins.Url; + model.Version = ins.Version; + + model.UmbracoVersion = ins.RequirementsType == RequirementsType.Strict + ? string.Format("{0}.{1}.{2}", ins.RequirementsMajor, ins.RequirementsMinor, ins.RequirementsPatch) + : string.Empty; + + //now we need to check for version comparison + model.IsCompatible = true; + if (ins.RequirementsType == RequirementsType.Strict) + { + var packageMinVersion = new System.Version(ins.RequirementsMajor, ins.RequirementsMinor, ins.RequirementsPatch); + if (UmbracoVersion.Current < packageMinVersion) + { + model.IsCompatible = false; + } + } + } + + [HttpPost] + [FileUploadCleanupFilter(false)] + public async Task UploadLocalPackage() + { + if (Request.Content.IsMimeMultipartContent() == false) + { + throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); + } + + var root = IOHelper.MapPath("~/App_Data/TEMP/FileUploads"); + //ensure it exists + Directory.CreateDirectory(root); + var provider = new MultipartFormDataStreamProvider(root); + + var result = await Request.Content.ReadAsMultipartAsync(provider); + + //must have a file + if (result.FileData.Count == 0) + { + throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); + } + + //TODO: App/Tree Permissions? + var model = new LocalPackageInstallModel + { + PackageGuid = Guid.NewGuid() + }; + + //get the files + foreach (var file in result.FileData) + { + var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }); + var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower(); + + //TODO: Only allow .zip + if (ext.InvariantEquals("zip") || ext.InvariantEquals("umb")) + { + //TODO: Currently it has to be here, it's not ideal but that's the way it is right now + var packageTempDir = IOHelper.MapPath(SystemDirectories.Data); + + //ensure it's there + Directory.CreateDirectory(packageTempDir); + + //copy it - must always be '.umb' for the installer thing to work + //the file name must be a GUID - this is what the packager expects (strange yes) + //because essentially this is creating a temporary package Id that will be used + //for unpacking/installing/etc... + model.ZipFilePath = model.PackageGuid + ".umb"; + var packageTempFileLocation = Path.Combine(packageTempDir, model.ZipFilePath); + File.Copy(file.LocalFileName, packageTempFileLocation, true); + + //Populate the model from the metadata in the package file (zip file) + PopulateFromPackageData(model); + + //TODO: We need to add the 'strict' requirement to the installer + } + else + { + model.Notifications.Add(new Notification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + Services.TextService.Localize("media/disallowedFileType"), + SpeechBubbleIcon.Warning)); + } + + } + + return model; + + } + + /// + /// Gets the package from Our to install + /// + /// + /// [HttpGet] - public PackageInstallModel Fetch(string packageGuid) + public LocalPackageInstallModel Fetch(string packageGuid) { //Default path string path = Path.Combine("packages", packageGuid + ".umb"); @@ -40,26 +352,52 @@ namespace Umbraco.Web.Editors path = our.fetch(packageGuid, Security.CurrentUser.Id); } } - - var p = new PackageInstallModel(); - p.PackageGuid = Guid.Parse(packageGuid); - p.RepositoryGuid = Guid.Parse("65194810-1f85-11dd-bd0b-0800200c9a66"); - p.ZipFilePath = path; - //p.ZipFilePath = Path.Combine("temp", "package.umb"); - return p; + var model = new LocalPackageInstallModel + { + PackageGuid = Guid.Parse(packageGuid), + RepositoryGuid = Guid.Parse("65194810-1f85-11dd-bd0b-0800200c9a66"), + ZipFilePath = path + }; + + //Populate the model from the metadata in the package file (zip file) + PopulateFromPackageData(model); + + return model; } + /// + /// Extracts the package zip and gets the packages information + /// + /// + /// [HttpPost] public PackageInstallModel Import(PackageInstallModel model) { var ins = new global::umbraco.cms.businesslogic.packager.Installer(Security.CurrentUser.Id); - model.TemporaryDirectoryPath = Path.Combine(SystemDirectories.Data, ins.Import(model.ZipFilePath)); + + var tempPath = ins.Import(model.ZipFilePath); + //now we need to check for version comparison + if (ins.RequirementsType == RequirementsType.Strict) + { + var packageMinVersion = new System.Version(ins.RequirementsMajor, ins.RequirementsMinor, ins.RequirementsPatch); + if (UmbracoVersion.Current < packageMinVersion) + { + throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse("This package cannot be installed, it requires a minimum Umbraco version of " + packageMinVersion)); + } + } + + model.TemporaryDirectoryPath = Path.Combine(SystemDirectories.Data, tempPath); model.Id = ins.CreateManifest( IOHelper.MapPath(model.TemporaryDirectoryPath), model.PackageGuid.ToString(), model.RepositoryGuid.ToString()); return model; } + /// + /// Installs the package files + /// + /// + /// [HttpPost] public PackageInstallModel InstallFiles(PackageInstallModel model) { @@ -69,7 +407,11 @@ namespace Umbraco.Web.Editors return model; } - + /// + /// Installs the packages data/business logic + /// + /// + /// [HttpPost] public PackageInstallModel InstallData(PackageInstallModel model) { @@ -79,11 +421,16 @@ namespace Umbraco.Web.Editors return model; } - + /// + /// Cleans up the package installation + /// + /// + /// [HttpPost] - public PackageInstallModel CleanUp(PackageInstallModel model) + public PackageInstallResult CleanUp(PackageInstallModel model) { var ins = new global::umbraco.cms.businesslogic.packager.Installer(Security.CurrentUser.Id); + var tempDir = IOHelper.MapPath(model.TemporaryDirectoryPath); ins.LoadConfig(IOHelper.MapPath(model.TemporaryDirectoryPath)); ins.InstallCleanUp(model.Id, IOHelper.MapPath(model.TemporaryDirectoryPath)); @@ -96,7 +443,30 @@ namespace Umbraco.Web.Editors global::umbraco.BusinessLogic.Actions.Action.ReRegisterActionsAndHandlers(); - return model; + var redirectUrl = ""; + if (ins.Control.IsNullOrWhiteSpace()) + { + redirectUrl = string.Format("/developer/framed/{0}", + Uri.EscapeDataString( + string.Format("/umbraco/developer/Packages/installer.aspx?installing=custominstaller&dir={0}&pId={1}&customUrl={2}", tempDir, model.Id, ins.Url))); + } + else + { + redirectUrl = string.Format("/developer/framed/{0}", + Uri.EscapeDataString( + string.Format("/umbraco/developer/Packages/installer.aspx?installing=custominstaller&dir={0}&pId={1}&customControl={2}&customUrl={3}", tempDir, model.Id, ins.Control, ins.Url))); + } + + return new PackageInstallResult + { + Id = model.Id, + ZipFilePath = model.ZipFilePath, + PackageGuid = model.PackageGuid, + RepositoryGuid = model.RepositoryGuid, + TemporaryDirectoryPath = model.TemporaryDirectoryPath, + PostInstallationPath = redirectUrl + }; + } diff --git a/src/Umbraco.Web/Models/ContentEditing/InstalledPackageModel.cs b/src/Umbraco.Web/Models/ContentEditing/InstalledPackageModel.cs new file mode 100644 index 0000000000..0bf6986e47 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/InstalledPackageModel.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract] + public class InstalledPackageModel + { + [DataMember(Name = "id")] + public int Id { get; set; } + + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "author")] + public string Author { get; set; } + + [DataMember(Name = "files")] + public IEnumerable Files { get; set; } + + [DataMember(Name = "version")] + public string Version { get; set; } + + [DataMember(Name = "url")] + public string Url { get; set; } + + [DataMember(Name = "license")] + public string License { get; set; } + + [DataMember(Name = "licenseUrl")] + public string LicenseUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/PostedFiles.cs b/src/Umbraco.Web/Models/ContentEditing/PostedFiles.cs new file mode 100644 index 0000000000..7793787670 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/PostedFiles.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// This is used for the response of PostAddFile so that we can analyze the response in a filter and remove the + /// temporary files that were created. + /// + [DataContract] + internal class PostedFiles : IHaveUploadedFiles, INotificationModel + { + public PostedFiles() + { + UploadedFiles = new List(); + Notifications = new List(); + } + public List UploadedFiles { get; private set; } + + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/LocalPackageInstallModel.cs b/src/Umbraco.Web/Models/LocalPackageInstallModel.cs new file mode 100644 index 0000000000..abb7c85cec --- /dev/null +++ b/src/Umbraco.Web/Models/LocalPackageInstallModel.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Models +{ + /// + /// A model that represents uploading a local package + /// + [DataContract(Name = "localPackageInstallModel")] + public class LocalPackageInstallModel : PackageInstallModel, IHaveUploadedFiles, INotificationModel + { + public LocalPackageInstallModel() + { + UploadedFiles = new List(); + Notifications = new List(); + } + + public List UploadedFiles { get; private set; } + + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } + + /// + /// A flag to determine if this package is compatible to be installed + /// + [DataMember(Name = "isCompatible")] + public bool IsCompatible { get; set; } + + /// + /// The minimum umbraco version that this package is pinned to + /// + [DataMember(Name = "umbracoVersion")] + public string UmbracoVersion { get; set; } + + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "url")] + public string Url { get; set; } + + [DataMember(Name = "version")] + public string Version { get; set; } + + [DataMember(Name = "containsUnsecureFiles")] + public bool ContainsUnsecureFiles { get; set; } + + [DataMember(Name = "containsTemplateConflicts")] + public bool ContainsTemplateConflicts { get; set; } + + [DataMember(Name = "containsStyleSheetConflicts")] + public bool ContainsStyleSheetConflicts { get; set; } + + [DataMember(Name = "containsMacroConflict")] + public bool ContainsMacroConflict { get; set; } + + [DataMember(Name = "containsLegacyPropertyEditors")] + public bool ContainsLegacyPropertyEditors { get; set; } + + [DataMember(Name = "containsBinaryFileErrors")] + public bool ContainsBinaryFileErrors { get; set; } + + [DataMember(Name = "conflictingTemplateAliases")] + public IDictionary ConflictingTemplateAliases { get; set; } + + [DataMember(Name = "conflictingStyleSheetNames")] + public IDictionary ConflictingStyleSheetNames { get; set; } + + [DataMember(Name = "conflictingMacroAliases")] + public IDictionary ConflictingMacroAliases { get; set; } + + [DataMember(Name = "readMe")] + public string ReadMe { get; set; } + + [DataMember(Name = "licenseUrl")] + public string LicenseUrl { get; set; } + + [DataMember(Name = "license")] + public string License { get; set; } + + [DataMember(Name = "authorUrl")] + public string AuthorUrl { get; set; } + + [DataMember(Name = "author")] + public string Author { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/PackageInstallModel.cs b/src/Umbraco.Web/Models/PackageInstallModel.cs index 34b28843b6..f903fc1880 100644 --- a/src/Umbraco.Web/Models/PackageInstallModel.cs +++ b/src/Umbraco.Web/Models/PackageInstallModel.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.Text; @@ -10,7 +9,7 @@ namespace Umbraco.Web.Models [DataContract(Name = "packageInstallModel")] public class PackageInstallModel { - [DataMember(Name="id")] + [DataMember(Name = "id")] public int Id { get; set; } [DataMember(Name = "packageGuid")] @@ -24,5 +23,7 @@ namespace Umbraco.Web.Models [DataMember(Name = "zipFilePath")] public string ZipFilePath { get; set; } + + } } diff --git a/src/Umbraco.Web/Models/PackageInstallResult.cs b/src/Umbraco.Web/Models/PackageInstallResult.cs new file mode 100644 index 0000000000..bb76bc663b --- /dev/null +++ b/src/Umbraco.Web/Models/PackageInstallResult.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models +{ + /// + /// Model that is returned when a package is totally finished installing + /// + public class PackageInstallResult : PackageInstallModel + { + [DataMember(Name = "postInstallationPath")] + public string PostInstallationPath { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 67da0cf355..35ae7cabfd 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -19,7 +19,7 @@ using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Trees { [UmbracoTreeAuthorize(Constants.Trees.DataTypes)] - [Tree(Constants.Applications.Developer, Constants.Trees.DataTypes)] + [Tree(Constants.Applications.Developer, Constants.Trees.DataTypes, null, sortOrder:1)] [PluginController("UmbracoTrees")] [CoreTree] public class DataTypeTreeController : TreeController diff --git a/src/Umbraco.Web/Trees/PackagesTreeController.cs b/src/Umbraco.Web/Trees/PackagesTreeController.cs new file mode 100644 index 0000000000..b2df155112 --- /dev/null +++ b/src/Umbraco.Web/Trees/PackagesTreeController.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Net.Http.Formatting; +using System.Web.Http; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using umbraco; +using umbraco.BusinessLogic.Actions; +using umbraco.cms.businesslogic.packager; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Services; +using Constants = Umbraco.Core.Constants; +using Umbraco.Core.Services; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.Packages)] + [Tree(Constants.Applications.Developer, Constants.Trees.Packages, null, sortOrder: 0)] + [PluginController("UmbracoTrees")] + [CoreTree] + [LegacyBaseTree(typeof(loadPackager))] + public class PackagesTreeController : TreeController + { + /// + /// Helper method to create a root model for a tree + /// + /// + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + + //this will load in a custom UI instead of the dashboard for the root node + root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Developer, Constants.Trees.Packages, "overview"); + root.Icon = "icon-box"; + + return root; + } + + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var baseUrl = Constants.Applications.Developer + "/packages/"; + + var nodes = new TreeNodeCollection(); + + var createdPackages = CreatedPackage.GetAllCreatedPackages(); + + if (id == "created") + { + nodes.AddRange( + createdPackages + .OrderBy(entity => entity.Data.Name) + .Select(dt => + { + var node = CreateTreeNode(dt.Data.Id.ToString(), id, queryStrings, dt.Data.Name, "icon-inbox", false, + string.Format("/{0}/framed/{1}", + queryStrings.GetValue("application"), + Uri.EscapeDataString("developer/Packages/EditPackage.aspx?id=" + dt.Data.Id))); + return node; + })); + } + else + { + //must be root + var node = CreateTreeNode( + "created", + id, + queryStrings, + Services.TextService.Localize("treeHeaders/createdPackages"), + "icon-folder", + createdPackages.Count > 0, + string.Empty); + + + + //TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now. + node.AdditionalData["jsClickCallback"] = "javascript:void(0);"; + + nodes.Add(node); + } + + + + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + + // Root actions + menu.Items.Add(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias))) + .ConvertLegacyMenuItem(null, Constants.Trees.Packages, queryStrings.GetValue("application")); + + if (id == "created") + { + menu.Items.Add( + Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true); + } + + return menu; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ed239bde23..622752e310 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -356,18 +356,22 @@ + + + + @@ -404,6 +408,7 @@ + diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 1e013da452..6c4eef7862 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -331,6 +331,9 @@ namespace umbraco using (var safeXml = GetSafeXmlWriter(false)) { + //TODO: This can be null: safeXml.Xml!!!! + + var node = safeXml.Xml.GetElementById(c.Id.ToString(CultureInfo.InvariantCulture)); if (node == null) return; var attr = node.GetAttributeNode("sortOrder"); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackager.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackager.cs index 5df6cc6f2a..5470d530ad 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackager.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackager.cs @@ -30,7 +30,8 @@ namespace umbraco /// /// Handles loading of the packager application into the developer application tree /// - [Tree(Constants.Applications.Developer, "packager", "Packages", sortOrder: 3)] + //[Tree(Constants.Applications.Developer, "packager", "Packages", sortOrder: 3)] + [Obsolete("This is no longer used and will be removed from the codebase in the future")] public class loadPackager : BaseTree { #region TreeI Members diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs index 7bbf789981..154f995618 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadPackages.cs @@ -12,7 +12,8 @@ using umbraco.interfaces; namespace umbraco { - [Tree(Constants.Applications.Developer, "packagerPackages", "Packager Packages", initialize: false, sortOrder: 1)] + //[Tree(Constants.Applications.Developer, "packagerPackages", "Packager Packages", initialize: false, sortOrder: 1)] + [Obsolete("This is no longer used and will be removed from the codebase in the future")] public class loadPackages : BaseTree { diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs index ceae3d5659..af26a25efe 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Packages/editPackage.aspx.cs @@ -12,6 +12,7 @@ using umbraco.cms.businesslogic.template; using umbraco.cms.businesslogic.web; using umbraco.cms.presentation.Trees; using umbraco.controls; +using Umbraco.Core; using Umbraco.Core.IO; namespace umbraco.presentation.developer.packages @@ -65,8 +66,8 @@ namespace umbraco.presentation.developer.packages if (Page.IsPostBack == false) { ClientTools - .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) - .SyncTree("-1,init," + loadPackages.PACKAGE_TREE_PREFIX + createdPackage.Data.Id, false); + .SetActiveTreeType(Constants.Trees.Packages) + .SyncTree("-1,created," + createdPackage.Data.Id, false); packageAuthorName.Text = pack.Author; packageAuthorUrl.Text = pack.AuthorUrl; @@ -182,8 +183,8 @@ namespace umbraco.presentation.developer.packages else { ClientTools - .SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree().Tree.Alias) - .SyncTree("-1,init," + loadPackages.PACKAGE_TREE_PREFIX + createdPackage.Data.Id, true); + .SetActiveTreeType(Constants.Trees.Packages) + .SyncTree("-1,created," + createdPackage.Data.Id, true); } } } diff --git a/src/umbraco.cms/businesslogic/Packager/Installer.cs b/src/umbraco.cms/businesslogic/Packager/Installer.cs index 0225dd94a8..fc3c866327 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/umbraco.cms/businesslogic/Packager/Installer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Globalization; using System.IO; using System.Xml; @@ -21,6 +22,12 @@ using umbraco.interfaces; namespace umbraco.cms.businesslogic.packager { + public enum RequirementsType + { + Strict, + Legacy + } + /// /// The packager is a component which enables sharing of both data and functionality components between different umbraco installations. /// @@ -88,6 +95,10 @@ namespace umbraco.cms.businesslogic.packager public int RequirementsMinor { get; private set; } public int RequirementsPatch { get; private set; } + public RequirementsType RequirementsType { get; private set; } + + public string IconUrl { get; private set; } + /// /// The xmldocument, describing the contents of a package. /// @@ -98,16 +109,16 @@ namespace umbraco.cms.businesslogic.packager /// public Installer() { - initialize(); + Initialize(); } public Installer(int currentUserId) { - initialize(); + Initialize(); _currentUserId = currentUserId; } - private void initialize() + private void Initialize() { ContainsBinaryFileErrors = false; ContainsTemplateConflicts = false; @@ -116,40 +127,50 @@ namespace umbraco.cms.businesslogic.packager ContainsStyleSheeConflicts = false; } + [Obsolete("Use the ctor with all parameters")] + [EditorBrowsable(EditorBrowsableState.Never)] + public Installer(string name, string version, string url, string license, string licenseUrl, string author, string authorUrl, int requirementsMajor, int requirementsMinor, int requirementsPatch, string readme, string control) + { + } + /// /// Constructor /// - /// The name of the package - /// The version of the package - /// The url to a descriptionpage - /// The license under which the package is released (preferably GPL ;)) - /// The url to a licensedescription - /// The original author of the package - /// The url to the Authors website - /// Umbraco version major - /// Umbraco version minor - /// Umbraco version patch - /// The readme text - /// The name of the usercontrol used to configure the package after install - public Installer(string Name, string Version, string Url, string License, string LicenseUrl, string Author, string AuthorUrl, int RequirementsMajor, int RequirementsMinor, int RequirementsPatch, string Readme, string Control) + /// The name of the package + /// The version of the package + /// The url to a descriptionpage + /// The license under which the package is released (preferably GPL ;)) + /// The url to a licensedescription + /// The original author of the package + /// The url to the Authors website + /// Umbraco version major + /// Umbraco version minor + /// Umbraco version patch + /// The readme text + /// The name of the usercontrol used to configure the package after install + /// + /// + public Installer(string name, string version, string url, string license, string licenseUrl, string author, string authorUrl, int requirementsMajor, int requirementsMinor, int requirementsPatch, string readme, string control, RequirementsType requirementsType, string iconUrl) { ContainsBinaryFileErrors = false; ContainsTemplateConflicts = false; ContainsUnsecureFiles = false; ContainsMacroConflict = false; ContainsStyleSheeConflicts = false; - this.Name = Name; - this.Version = Version; - this.Url = Url; - this.License = License; - this.LicenseUrl = LicenseUrl; - this.RequirementsMajor = RequirementsMajor; - this.RequirementsMinor = RequirementsMinor; - this.RequirementsPatch = RequirementsPatch; - this.Author = Author; - this.AuthorUrl = AuthorUrl; - ReadMe = Readme; - this.Control = Control; + this.Name = name; + this.Version = version; + this.Url = url; + this.License = license; + this.LicenseUrl = licenseUrl; + this.RequirementsMajor = requirementsMajor; + this.RequirementsMinor = requirementsMinor; + this.RequirementsPatch = requirementsPatch; + this.RequirementsType = requirementsType; + this.Author = author; + this.AuthorUrl = authorUrl; + this.IconUrl = iconUrl; + ReadMe = readme; + this.Control = control; } #region Public Methods @@ -157,24 +178,25 @@ namespace umbraco.cms.businesslogic.packager /// /// Imports the specified package /// - /// Filename of the umbracopackage + /// Filename of the umbracopackage + /// true if the input file should be deleted after import /// - public string Import(string InputFile) + public string Import(string inputFile, bool deleteFile) { using (DisposableTimer.DebugDuration( - () => "Importing package file " + InputFile, - () => "Package file " + InputFile + "imported")) + () => "Importing package file " + inputFile, + () => "Package file " + inputFile + "imported")) { var tempDir = ""; - if (File.Exists(IOHelper.MapPath(SystemDirectories.Data + Path.DirectorySeparatorChar + InputFile))) + if (File.Exists(IOHelper.MapPath(SystemDirectories.Data + Path.DirectorySeparatorChar + inputFile))) { - var fi = new FileInfo(IOHelper.MapPath(SystemDirectories.Data + Path.DirectorySeparatorChar + InputFile)); + var fi = new FileInfo(IOHelper.MapPath(SystemDirectories.Data + Path.DirectorySeparatorChar + inputFile)); // Check if the file is a valid package if (fi.Extension.ToLower() == ".umb") { try { - tempDir = UnPack(fi.FullName); + tempDir = UnPack(fi.FullName, deleteFile); LoadConfig(tempDir); } catch (Exception unpackE) @@ -186,9 +208,20 @@ namespace umbraco.cms.businesslogic.packager throw new Exception("Error - file isn't a package (doesn't have a .umb extension). Check if the file automatically got named '.zip' upon download."); } else - throw new Exception("Error - file not found. Could find file named '" + IOHelper.MapPath(SystemDirectories.Data + Path.DirectorySeparatorChar + InputFile) + "'"); + throw new Exception("Error - file not found. Could find file named '" + IOHelper.MapPath(SystemDirectories.Data + Path.DirectorySeparatorChar + inputFile) + "'"); return tempDir; } + + } + + /// + /// Imports the specified package + /// + /// Filename of the umbracopackage + /// + public string Import(string inputFile) + { + return Import(inputFile, true); } public int CreateManifest(string tempDir, string guid, string repoGuid) @@ -485,9 +518,21 @@ namespace umbraco.cms.businesslogic.packager Url = Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/url").FirstChild.Value; License = Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/license").FirstChild.Value; LicenseUrl = Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/license").Attributes.GetNamedItem("url").Value; + RequirementsMajor = int.Parse(Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/requirements/major").FirstChild.Value); RequirementsMinor = int.Parse(Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/requirements/minor").FirstChild.Value); RequirementsPatch = int.Parse(Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/requirements/patch").FirstChild.Value); + + var reqNode = Config.DocumentElement.SelectSingleNode("/umbPackage/info/package/requirements"); + RequirementsType = reqNode != null && reqNode.Attributes != null && reqNode.Attributes["type"] != null + ? Enum.Parse(reqNode.Attributes["type"].Value, true) + : RequirementsType.Legacy; + var iconNode = Config.DocumentElement.SelectSingleNode("/umbPackage/info/author/iconUrl"); + if (iconNode != null) + { + IconUrl = iconNode.FirstChild.Value; + } + Author = Config.DocumentElement.SelectSingleNode("/umbPackage/info/author/name").FirstChild.Value; AuthorUrl = Config.DocumentElement.SelectSingleNode("/umbPackage/info/author/website").FirstChild.Value; @@ -605,7 +650,7 @@ namespace umbraco.cms.businesslogic.packager } catch { } } - + /// /// This uses the old method of fetching and only supports the packages.umbraco.org repository. /// @@ -680,7 +725,8 @@ namespace umbraco.cms.businesslogic.packager return path + fileName; return path + Path.DirectorySeparatorChar + fileName; } - private static string UnPack(string zipName) + + private static string UnPack(string zipName, bool deleteFile) { // Unzip @@ -729,7 +775,12 @@ namespace umbraco.cms.businesslogic.packager // Clean up s.Close(); - File.Delete(zipName); + + if (deleteFile) + { + File.Delete(zipName); + } + return tempDir; diff --git a/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageInstance.cs b/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageInstance.cs index ed25e1f29f..39e806133e 100644 --- a/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageInstance.cs +++ b/src/umbraco.cms/businesslogic/Packager/PackageInstance/PackageInstance.cs @@ -8,269 +8,89 @@ namespace umbraco.cms.businesslogic.packager { public class PackageInstance { - private int _id; + public int Id { get; set; } - private string _name = ""; - private string _url = ""; - private string _folder = ""; - private string _packagePath = ""; - private string _version = ""; + public string RepositoryGuid { get; set; } - private string _author = ""; - private string _authorUrl = ""; + public string PackageGuid { get; set; } - private string _license = ""; - private string _licenseUrl = ""; + public bool HasUpdate { get; set; } - private string _readMe = ""; - - private bool _contentLoadChildNodes = false; - private string _contentNodeId = ""; + public bool EnableSkins { get; set; } - private List _macros = new List(); - private List _templates = new List(); - private List _documentTypes = new List(); - private List _stylesheets = new List(); - private List _files = new List(); - private List _languages = new List(); - private List _dictionaryItems = new List(); - private List _dataTypes = new List(); + public Guid SkinRepoGuid { get; set; } - private string _loadControl = ""; - private string _repoGuid; - private string _packageGuid; - private bool _hasUpdate; - private bool _enableSkins = false; - private Guid _skinRepoGuid = Guid.Empty; - private string _actions; + public string Name { get; set; } - public int Id + public string Url { get; set; } + + public string Folder { get; set; } + + public string PackagePath { get; set; } + + public string Version { get; set; } + + public string Author { get; set; } + + public string AuthorUrl { get; set; } + + + public string License { get; set; } + + public string LicenseUrl { get; set; } + + public string Readme { get; set; } + + public bool ContentLoadChildNodes { get; set; } + + public string ContentNodeId { get; set; } + + public List Macros { get; set; } + + public List Languages { get; set; } + + public List DictionaryItems { get; set; } + + public List Templates { get; set; } + + public List Documenttypes { get; set; } + + public List Stylesheets { get; set; } + + public List Files { get; set; } + + public string LoadControl { get; set; } + + public string Actions { get; set; } + + public List DataTypes { get; set; } + + public PackageInstance() { - get { return _id; } - set {_id = value; } + SkinRepoGuid = Guid.Empty; + Name = ""; + Url = ""; + Folder = ""; + PackagePath = ""; + Version = ""; + Author = ""; + AuthorUrl = ""; + License = ""; + LicenseUrl = ""; + Readme = ""; + ContentNodeId = ""; + Macros = new List(); + Languages = new List(); + DictionaryItems = new List(); + Templates = new List(); + Documenttypes = new List(); + Stylesheets = new List(); + Files = new List(); + LoadControl = ""; + DataTypes = new List(); + EnableSkins = false; + ContentLoadChildNodes = false; } - - public String RepositoryGuid { - get { return _repoGuid; } - set { _repoGuid = value; } - } - - public String PackageGuid { - get { return _packageGuid; } - set { _packageGuid = value; } - } - - public bool HasUpdate { - get { return _hasUpdate; } - set { _hasUpdate = value; } - } - - public bool EnableSkins - { - get { return _enableSkins; } - set { _enableSkins = value; } - } - - public Guid SkinRepoGuid - { - get { return _skinRepoGuid; } - set { _skinRepoGuid = value; } - } - - - public String Name - { - get { return _name; } - set - { - _name = value; - } - } - - public String Url - { - get { return _url; } - set - { - _url = value; - } - } - - public String Folder - { - get { return _folder; } - set - { - _folder = value; - } - } - - public String PackagePath - { - get { return _packagePath; } - set - { - _packagePath = value; - } - } - - public String Version - { - get { return _version; } - set - { - _version = value; - } - } - - public String Author - { - get { return _author; } - set - { - _author = value; - } - } - - public String AuthorUrl - { - get { return _authorUrl; } - set - { - _authorUrl = value; - } - } - - - public String License - { - get { return _license; } - set - { - _license = value; - } - } - - public String LicenseUrl - { - get { return _licenseUrl; } - set - { - _licenseUrl = value; - } - } - - public String Readme - { - get { return _readMe ; } - set - { - _readMe = value; - } - } - - public bool ContentLoadChildNodes - { - get { return _contentLoadChildNodes; } - set - { - _contentLoadChildNodes = value; - } - } - public string ContentNodeId - { - get { return _contentNodeId; } - set - { - _contentNodeId = value; - } - } - - public List Macros - { - get { return _macros; } - set - { - _macros = value; - } - } - - public List Languages { - get { return _languages; } - set { - _languages = value; - } - } - - public List DictionaryItems { - get { return _dictionaryItems; } - set { - _dictionaryItems = value; - } - } - - public List Templates - { - get { return _templates; } - set - { - _templates = value; - } - } - - public List Documenttypes - { - get { return _documentTypes; } - set - { - _documentTypes = value; - } - } - - public List Stylesheets - { - get { return _stylesheets; } - set - { - _stylesheets = value; - } - } - - public List Files - { - get { return _files; } - set - { - _files = value; - } - } - - public String LoadControl - { - get { return _loadControl; } - set - { - _loadControl = value; - } - } - - public String Actions - { - get { return _actions; } - set - { - _actions = value; - } - } - - public List DataTypes { - get { return _dataTypes; } - set { - _dataTypes = value; - } - } - - public PackageInstance() {} } }