Compare commits

...

279 Commits

Author SHA1 Message Date
Shannon 2dc11f6fa1 Fixes new installation user login 2017-11-09 23:05:56 +11:00
Shannon fe2e689865 Merge branch 'dev-v7.8' of https://github.com/umbraco/Umbraco-CMS into dev-v7.8 2017-11-09 22:15:22 +11:00
Shannon ece5fecf12 fixes doc type conflict name so the tour uses "My Home Page" 2017-11-09 22:15:05 +11:00
Mads Rasmussen b21dea84d9 remove markup from tour step title 2017-11-08 15:38:15 +01:00
Shannon 492b0aadc8 ensure that the user login table is created 2017-11-09 01:14:00 +11:00
Shannon b399630202 bumps version 2017-11-09 01:02:09 +11:00
Shannon b9b1fc3a70 Adds a global config switch to enable/disable tours entirely 2017-11-09 00:57:05 +11:00
Shannon 1d52f59657 Puts tours on the file system and loads them from an api controller 2017-11-09 00:41:59 +11:00
Shannon 78f6b8d8bc Fixes up more the the session id validation, makes sure that the GetUserSecondsMiddleWare also validates the session since this is what keeps the user logged in if that option is being used. 2017-11-08 23:57:17 +11:00
Shannon 0afee71e9e Fixes locking issue with SessionIdValidator which was forcing a log out in some cases 2017-11-08 22:15:00 +11:00
Shannon ffa7b922e8 Merge branch 'dev-v7' into dev-v7.8 2017-11-08 19:48:34 +11:00
Shannon e6de9e0004 Merge branch 'dev-v7' into dev-v7.8 2017-11-08 19:25:22 +11:00
Shannon 08aff2654a updates installer image 2017-11-08 15:51:14 +11:00
Shannon Deminick cd0fdae47b Merge pull request #2254 from umbraco/temp-U4-10305
U4-10305 Back office Tour
2017-11-08 14:52:55 +11:00
Shannon 0f3a76809b Merge branch 'dev-v7.8' into temp-U4-10305
# Conflicts:
#	src/Umbraco.Core/Umbraco.Core.csproj
#	src/Umbraco.Web.UI.Client/src/common/directives/components/tree/umbtreeitem.directive.js
2017-11-08 14:52:21 +11:00
Shannon 4f88da2c26 Merge branch 'dev-v7' into dev-v7.8
# Conflicts:
#	src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml
#	src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs
2017-11-08 14:51:27 +11:00
Mads Rasmussen 4ce0553630 add allowDisable flag on welcome tour 2017-11-07 14:53:44 +01:00
Mads Rasmussen b1b285b673 Merge pull request #2269 from umbraco/temp-tour-review-rune
Temp tour review rune - Introduction
2017-11-07 14:46:56 +01:00
Mads Rasmussen c773714e2d fix folder name validation 2017-11-07 14:11:57 +01:00
Mads Rasmussen 2a90478ba7 fixing typos and the properties tab 2017-11-07 14:00:39 +01:00
Rune Hem Strand 93756a62f4 Update popover width 2017-11-07 11:37:23 +01:00
Rune Hem Strand 2343004be1 Manual merge to init.js tour steps 2017-11-07 11:30:21 +01:00
Rune Hem Strand d6fe350a4a Merge branch temp-U4-10305 into temp-tour-review-rune 2017-11-07 11:28:26 +01:00
Warren Buckley 23321f8ac9 Merge pull request #2265 from umbraco/temp-U4-10544
fixes: U4-10544 SPIKE: Register tours
2017-11-07 10:23:58 +00:00
Rune Hem Strand 71902f3b9b Update Media library tour 2017-11-07 10:53:02 +01:00
Rune Hem Strand 13b544521d Update Media library tour 2017-11-07 10:52:39 +01:00
Rune Hem Strand 2cf8c1207d Update View Home Page step 2017-11-07 10:34:24 +01:00
Mads Rasmussen ab8653d3de manual merging 2017-11-06 14:26:55 +01:00
Mads Rasmussen 748a3c167e Merge branch 'temp-U4-10305' into temp-U4-10544
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/common/services/tour.service.js
#	src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js
2017-11-06 13:58:07 +01:00
Mads Rasmussen 5b93b35882 Show "Other" as group title instead of "undefined" 2017-11-06 13:17:31 +01:00
Mads Rasmussen b29ee4df60 don't render tour markup if no tours are registered 2017-11-06 13:16:53 +01:00
Mads Rasmussen d8fe3c6faf adds basic validation to registering and starting tours 2017-11-06 13:09:16 +01:00
Robert 9b7a9fc988 Merge remote-tracking branch 'origin/temp-U4-5896-2' into dev-v7.8 2017-11-03 15:38:08 +01:00
Claus 62485157ad Merge pull request #2270 from umbraco/temp-U4-10286
New cmsMedia database table to track media by media paths for fast lookups
2017-11-03 14:19:06 +01:00
Claus 4ce7b089ca cleanup... 2017-11-03 14:16:24 +01:00
Claus 9e91098ebd adding cmsMedia to the empty recyclebin query. 2017-11-03 14:15:54 +01:00
Claus 49356b31a1 Merge branch 'dev-v7.8' into temp-U4-10286
# Conflicts:
#	src/Umbraco.Core/Umbraco.Core.csproj
2017-11-03 14:08:46 +01:00
Robert 507c2353e9 Merge pull request #2274 from umbraco/temp-U4-10150
U4-10150 Process load balancing instructions on a background thread i…
2017-11-03 11:43:02 +01:00
Robert d02139bbcd Merge pull request #2273 from umbraco/temp-U4-9120
U4-9120 Add instruction count column on the db instructions table
2017-11-03 10:20:57 +01:00
Shannon 8a19b97900 removes unneeded mapping 2017-11-03 18:23:56 +11:00
Shannon 981fad1ec8 fixes test 2017-11-03 18:20:41 +11:00
Shannon 019147c1a1 Moves the session generation to the BackOfficeCookieAuthenticationProvider which is where it should be, so it only gets generated before the cookie is written. Writes the checking cookie inside of a lock to prevent multiple db and cookie writes that are unecessary. 2017-11-03 18:07:17 +11:00
Shannon 782d6100f0 Gets user session management tracking via the database including detecting stale sessions, generating and removing them along with cleaning them up. This takes into account legacy code too. The session is revalidated on a one minute threshold per user so that it's not hammering the databse on every request. 2017-11-03 17:27:23 +11:00
Robert e17690fbed Merge branch 'temp-U4-10601' into temp-U4-10305 2017-11-02 14:41:33 +01:00
Rune Hem Strand d7d1b3f2a0 Update template tour 2017-11-02 12:06:42 +01:00
Shannon 5b19d5e0d8 WIP on login ids 2017-11-02 15:04:54 +11:00
Claus 7c21889239 fixing comment causing confusion. 2017-11-01 19:41:50 +01:00
Rune Hem Strand 52d35c06eb Update tour validation to use valid JS 2017-11-01 17:36:49 +01:00
Rune Hem Strand 52c3ea24d5 Update validation in Create Content tour 2017-11-01 17:34:34 +01:00
Rune Hem Strand 03756ab894 Update Create Content Tour 2017-11-01 17:23:15 +01:00
Mads Rasmussen 8e35d904cb fixes: U4-10617 Update tour to work with new info tab 2017-11-01 13:28:44 +01:00
Rune Hem Strand 9808492509 Update validation on Create Content tour 2017-11-01 11:18:46 +01:00
Rune Hem Strand 81788338e3 Update Create content tour 2017-11-01 11:17:26 +01:00
Mads Rasmussen 3ffe1517e0 Merge pull request #2268 from umbraco/temp-U4-10597
U4-10597 Add attention to clickable element If it isn't clicked
2017-11-01 10:30:11 +01:00
Mads Rasmussen 641e74b747 Lower highlight pulse timeout to 1 sec 2017-11-01 10:20:14 +01:00
Claus 49c316e13d Merge pull request #2066 from arknu/U4-10191-tinymce46
U4-10191: Update to TinyMCE 4.6.x
2017-11-01 09:42:33 +01:00
Claus 5235053523 updating to latest tinymce 2017-11-01 09:27:04 +01:00
Shannon af6d2df823 Fixes % calc for tours since it now requires a promise 2017-11-01 15:54:47 +11:00
Shannon bbe80d882a Simplifies user tour models returned, fixes mapping so it's actually persisted. 2017-11-01 15:45:48 +11:00
Shannon 408ec17201 Merge branch 'temp-U4-10305' into temp-U4-10601 2017-11-01 15:25:50 +11:00
Shannon a4ef487fab Merge branch 'dev-v7.8' into temp-U4-10305
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/less/belle.less
#	src/Umbraco.Web.UI.Client/src/views/components/content/edit.html
#	src/Umbraco.Web.UI.Client/src/views/media/edit.html
2017-11-01 15:24:49 +11:00
Shannon 8208f8de51 Updates codebase to store tour data for a user in the user table, adds new column to store json and adds migration to create the column 2017-11-01 15:11:13 +11:00
Shannon Deminick e8ea7f9d29 Merge pull request #2264 from umbraco/temp-U4-10542
fixes: U4-10542 Fix SVG backdrop bugs in Firefox (maybe other browser…
2017-11-01 13:13:00 +11:00
Shannon Deminick e431a41fc1 Merge pull request #2276 from umbraco/temp-U4-10599
U4-10599 Move "End tour"-button to another position
2017-11-01 12:08:43 +11:00
Mads Rasmussen 1f5c9d2e1b fixes: U4-10590 Saving a document with no tabs throws a javascript error 2017-10-31 14:12:40 +01:00
Rune Hem Strand 372f981d33 Lower Case name validation 2017-10-31 12:05:49 +01:00
Rune Hem Strand 6f03e221c6 Update Create Document Type tour 2017-10-31 12:03:39 +01:00
Rune Hem Strand 90a5adec8f Update Add a property step 2017-10-31 11:45:55 +01:00
Rune Hem Strand 62939385a0 Update Name the tab step 2017-10-31 11:42:41 +01:00
Mads Rasmussen d4729e1483 set highlight pulse timeout to 2 seconds 2017-10-31 11:38:45 +01:00
Rune Hem Strand 9de35f0202 Updated Document Type name to Home Page 2017-10-31 11:35:38 +01:00
Mads Rasmussen 773d249d36 fixes: U4-10600 Add "Don't show this tour again" option 2017-10-31 10:59:13 +01:00
Rune Hem Strand d12a492cb6 Update Create Document Type step 2017-10-31 10:40:03 +01:00
Rune Hem Strand ef0b521083 Update Create document type step 2017-10-31 10:38:46 +01:00
Rune Hem Strand 6261447923 Update Create document type step 2017-10-31 10:34:50 +01:00
Rune Hem Strand d18881c8ad Update Navigate to the Settings sections step 2017-10-31 10:29:23 +01:00
Rune Hem Strand 89596dbe0b Update Create your first Document Type step 2017-10-31 10:27:59 +01:00
Mads Rasmussen 358536ffaa fixes: U4-10599 Move "End tour"-button to another position 2017-10-31 10:20:24 +01:00
Mads Rasmussen 4592f8e8d8 Send app.ready event after tour is ready 2017-10-31 10:03:11 +01:00
Shannon 0038d173a2 U4-10150 Process load balancing instructions on a background thread instead of a request thread 2017-10-31 15:11:45 +11:00
Shannon 68e6ad76fb Make sure the count of instructions takes into account individual ids when using RefreshByIds 2017-10-31 12:39:08 +11:00
Shannon 6462329494 U4-9120 Add instruction count column on the db instructions table 2017-10-31 11:54:08 +11:00
Shannon 2fdd1c0bd2 bump version 2017-10-31 11:52:43 +11:00
Shannon 891bea81bf bumps version and updates DatabaseSchemaResult to check for cmsMedia table, fixes the bulk insert logic to ignore result columns 2017-10-31 11:20:01 +11:00
Shannon f71577f86e Adds data migrations for media path table 2017-10-31 11:20:01 +11:00
Shannon e3e88b3679 Gets the media path being stored in the db, the column is indexed and so is the property type alias, tests are passing for the lookup 2017-10-31 11:20:01 +11:00
Shannon 5dc993890d New cmsMedia database table to track media by media paths for fast lookups 2017-10-31 11:20:01 +11:00
Shannon a2025c14b1 Merge branch 'dev-v7' into dev-v7.8
# Conflicts:
#	src/Umbraco.Core/Umbraco.Core.csproj
2017-10-31 11:18:52 +11:00
Zsolt 34f811c128 Small alignment bug fix 2017-10-30 11:20:37 +01:00
Mads Rasmussen 7e4c284abb U4-10597 Add attention to clickable element If it isn't clicked 2017-10-30 10:45:38 +01:00
Mads Rasmussen 664d0c5160 add folder name validation in media section tour 2017-10-27 15:14:09 +02:00
Rune Hem Strand eb26fb901f Update User profile step 2017-10-27 14:41:26 +02:00
Rune Hem Strand 3141b04376 Update User profile step 2017-10-27 14:39:30 +02:00
Rune Hem Strand 201fb5dc01 Update Tree step 2017-10-27 14:38:11 +02:00
Rune Hem Strand 15e668c056 Update Search step 2017-10-27 14:24:28 +02:00
Rune Hem Strand a42f425971 Update dashboard step 2017-10-27 14:23:29 +02:00
Rune Hem Strand 3f4774855e Update tree step 2017-10-27 14:21:46 +02:00
Rune Hem Strand 28f995cea6 Add main menu step 2017-10-27 14:06:48 +02:00
Rune Hem Strand cfb14d7089 Adds info on how to acces the help drawer. 2017-10-27 13:53:17 +02:00
Mads Rasmussen ef4d41c80a fix type 2017-10-27 10:12:48 +02:00
Mads Rasmussen 584e47f51f update docs 2017-10-27 10:10:34 +02:00
Mads Rasmussen 66e40fcbb0 Merge branch 'temp-U4-10305' into temp-U4-10544
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/common/services/tour.service.js
2017-10-27 10:08:04 +02:00
Mads Rasmussen 4ed236249a Merge pull request #2267 from umbraco/temp-U4-10550
U4-10550 Finalise tour layout in the drawer
2017-10-26 21:28:01 +02:00
Mads Rasmussen 00a4287945 add back class 2017-10-26 21:24:16 +02:00
Mads Rasmussen 92d52bf99b clean up list + remove unused files 2017-10-26 21:14:22 +02:00
Mads Rasmussen 2e611c20d4 fix text positioning in Firefox 2017-10-26 20:35:04 +02:00
Mads Rasmussen ae10f3d21f calculate progress circle font size instead of manually setting it + align colors 2017-10-26 20:10:22 +02:00
Mads Rasmussen 7a9b392f69 add validation to document type tab name 2017-10-26 15:57:00 +02:00
Mads Rasmussen 493d71dba9 open matching tour group if a tour is running 2017-10-26 15:41:43 +02:00
Mads Rasmussen 55d4e4072a add steps for dashboard and tours 2017-10-26 15:39:56 +02:00
Mads Rasmussen f687b34c14 add data elements to editor content 2017-10-26 15:36:45 +02:00
Mads Rasmussen 507517c1ad add data-elements to help drawer 2017-10-26 15:36:18 +02:00
Mads Rasmussen ae8cfb2b23 add getCurrentTour to tour.service 2017-10-26 15:35:46 +02:00
Mads Rasmussen 9048252826 make start tour button green 2017-10-26 14:20:11 +02:00
Zsolt e76819a570 Merge remote-tracking branch 'origin/temp-U4-10550' into temp-U4-10550 2017-10-26 12:58:50 +02:00
Zsolt 87c311d6e7 Fixing the hover on the groups 2017-10-26 12:58:36 +02:00
Mads Rasmussen 86d8eae984 remove unused code 2017-10-26 12:53:23 +02:00
Mads Rasmussen 6aac216763 update tour list when one is completed 2017-10-26 12:50:46 +02:00
Mads Rasmussen b7c3fb7f30 set text position in progress circle with css instead of js 2017-10-26 12:49:36 +02:00
Zsolt a0a3b7e9c9 Changing the css of umbProgressCircle 2017-10-26 12:02:46 +02:00
Mads Rasmussen d98db5e6bc updates to tour layout in drawer 2017-10-26 11:39:49 +02:00
Mads Rasmussen 48adc4c98d only load the getting started tours if the Get started dashboard is installed 2017-10-26 10:18:44 +02:00
Mads Rasmussen 79d4562cb1 fixes: U4-10544 SPIKE: Register tours 2017-10-25 21:59:37 +02:00
Mads Rasmussen f48263e5a2 fixes: U4-10542 Fix SVG backdrop bugs in Firefox (maybe other browser too?) 2017-10-25 20:24:04 +02:00
Mads Rasmussen 63196d7e53 Merge branch 'temp-U4-10305' into temp-U4-10550
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/common/services/tour.service.js
2017-10-25 17:02:46 +02:00
Claus 72a0d614af Merge pull request #2261 from umbraco/temp-U4-10565
U4-10565 Add introduction tour to the overall Umbraco UI
2017-10-25 14:47:22 +02:00
Zsolt 4f8bb7fb51 Adding new components to the help drawer 2017-10-25 12:48:34 +02:00
Claus 1c883be16b Merge remote-tracking branch 'origin/temp-u4-8409' into dev-v7.8 2017-10-25 12:27:34 +02:00
Claus 73d681d674 Merge remote-tracking branch 'origin/dev-v7' into dev-v7.8
# Conflicts:
#	src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs
2017-10-25 11:52:30 +02:00
Zsolt 8daa558997 Revert "adding components to the drawer"
This reverts commit b78fd0f6ac.
2017-10-25 11:46:52 +02:00
Zsolt b78fd0f6ac adding components to the drawer 2017-10-25 11:19:53 +02:00
Mads Rasmussen 831eb3ef5e add icon for link to document for better clarification 2017-10-24 15:17:14 +02:00
Claus fa68bee762 removing left over using statements and references to SharpZipLib. 2017-10-24 15:16:12 +02:00
Mads Rasmussen 796700b4fe fix end tour in custom step 2017-10-24 15:05:20 +02:00
Mads Rasmussen ab09042fdc add options to prevent click in backdrop highlighted area 2017-10-24 15:00:14 +02:00
Mads Rasmussen 041d4a4a8b fix issue in create media dialog where it sometimes refreshes whole page 2017-10-24 13:54:36 +02:00
Mads Rasmussen ee79d7ecdc add custom backdrop opacity to tour steps 2017-10-24 13:35:26 +02:00
Mads Rasmussen e33456d168 add alias to document type save button 2017-10-24 11:46:51 +02:00
Mads Rasmussen df2c104002 remove console.log 2017-10-23 20:15:48 +02:00
Mads Rasmussen f265fa683c don't use index in button group + update text in some tours 2017-10-23 19:58:44 +02:00
Mads Rasmussen 12093ae7ea fix tour steps with ng-if 2017-10-23 09:50:14 +02:00
pijemcolu 6a980e64eb zipPackage to disk 2017-10-23 09:02:28 +02:00
Mads Rasmussen 37071ada78 restyle step loader, fade in steps, fix custom view step positionen 2017-10-19 21:17:26 +02:00
pijemcolu 3c7e820773 remove the directory check 2017-10-19 19:50:27 +02:00
Mads Rasmussen 203837aa84 start intro tour on load (if it hasn't been completed) 2017-10-19 16:01:20 +02:00
Mads Rasmussen 5fe63c2c1a fix lost element header 2017-10-19 14:27:00 +02:00
pijemcolu 2c1d85989f Extract the zip in UnPack(), remove check for directory exists as it should not and that's what the method did previously 2017-10-19 13:14:06 +02:00
Mads Rasmussen d518ab908e add intro/outro step logic, start on intro tour, testing intro steps 2017-10-19 13:11:10 +02:00
pijemcolu bc32ff31d4 remove last 3 dependencies 2017-10-19 12:11:07 +02:00
Mads Rasmussen 7804677f2b use name instead of alias in editor picker element attribute because there can be many editors with the same alias 2017-10-19 11:41:12 +02:00
Mads Rasmussen 27c884f4eb add more data elements for navigating the UI 2017-10-19 09:10:13 +02:00
Mads Rasmussen a966f63bbd Merge pull request #2244 from umbraco/temp-U4-10541
fixes: U4-10541 Use local storage to store completed tours
2017-10-18 11:25:51 +02:00
Mads Rasmussen 76f9e6baee fix bug in step validation 2017-10-18 10:26:50 +02:00
Mads Rasmussen 49f17be9df fixes: U4-10540 Make it possible to run custom logic for a tour step 2017-10-18 08:46:13 +02:00
Stephan 8a114a105e U4-8409 - reduce sharpZipLib dependencies 2017-10-16 19:03:05 +02:00
Warren Buckley 5f9865cc73 Merge pull request #2228 from umbraco/temp-U4-10309
fixes: MBOGA: Redesign the properties tab
2017-10-16 14:57:45 +01:00
Mads Rasmussen b891b18b20 fixes: U4-10541 Use local storage to store completed tours 2017-10-16 13:52:34 +02:00
Mads Rasmussen bc461b1646 change id's to custom attr 2017-10-16 13:46:03 +02:00
Mads Rasmussen b0eeaecf1c remove unused files 2017-10-16 13:41:02 +02:00
Mads Rasmussen 2708f11fcd fix path to angular moment 2017-10-16 12:01:19 +02:00
Mads Rasmussen c59ac4ee0e moved tours from dashboard to service, option to group tours, added functions to get tours from service 2017-10-13 12:29:44 +02:00
Warren Buckley a03aa9cbab Fix in PowerShell build - The 7Zip tool download was giving two paths & thus break the build script 2017-10-13 11:20:45 +01:00
Zsolt 89b714c960 Merge remote-tracking branch 'origin/dev-v7.8' into temp-U4-10305 2017-10-12 10:15:22 +02:00
Mads Rasmussen d9b0b0112e fix upload step in tour 2017-10-12 09:53:41 +02:00
Mads Rasmussen 6640039cdf clean up tours + small fixes 2017-10-11 19:37:14 +02:00
Warren Buckley 70deffd418 Merge pull request #2235 from umbraco/temp-U4-10051
U4-10051 - Help Drawer in prep for Lessons
2017-10-11 14:51:01 +01:00
Mads Rasmussen f31d5b12da add null check to back drop element - fix js errors 2017-10-10 21:09:08 +02:00
Warren Buckley 7fd6155807 Bit too aggressive with the C# cleanup - adds back in HelpController but just with the method that is needed to load the videos in the help section 2017-10-10 16:05:21 +01:00
Warren Buckley 043c677ddc Merge branch 'dev-v7.8' into temp-U4-10051
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/less/components/overlays/umb-overlay-backdrop.less
2017-10-10 13:09:06 +01:00
Warren Buckley f634d9f8b1 Remove the Help/Lessons Controller from the globaly Umbraco.Sys JS object 2017-10-10 12:53:15 +01:00
Warren Buckley db637d9c31 Remove HelpController for Lessons as we lift & shift it over to StarterKit 2017-10-10 12:20:11 +01:00
Mads Rasmussen 24b521881c also prevent clicks inside umb-tour component 2017-10-10 12:22:30 +02:00
Mads Rasmussen 0b3a38ec8f add option to disable all events on backdrop click to prevent dialogs, overlays, etc. form closing 2017-10-10 12:14:10 +02:00
Mads Rasmussen 330673bef9 Clear step custom view if none is set 2017-10-10 09:22:57 +02:00
Mads Rasmussen 661ff0e726 add drop shadow to popover 2017-10-09 21:42:06 +02:00
Mads Rasmussen e1c45fda9a compress the popover a bit more 2017-10-09 21:41:37 +02:00
Mads Rasmussen b45a4a18f2 make backdrop a little lighter 2017-10-09 21:41:14 +02:00
Zsolt 1456e7028a Merge remote-tracking branch 'origin/temp-U4-10305' into temp-U4-10305 2017-10-09 11:41:11 +02:00
Zsolt b38ee33e5a Fixing popover orientation so it is not off-screen 2017-10-09 11:40:57 +02:00
Mads Rasmussen 903a9f93c3 clean up backdrop component 2017-10-09 10:56:11 +02:00
Robert b08376651a Merge pull request #2225 from umbraco/temp-U4-10473
Moved documentType and mediaLink to root node for media section
2017-10-09 10:54:51 +02:00
Robert 75514c7c42 Changed angular to make sure the values are matching 2017-10-09 10:43:03 +02:00
Mads Rasmussen ea6f561c96 move backdrop to its own component 2017-10-09 10:19:49 +02:00
Shannon 053544a578 Changes the type of MediaItemDisplay.ContentType to be what it should be, removes non-required mapping 2017-10-09 15:02:27 +11:00
Zsolt 4308718c88 adedd positioning to the popover div 2017-10-06 14:46:27 +02:00
Mads Rasmussen 1b59ca0412 remove unused code 2017-10-06 11:11:52 +02:00
Mads Rasmussen 282cc0c722 testing scrolling, custom html in content and custom step views 2017-10-06 10:14:33 +02:00
Mads Rasmussen 4584abaa72 add a bunch of element navigation attributes 2017-10-05 13:42:51 +02:00
Zsolt c1ca0c9c2f Merge remote-tracking branch 'origin/temp-U4-10305' into temp-U4-10305 2017-10-04 15:20:15 +02:00
Zsolt 27c260cd03 Sticking the popoup to the highlight 2017-10-04 15:18:20 +02:00
Mads Rasmussen 73a5c55547 add data attribute to tree-item-options 2017-10-04 15:12:50 +02:00
Mads Rasmussen b8831c253e testing different tours 2017-10-04 15:09:26 +02:00
Zsolt b2d3e75566 rounding X and Y coordinate values for SVG 2017-10-04 13:12:53 +02:00
Zsolt cb526170e2 Merge remote-tracking branch 'origin/temp-U4-10305' into temp-U4-10305 2017-10-04 13:03:29 +02:00
Zsolt 93237bfdb8 Style of the Guided tour CSS + HTML 2017-10-04 13:03:24 +02:00
Mads Rasmussen 1c39c741b6 get outerWidth and outerHeight 2017-10-04 13:01:33 +02:00
Mads Rasmussen ec2589f517 add global data attribute to all main editors 2017-10-04 11:35:30 +02:00
Zsolt 2ca6e8be9a Implemented SVG backdrop 2017-10-04 11:17:44 +02:00
Mads Rasmussen b9d1a776cb init tour component 2017-10-04 09:19:33 +02:00
Robert 5eb609d8a0 Renamed a few properties and a class 2017-10-03 15:35:36 +02:00
Mads Rasmussen 4d5069726b Update client side unit tests 2017-09-29 10:44:18 +02:00
Mads Rasmussen 0da8644e77 Merge branch 'dev-v7' into temp-U4-10309 2017-09-29 09:55:32 +02:00
Robert e643189b10 Moved mediaLink mapping directly to autoMapper 2017-09-28 16:07:16 +02:00
Robert 97716d5570 Merge pull request #2205 from umbraco/temp-U4-10420
added event to load audit trail on Properties tab only
2017-09-28 15:32:19 +02:00
Robert 5517252200 Moved documentType and mediaLink to root node for media section
Created Map in MediaModelMapper to cater for the new property (DocumentType)
Wired front-end to match the new properties
2017-09-28 15:21:04 +02:00
Mads Rasmussen 92a5c31ce6 remove publish date in footer 2017-09-28 13:55:11 +02:00
Mads Rasmussen 345c038e8b make sure we don't have unwanted markup in the dom 2017-09-28 10:41:08 +02:00
Mads Rasmussen 59defa6b11 rename event to follow naming convention, fix missing tab id, change so the tab id matches the new "info"-tab 2017-09-28 10:12:01 +02:00
Mads Rasmussen 7e0d7ebbd4 Merge branch 'temp-U4-10309' into temp-U4-10420 2017-09-28 10:03:19 +02:00
Robert c13d4ed235 Merge pull request #2218 from umbraco/temp-U4-10421
fixes: U4-10421 Split up properties tab: One for umbraco properties a…
2017-09-28 09:55:55 +02:00
Robert a47b5035fe Merge pull request #2219 from umbraco/temp-U4-10311
U4-10311 Wire up angular with backend
2017-09-28 09:10:02 +02:00
Shannon f6be389f4f fixes tests 2017-09-28 16:13:50 +10:00
Shannon 252f0fe88e Fixes up the angular side of things to ensure the right property values are wired together correctly 2017-09-28 15:13:33 +10:00
Shannon 387faca71e Simplifies the model mapping, uses strongly typed values 2017-09-28 14:54:18 +10:00
Robert 62cda02ded Some clean up in ContentModelMapper.cs 2017-09-27 16:02:32 +02:00
Mads Rasmussen 3e2a6424bd wire up audit trail log type colors 2017-09-27 14:08:29 +02:00
Mads Rasmussen c0f695bcf9 make grunt linter happy 2017-09-27 14:07:47 +02:00
Robert 2e1e2e4325 Properties tab will be removed if it has no properties 2017-09-27 13:58:33 +02:00
Mads Rasmussen 1316bb5d5b fixes: U4-10421 Split up properties tab: One for umbraco properties and one for user added properties 2017-09-27 12:56:43 +02:00
Robert fe97860694 Changed dataFormatter to work with values from the root node instead of values from properties tab 2017-09-27 11:56:11 +02:00
Robert 98b4aa6bae Cleared more of the not needed code 2017-09-27 10:56:45 +02:00
Robert eb7f4e74f2 Removed unused code as we dont have those properties in the properties tab anymore 2017-09-27 09:31:31 +02:00
Robert 4a9e326267 Remove the rest of umb properties 2017-09-27 09:28:51 +02:00
Robert 67edc6cf9a Deleted updateDate, createDate and createdBy from properties tab 2017-09-26 15:57:50 +02:00
Robert e740838ef7 Moved template and docType from properties tab to root node and linked the values back together in front-end 2017-09-26 15:44:41 +02:00
Robert dec44b1288 Moved templateConfig to root node 2017-09-26 09:20:38 +02:00
Sebastiaan Janssen 98a0729549 Merge pull request #2202 from umbraco/temp-U4-10382
U4-10382 Add end point to get a paginated audit trail
2017-09-25 10:55:09 +02:00
Robert 97868593f8 Tab check is now using id and hash instead of innerHtml 2017-09-21 15:50:22 +02:00
Robert dcdfb074af Forgot to clean after myself :) 2017-09-21 14:09:14 +02:00
Robert a36b1097c5 added event to load audit trail on Properties tab only 2017-09-21 13:56:03 +02:00
Mads Rasmussen e268a05f6e remove client side lesson functions from core - these will be moved to the starter-kit 2017-09-21 11:14:31 +02:00
Mads Rasmussen fb7d22dca8 polish 2017-09-20 20:58:52 +02:00
Mads Rasmussen bac53bc241 rename dynamic user dashboards to "user-dialog" so they aren't confused with the user profiles in the new user section 2017-09-20 20:39:05 +02:00
Mads Rasmussen ce690acda0 move drawer width to less variable + fix backdrops when drawer is open 2017-09-20 20:30:44 +02:00
Mads Rasmussen cb8157dfca Start on a fresh help drawer branch - the other branch was a mess of testing data and nested content merges 2017-09-20 15:46:33 +02:00
Robert 4c7c6e37d1 Forgot to clean after myself :) 2017-09-19 12:02:29 +02:00
Robert 116e53e07d Custom serialization attribute added to AuditType property 2017-09-19 11:49:32 +02:00
Robert a74c4433f7 Mapping the DateStamp and UserName to AuditLog
Refactored how the the Username is displayed due to some misunderstanding on my part.
2017-09-19 11:01:07 +02:00
Robert 2ccf74a921 Adding the userName to the audit comment
Before the
2017-09-19 09:29:24 +02:00
Robert f3a81d82be Merge pull request #2184 from umbraco/temp-U4-10405
fixes: U4-10405 When scheduled publishing it set, change the default …
2017-09-18 13:24:47 +02:00
Sebastiaan Janssen 5451d28fe4 Update unit tests to account for the new AuditMapper 2017-09-14 21:29:24 +02:00
Mads Rasmussen db57268440 add prototype for media properties tab 2017-09-14 21:00:26 +02:00
Mads Rasmussen ac265067e8 move media node info component to media subfolder 2017-09-14 15:41:47 +02:00
Mads Rasmussen 5330830362 Merge branch 'dev-v7.7' into temp-U4-10309
# Conflicts:
#	src/Umbraco.Web.UI.Client/bower.json
#	src/Umbraco.Web.UI.Client/gruntFile.js
2017-09-14 15:28:21 +02:00
Mads Rasmussen 4b6b24b2f5 add empty state to history 2017-09-12 11:56:39 +02:00
Mads Rasmussen 7d5d89e9cb reuse styles for schedule dates 2017-09-12 11:56:13 +02:00
Mads Rasmussen ab85cbf599 remove unused code + clean up styles in general box 2017-09-12 10:24:44 +02:00
Mads Rasmussen c6937618c2 fixes: U4-10405 When scheduled publishing it set, change the default button to "Save and schedule" 2017-09-11 20:02:46 +02:00
Zsolt ef84d63982 Merge remote-tracking branch 'origin/temp-U4-10309' into temp-U4-10309 2017-09-11 13:06:49 +02:00
Zsolt 280a7445f4 media sections properties tab 2017-09-11 13:05:57 +02:00
Mads Rasmussen 5ae8a47f4b wire up audit trail 2017-09-10 22:58:45 +02:00
Shannon bc148d08fa maps avatars in log 2017-09-07 22:24:18 +10:00
Shannon 57a1e73383 Updates endpoints for both c# and js 2017-09-07 22:03:49 +10:00
Shannon c7b9a57795 U4-10382 Add end point to get a paginated audit trail 2017-09-07 17:27:37 +10:00
Shannon eb1edf94d3 Merge branch 'dev-v7.7' into temp-U4-10309 2017-09-06 15:51:01 +10:00
Zsolt c0b230a95e document type now openable + formatted the create date 2017-09-05 15:15:11 +02:00
Zsolt 607c2cc8e7 used localization and language keys on properties tab 2017-09-05 13:38:34 +02:00
Zsolt 48d3a3defd Made timeline more responsive + restructured the date in the footer 2017-09-05 11:57:22 +02:00
Mads Rasmussen 8509285aae move properties/info tab to it's own component 2017-09-04 21:12:47 +02:00
Mads Rasmussen 2413e9951f load in moment with locales globally + remove lazy loading from date picker 2017-09-04 14:04:06 +02:00
Mads Rasmussen 1a444fd703 add date pickers 2017-09-04 12:58:04 +02:00
Zsolt bd68a140ba Merge remote-tracking branch 'origin/temp-U4-10309' into temp-U4-10309 2017-09-01 15:10:50 +02:00
Zsolt 7b34211bb1 bit more responsive + changed the colors to variables in less 2017-09-01 15:09:53 +02:00
Mads Rasmussen fa78223229 make doc type + go to doc type work 2017-08-31 14:00:20 +02:00
Zsolt 39c19dc1d4 Merge remote-tracking branch 'origin/temp-U4-10309' into temp-U4-10309 2017-08-31 12:52:30 +02:00
Zsolt 7b4b6357cb changed more <h> tags + realigned them 2017-08-31 12:47:01 +02:00
Mads Rasmussen 4454290820 make link to document block element, add hover state + open in new tab 2017-08-31 11:56:03 +02:00
Mads Rasmussen d94cd9dc8d Merge branch 'temp-U4-10309' of https://github.com/umbraco/Umbraco-CMS into temp-U4-10309
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/views/components/content/edit.html
2017-08-31 11:34:25 +02:00
Mads Rasmussen 8c07af9505 fake loading the auditTrail 2017-08-31 11:31:41 +02:00
Zsolt d32fb44187 Merge remote-tracking branch 'origin/temp-U4-10309' into temp-U4-10309 2017-08-31 11:29:08 +02:00
Zsolt dd38480ce2 changing <h> tags to span + adding extra description to timeline 2017-08-31 11:29:03 +02:00
Mads Rasmussen 08e8e3918e make template selector work 2017-08-31 11:23:10 +02:00
Zsolt 7a3591d1bd last line of the timeline is not there anymore 2017-08-31 10:20:49 +02:00
Zsolt 59ab7f5c8d Changed the order of meta data 2017-08-31 10:01:36 +02:00
Zsolt c2b2182182 Merge remote-tracking branch 'origin/temp-U4-10309' into temp-U4-10309 2017-08-31 09:51:54 +02:00
Zsolt 66b656f058 added the icon to the link, also the link functions as a link 2017-08-31 09:51:20 +02:00
Mads Rasmussen c8f7ecabf4 add avatar + set correct color on badges for history 2017-08-30 20:32:40 +02:00
Zsolt 9137249510 properties tab UI prototype with dummy data 2017-08-30 16:42:20 +02:00
Asbjørn Riis-Knudsen 841074fc27 U4-10191: Update to TinyMCE 4.6.x 2017-07-24 09:31:47 +02:00
268 changed files with 7762 additions and 1268 deletions
-1
View File
@@ -27,7 +27,6 @@
<dependency id="MiniProfiler" version="[2.1.0, 3.0.0)" />
<dependency id="HtmlAgilityPack" version="[1.4.9.5, 2.0.0)" />
<dependency id="Lucene.Net" version="[2.9.4.1, 3.0.0.0)" />
<dependency id="SharpZipLib" version="[0.86.0, 1.0.0)" />
<dependency id="MySql.Data" version="[6.9.9, 7.0.0)" />
<dependency id="ClientDependency" version="[1.9.2, 2.0.0)" />
<dependency id="ClientDependency-Mvc5" version="[1.8.0.0, 2.0.0)" />
+2 -2
View File
@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.7.4")]
[assembly: AssemblyInformationalVersion("7.7.4")]
[assembly: AssemblyFileVersion("7.8.0")]
[assembly: AssemblyInformationalVersion("7.8.0-beta")]
+47
View File
@@ -1,6 +1,7 @@
using System;
using System.Configuration;
using System.Threading;
using Semver;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.ObjectResolution;
@@ -341,6 +342,52 @@ namespace Umbraco.Core
}
}
/// <summary>
/// Gets the Current Version of the Umbraco Site before an upgrade
/// by using the last/most recent Umbraco Migration that has been run
/// </summary>
/// <returns>A SemVersion of the latest Umbraco DB Migration run</returns>
/// <remarks>
/// NOTE: This existed in the InstallHelper previously but should really be here so it can be re-used if necessary
/// </remarks>
internal SemVersion CurrentVersion()
{
//Set a default version of 0.0.0
var version = new SemVersion(0);
//If we have a db context available, if we don't then we are not installed anyways
if (DatabaseContext.IsDatabaseConfigured && DatabaseContext.CanConnect)
version = DatabaseContext.ValidateDatabaseSchema().DetermineInstalledVersionByMigrations(Services.MigrationEntryService);
if (version != new SemVersion(0))
return version;
// If we aren't able to get a result from the umbracoMigrations table then use the version in web.config, if it's available
if (string.IsNullOrWhiteSpace(GlobalSettings.ConfigurationStatus))
return version;
var configuredVersion = GlobalSettings.ConfigurationStatus;
string currentComment = null;
var current = configuredVersion.Split('-');
if (current.Length > 1)
currentComment = current[1];
Version currentVersion;
if (Version.TryParse(current[0], out currentVersion))
{
version = new SemVersion(
currentVersion.Major,
currentVersion.Minor,
currentVersion.Build,
string.IsNullOrWhiteSpace(currentComment) ? null : currentComment,
currentVersion.Revision > 0 ? currentVersion.Revision.ToString() : null);
}
return version;
}
private void AssertIsNotReady()
{
if (this.IsReady)
@@ -0,0 +1,18 @@
using System.Configuration;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class BackOfficeElement : UmbracoConfigurationElement, IBackOfficeSection
{
[ConfigurationProperty("tours")]
internal TourConfigElement Tours
{
get { return (TourConfigElement)this["tours"]; }
}
ITourSection IBackOfficeSection.Tours
{
get { return Tours; }
}
}
}
@@ -0,0 +1,7 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
public interface IBackOfficeSection
{
ITourSection Tours { get; }
}
}
@@ -0,0 +1,7 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
public interface ITourSection
{
bool EnableTours { get; }
}
}
@@ -5,6 +5,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
{
public interface IUmbracoSettingsSection : IUmbracoConfigurationSection
{
IBackOfficeSection BackOffice { get; }
IContentSection Content { get; }
ISecuritySection Security { get; }
@@ -0,0 +1,17 @@
using System.Configuration;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class TourConfigElement : UmbracoConfigurationElement, ITourSection
{
//disabled by default so that upgraders don't get it enabled by default
//TODO: we probably just want to disable the initial one from automatically loading ?
[ConfigurationProperty("enable", DefaultValue = false)]
public bool EnableTours
{
get { return (bool)this["enable"]; }
}
//TODO: We could have additional filters, etc... defined here
}
}
@@ -8,6 +8,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
public class UmbracoSettingsSection : ConfigurationSection, IUmbracoSettingsSection
{
[ConfigurationProperty("backOffice")]
internal BackOfficeElement BackOffice
{
get { return (BackOfficeElement)this["backOffice"]; }
}
[ConfigurationProperty("content")]
internal ContentElement Content
{
@@ -149,6 +155,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
get { return Templates; }
}
IBackOfficeSection IUmbracoSettingsSection.BackOffice
{
get { return BackOffice; }
}
IDeveloperSection IUmbracoSettingsSection.Developer
{
get { return Developer; }
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
{
public class UmbracoVersion
{
private static readonly Version Version = new Version("7.7.4");
private static readonly Version Version = new Version("7.8.0");
/// <summary>
/// Gets the current version of Umbraco.
+2 -2
View File
@@ -26,13 +26,13 @@ namespace Umbraco.Core
var ipAddress = httpContext.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (string.IsNullOrEmpty(ipAddress))
return httpContext.Request.ServerVariables["REMOTE_ADDR"];
return httpContext.Request.UserHostAddress;
var addresses = ipAddress.Split(',');
if (addresses.Length != 0)
return addresses[0];
return httpContext.Request.ServerVariables["REMOTE_ADDR"];
return httpContext.Request.UserHostAddress;
}
catch (System.Exception ex)
{
+13 -1
View File
@@ -2,18 +2,30 @@
namespace Umbraco.Core.Models
{
public sealed class AuditItem : Entity, IAggregateRoot
public sealed class AuditItem : Entity, IAuditItem
{
/// <summary>
/// Constructor for creating an item to be created
/// </summary>
/// <param name="objectId"></param>
/// <param name="comment"></param>
/// <param name="type"></param>
/// <param name="userId"></param>
public AuditItem(int objectId, string comment, AuditType type, int userId)
{
DisableChangeTracking();
Id = objectId;
Comment = comment;
AuditType = type;
UserId = userId;
EnableChangeTracking();
}
public string Comment { get; private set; }
public AuditType AuditType { get; private set; }
public int UserId { get; private set; }
}
}
+12
View File
@@ -0,0 +1,12 @@
using System;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Models
{
public interface IAuditItem : IAggregateRoot
{
string Comment { get; }
AuditType AuditType { get; }
int UserId { get; }
}
}
@@ -40,19 +40,6 @@ namespace Umbraco.Core.Models.Identity
identityUser.EnableChangeTracking();
});
config.CreateMap<BackOfficeIdentityUser, UserData>()
.ConstructUsing((BackOfficeIdentityUser user) => new UserData(Guid.NewGuid().ToString("N"))) //this is the 'session id'
.ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id))
.ForMember(detail => detail.AllowedApplications, opt => opt.MapFrom(user => user.AllowedSections))
.ForMember(detail => detail.Roles, opt => opt.MapFrom(user => user.Roles.Select(x => x.RoleId).ToArray()))
.ForMember(detail => detail.RealName, opt => opt.MapFrom(user => user.Name))
//When mapping to UserData which is used in the authcookie we want ALL start nodes including ones defined on the groups
.ForMember(detail => detail.StartContentNodes, opt => opt.MapFrom(user => user.CalculatedContentStartNodeIds))
//When mapping to UserData which is used in the authcookie we want ALL start nodes including ones defined on the groups
.ForMember(detail => detail.StartMediaNodes, opt => opt.MapFrom(user => user.CalculatedMediaStartNodeIds))
.ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.UserName))
.ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.Culture))
.ForMember(detail => detail.SessionId, opt => opt.MapFrom(user => user.SecurityStamp.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString("N") : user.SecurityStamp));
}
private string GetPasswordHash(string storedPass)
+6 -1
View File
@@ -69,6 +69,11 @@ namespace Umbraco.Core.Models.Membership
/// <summary>
/// Will hold the media file system relative path of the users custom avatar if they uploaded one
/// </summary>
string Avatar { get; set; }
string Avatar { get; set; }
/// <summary>
/// A Json blob stored for recording tour data for a user
/// </summary>
string TourData { get; set; }
}
}
+13 -1
View File
@@ -99,6 +99,7 @@ namespace Umbraco.Core.Models.Membership
private string _name;
private string _securityStamp;
private string _avatar;
private string _tourData;
private int _sessionTimeout;
private int[] _startContentIds;
private int[] _startMediaIds;
@@ -132,6 +133,7 @@ namespace Umbraco.Core.Models.Membership
public readonly PropertyInfo SecurityStampSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.SecurityStamp);
public readonly PropertyInfo AvatarSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.Avatar);
public readonly PropertyInfo TourDataSelector = ExpressionHelper.GetPropertyInfo<User, string>(x => x.TourData);
public readonly PropertyInfo SessionTimeoutSelector = ExpressionHelper.GetPropertyInfo<User, int>(x => x.SessionTimeout);
public readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo<User, int[]>(x => x.StartContentIds);
public readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo<User, int[]>(x => x.StartMediaIds);
@@ -464,8 +466,18 @@ namespace Umbraco.Core.Models.Membership
{
get { return _avatar; }
set { SetPropertyValueAndDetectChanges(value, ref _avatar, Ps.Value.AvatarSelector); }
}
}
/// <summary>
/// A Json blob stored for recording tour data for a user
/// </summary>
[DataMember]
public string TourData
{
get { return _tourData; }
set { SetPropertyValueAndDetectChanges(value, ref _tourData, Ps.Value.TourDataSelector); }
}
/// <summary>
/// Gets or sets the session timeout.
/// </summary>
@@ -27,5 +27,10 @@ namespace Umbraco.Core.Models.Rdbms
[NullSetting(NullSetting = NullSettings.NotNull)]
[Length(500)]
public string OriginIdentity { get; set; }
[Column("instructionCount")]
[NullSetting(NullSetting = NullSettings.NotNull)]
[Constraint(Default = 1)]
public int InstructionCount { get; set; }
}
}
@@ -24,4 +24,5 @@ namespace Umbraco.Core.Models.Rdbms
[ResultColumn]
public NodeDto NodeDto { get; set; }
}
}
+1 -1
View File
@@ -32,6 +32,6 @@ namespace Umbraco.Core.Models.Rdbms
[Column("logComment")]
[NullSetting(NullSetting = NullSettings.Null)]
[Length(4000)]
public string Comment { get; set; }
public string Comment { get; set; }
}
}
+29
View File
@@ -0,0 +1,29 @@
using System;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Models.Rdbms
{
[TableName("cmsMedia")]
[PrimaryKey("versionId", autoIncrement = false)]
[ExplicitColumns]
internal class MediaDto
{
[Column("nodeId")]
[ForeignKey(typeof(ContentDto), Column = "nodeId")]
[ForeignKey(typeof(NodeDto))]
[Index(IndexTypes.UniqueNonClustered, Name = "IX_cmsMedia", ForColumns = "nodeId, versionId, mediaPath")]
public int NodeId { get; set; }
[Column("versionId")]
[PrimaryKeyColumn(AutoIncrement = false)]
public Guid VersionId { get; set; }
[Column("mediaPath")]
[NullSetting(NullSetting = NullSettings.Null)]
public string MediaPath { get; set; }
[ResultColumn]
public ContentVersionDto ContentVersionDto { get; set; }
}
}
@@ -27,6 +27,7 @@ namespace Umbraco.Core.Models.Rdbms
[ForeignKey(typeof(PropertyTypeGroupDto))]
public int? PropertyTypeGroupId { get; set; }
[Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyTypeAlias")]
[Column("Alias")]
public string Alias { get; set; }
+11 -3
View File
@@ -15,7 +15,7 @@ namespace Umbraco.Core.Models.Rdbms
{
UserGroupDtos = new List<UserGroupDto>();
UserStartNodeDtos = new HashSet<UserStartNodeDto>();
}
}
[Column("id")]
[PrimaryKeyColumn(Name = "PK_user")]
@@ -40,7 +40,7 @@ namespace Umbraco.Core.Models.Rdbms
[Column("userPassword")]
[Length(500)]
public string Password { get; set; }
/// <summary>
/// This will represent a JSON structure of how the password has been created (i.e hash algorithm, iterations)
/// </summary>
@@ -95,7 +95,7 @@ namespace Umbraco.Core.Models.Rdbms
[NullSetting(NullSetting = NullSettings.NotNull)]
[Constraint(Default = SystemMethods.CurrentDateTime)]
public DateTime UpdateDate { get; set; }
/// <summary>
/// Will hold the media file system relative path of the users custom avatar if they uploaded one
/// </summary>
@@ -103,6 +103,14 @@ namespace Umbraco.Core.Models.Rdbms
[NullSetting(NullSetting = NullSettings.Null)]
[Length(500)]
public string Avatar { get; set; }
/// <summary>
/// A Json blob stored for recording tour data for a user
/// </summary>
[Column("tourData")]
[NullSetting(NullSetting = NullSettings.Null)]
[SpecialDbType(SpecialDbTypes.NTEXT)]
public string TourData { get; set; }
[ResultColumn]
public List<UserGroupDto> UserGroupDtos { get; set; }
@@ -0,0 +1,52 @@
using System;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Models.Rdbms
{
[TableName("umbracoUserLogin")]
[PrimaryKey("sessionId", autoIncrement = false)]
[ExplicitColumns]
internal class UserLoginDto
{
[Column("sessionId")]
[PrimaryKeyColumn(AutoIncrement = false)]
public Guid SessionId { get; set; }
[Column("userId")]
[ForeignKey(typeof(UserDto), Name = "FK_umbracoUserLogin_umbracoUser_id")]
public int UserId { get; set; }
/// <summary>
/// Tracks when the session is created
/// </summary>
[Column("loggedInUtc")]
[NullSetting(NullSetting = NullSettings.NotNull)]
public DateTime LoggedInUtc { get; set; }
/// <summary>
/// Updated every time a user's session is validated
/// </summary>
/// <remarks>
/// This allows us to guess if a session is timed out if a user doesn't actively log out
/// and also allows us to trim the data in the table
/// </remarks>
[Column("lastValidatedUtc")]
[NullSetting(NullSetting = NullSettings.NotNull)]
public DateTime LastValidatedUtc { get; set; }
/// <summary>
/// Tracks when the session is removed when the user's account is logged out
/// </summary>
[Column("loggedOutUtc")]
[NullSetting(NullSetting = NullSettings.Null)]
public DateTime? LoggedOutUtc { get; set; }
/// <summary>
/// Logs the IP address of the session if available
/// </summary>
[Column("ipAddress")]
[NullSetting(NullSetting = NullSettings.Null)]
public string IpAddress { get; set; }
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Models.Rdbms
{
[Obsolete("Table no longer exists as of 7.6 - retained only to support migrations from previous versions")]
[Obsolete("Table no longer exists as of 7.7 - retained only to support migrations from previous versions")]
[TableName("umbracoUserType")]
[PrimaryKey("id")]
[ExplicitColumns]
+1 -2
View File
@@ -30,12 +30,11 @@ namespace Umbraco.Core.Models
/// Tries to lookup the user's gravatar to see if the endpoint can be reached, if so it returns the valid URL
/// </summary>
/// <param name="user"></param>
/// <param name="userService"></param>
/// <param name="staticCache"></param>
/// <returns>
/// A list of 5 different sized avatar URLs
/// </returns>
internal static string[] GetCurrentUserAvatarUrls(this IUser user, IUserService userService, ICacheProvider staticCache)
internal static string[] GetUserAvatarUrls(this IUser user, ICacheProvider staticCache)
{
//check if the user has explicitly removed all avatars including a gravatar, this will be possible and the value will be "none"
if (user.Avatar == "none")
+28 -38
View File
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ICSharpCode.SharpZipLib.Zip;
using System.IO.Compression;
namespace Umbraco.Core.Packaging
{
@@ -14,17 +14,17 @@ namespace Umbraco.Core.Packaging
bool fileFound = false;
string foundDir = null;
ReadZipfileEntries(packageFilePath, (entry, stream) =>
ReadZipfileEntries(packageFilePath, entry =>
{
string fileName = Path.GetFileName(entry.Name);
if (string.IsNullOrEmpty(fileName) == false &&
fileName.Equals(fileToRead, StringComparison.CurrentCultureIgnoreCase))
if (string.IsNullOrEmpty(fileName) == false && fileName.Equals(fileToRead, StringComparison.CurrentCultureIgnoreCase))
{
foundDir = entry.Name.Substring(0, entry.Name.Length - fileName.Length);
fileFound = true;
using (var reader = new StreamReader(stream))
using (var entryStream = entry.Open())
using (var reader = new StreamReader(entryStream))
{
retVal = reader.ReadToEnd();
return false;
@@ -77,7 +77,7 @@ namespace Umbraco.Core.Packaging
var d = sourceDestination.ToDictionary(k => k.Key.ToLower(), v => v.Value);
ReadZipfileEntries(packageFilePath, (entry, stream) =>
ReadZipfileEntries(packageFilePath, entry =>
{
string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower();
if (fileName == string.Empty) { return true; }
@@ -86,8 +86,9 @@ namespace Umbraco.Core.Packaging
if (string.IsNullOrEmpty(fileName) == false && d.TryGetValue(fileName, out destination))
{
using (var streamWriter = File.Open(destination, FileMode.Create))
using (var entryStream = entry.Open())
{
stream.CopyTo(streamWriter);
entryStream.CopyTo(streamWriter);
}
d.Remove(fileName);
@@ -106,7 +107,7 @@ namespace Umbraco.Core.Packaging
{
var retVal = expectedFiles.ToList();
ReadZipfileEntries(packageFilePath, (zipEntry, stream) =>
ReadZipfileEntries(packageFilePath, zipEntry =>
{
string fileName = Path.GetFileName(zipEntry.Name);
@@ -125,7 +126,7 @@ namespace Umbraco.Core.Packaging
var dictionary = new Dictionary<string, List<string>>();
ReadZipfileEntries(packageFilePath, (entry, stream) =>
ReadZipfileEntries(packageFilePath, entry =>
{
string fileName = (Path.GetFileName(entry.Name) ?? string.Empty).ToLower();
@@ -148,53 +149,42 @@ namespace Umbraco.Core.Packaging
{
CheckPackageExists(packageFilePath);
var files = new HashSet<string>(filesToGet.Select(f => f.ToLower()));
var files = new HashSet<string>(filesToGet.Select(f => f.ToLowerInvariant()));
using (var fs = File.OpenRead(packageFilePath))
using (var fs = File.OpenRead(packageFilePath))
using (var zipArchive = new ZipArchive(fs))
{
using (var zipInputStream = new ZipInputStream(fs))
foreach (var zipEntry in zipArchive.Entries)
{
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.GetNextEntry()) != null)
{
if (zipEntry.IsDirectory) continue;
if (zipEntry.Name.IsNullOrWhiteSpace() && zipEntry.FullName.EndsWith("/")) continue;
if (files.Contains(zipEntry.Name))
if (files.Contains(zipEntry.Name.ToLowerInvariant()))
{
using (var memStream = new MemoryStream())
using (var entryStream = zipEntry.Open())
{
using (var memStream = new MemoryStream())
{
zipInputStream.CopyTo(memStream);
yield return memStream.ToArray();
memStream.Close();
}
entryStream.CopyTo(memStream);
memStream.Close();
yield return memStream.ToArray();
}
}
zipInputStream.Close();
}
fs.Close();
}
}
private void ReadZipfileEntries(string packageFilePath, Func<ZipEntry, ZipInputStream, bool> entryFunc, bool skipsDirectories = true)
private void ReadZipfileEntries(string packageFilePath, Func<ZipArchiveEntry, bool> entryFunc, bool skipsDirectories = true)
{
CheckPackageExists(packageFilePath);
using (var fs = File.OpenRead(packageFilePath))
using (var zipArchive = new ZipArchive(fs))
{
using (var zipInputStream = new ZipInputStream(fs))
foreach (var zipEntry in zipArchive.Entries)
{
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.GetNextEntry()) != null)
{
if (zipEntry.IsDirectory && skipsDirectories) continue;
if (entryFunc(zipEntry, zipInputStream) == false) break;
}
zipInputStream.Close();
if (zipEntry.Name.IsNullOrWhiteSpace() && zipEntry.FullName.EndsWith("/") && skipsDirectories)
continue;
if (entryFunc(zipEntry) == false) break;
}
fs.Close();
}
}
}
@@ -98,18 +98,18 @@ namespace Umbraco.Core.Persistence.Factories
{
//NOTE Currently doesn't add Alias (legacy that eventually will go away)
var documentDto = new DocumentDto
{
Newest = true,
NodeId = entity.Id,
Published = entity.Published,
Text = entity.Name,
UpdateDate = entity.UpdateDate,
WriterUserId = entity.WriterId,
VersionId = entity.Version,
ExpiresDate = null,
ReleaseDate = null,
ContentVersionDto = BuildContentVersionDto(entity)
};
{
Newest = true,
NodeId = entity.Id,
Published = entity.Published,
Text = entity.Name,
UpdateDate = entity.UpdateDate,
WriterUserId = entity.WriterId,
VersionId = entity.Version,
ExpiresDate = null,
ReleaseDate = null,
ContentVersionDto = BuildContentVersionDto(entity)
};
if (entity.Template != null && entity.Template.Id > 0)
documentDto.TemplateId = entity.Template.Id;
@@ -5,6 +5,7 @@ using Umbraco.Core.Models.Rdbms;
namespace Umbraco.Core.Persistence.Factories
{
internal class MacroFactory
{
public IMacro BuildEntity(MacroDto dto)
@@ -1,5 +1,6 @@
using System;
using System.Globalization;
using System.Text.RegularExpressions;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
@@ -9,20 +10,17 @@ namespace Umbraco.Core.Persistence.Factories
{
private readonly IMediaType _contentType;
private readonly Guid _nodeObjectTypeId;
private readonly int _id;
private int _primaryKey;
public MediaFactory(IMediaType contentType, Guid nodeObjectTypeId, int id)
public MediaFactory(IMediaType contentType, Guid nodeObjectTypeId)
{
_contentType = contentType;
_nodeObjectTypeId = nodeObjectTypeId;
_id = id;
}
public MediaFactory(Guid nodeObjectTypeId, int id)
public MediaFactory(Guid nodeObjectTypeId)
{
_nodeObjectTypeId = nodeObjectTypeId;
_id = id;
}
#region Implementation of IEntityFactory<IMedia,ContentVersionDto>
@@ -64,15 +62,27 @@ namespace Umbraco.Core.Persistence.Factories
return BuildEntity(dto, _contentType);
}
public ContentVersionDto BuildDto(IMedia entity)
public MediaDto BuildDto(IMedia entity)
{
var dto = new ContentVersionDto
{
NodeId = entity.Id,
VersionDate = entity.UpdateDate,
VersionId = entity.Version,
ContentDto = BuildContentDto(entity)
};
var versionDto = new ContentVersionDto
{
NodeId = entity.Id,
VersionDate = entity.UpdateDate,
VersionId = entity.Version,
ContentDto = BuildContentDto(entity)
};
//Extract the media path for storage
string mediaPath;
TryMatch(entity.GetValue<string>("umbracoFile"), out mediaPath);
var dto = new MediaDto()
{
NodeId = entity.Id,
ContentVersionDto = versionDto,
MediaPath = mediaPath,
VersionId = entity.Version
};
return dto;
}
@@ -119,5 +129,30 @@ namespace Umbraco.Core.Persistence.Factories
return nodeDto;
}
private static readonly Regex MediaPathPattern = new Regex(@"(/media/.+?)(?:['""]|$)", RegexOptions.Compiled);
/// <summary>
/// Try getting a media path out of the string being stored for media
/// </summary>
/// <param name="text"></param>
/// <param name="mediaPath"></param>
/// <returns></returns>
internal static bool TryMatch(string text, out string mediaPath)
{
mediaPath = null;
if (string.IsNullOrWhiteSpace(text))
return false;
var match = MediaPathPattern.Match(text);
if (match.Success == false || match.Groups.Count != 2)
return false;
var url = match.Groups[1].Value;
mediaPath = url;
return true;
}
}
}
@@ -36,6 +36,7 @@ namespace Umbraco.Core.Persistence.Factories
user.Avatar = dto.Avatar;
user.EmailConfirmedDate = dto.EmailConfirmedDate;
user.InvitedDate = dto.InvitedDate;
user.TourData = dto.TourData;
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
@@ -69,7 +70,8 @@ namespace Umbraco.Core.Persistence.Factories
UpdateDate = entity.UpdateDate,
Avatar = entity.Avatar,
EmailConfirmedDate = entity.EmailConfirmedDate,
InvitedDate = entity.InvitedDate
InvitedDate = entity.InvitedDate,
TourData = entity.TourData
};
foreach (var startNodeId in entity.StartContentIds)
@@ -0,0 +1,47 @@
using System.Collections.Concurrent;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Mappers
{
[MapperFor(typeof(AuditItem))]
[MapperFor(typeof(IAuditItem))]
public sealed class AuditMapper : BaseMapper
{
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance = new ConcurrentDictionary<string, DtoMapModel>();
public AuditMapper(ISqlSyntaxProvider sqlSyntax) : base(sqlSyntax)
{
}
//NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it
// otherwise that would fail because there is no public constructor.
public AuditMapper()
{
BuildMap();
}
#region Overrides of BaseMapper
internal override ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache
{
get { return PropertyInfoCacheInstance; }
}
internal override void BuildMap()
{
if (PropertyInfoCache.IsEmpty)
{
CacheMap<AuditItem, LogDto>(src => src.Id, dto => dto.NodeId);
CacheMap<AuditItem, LogDto>(src => src.CreateDate, dto => dto.Datestamp);
CacheMap<AuditItem, LogDto>(src => src.UserId, dto => dto.UserId);
CacheMap<AuditItem, LogDto>(src => src.AuditType, dto => dto.Header);
CacheMap<AuditItem, LogDto>(src => src.Comment, dto => dto.Comment);
}
}
#endregion
}
}
@@ -43,27 +43,28 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
{3, typeof (ContentDto)},
{4, typeof (ContentVersionDto)},
{5, typeof (DocumentDto)},
{6, typeof (MediaDto)},
{6, typeof (ContentTypeTemplateDto)},
{7, typeof (DataTypeDto)},
{8, typeof (DataTypePreValueDto)},
{9, typeof (DictionaryDto)},
{10, typeof (LanguageDto)},
{11, typeof (LanguageTextDto)},
{12, typeof (DomainDto)},
{13, typeof (LogDto)},
{14, typeof (MacroDto)},
{15, typeof (MacroPropertyDto)},
{16, typeof (MemberTypeDto)},
{17, typeof (MemberDto)},
{18, typeof (Member2MemberGroupDto)},
{19, typeof (ContentXmlDto)},
{20, typeof (PreviewXmlDto)},
{21, typeof (PropertyTypeGroupDto)},
{22, typeof (PropertyTypeDto)},
{23, typeof (PropertyDataDto)},
{24, typeof (RelationTypeDto)},
{25, typeof (RelationDto)},
{7, typeof (ContentTypeTemplateDto)},
{8, typeof (DataTypeDto)},
{9, typeof (DataTypePreValueDto)},
{10, typeof (DictionaryDto)},
{11, typeof (LanguageDto)},
{12, typeof (LanguageTextDto)},
{13, typeof (DomainDto)},
{14, typeof (LogDto)},
{15, typeof (MacroDto)},
{16, typeof (MacroPropertyDto)},
{17, typeof (MemberTypeDto)},
{18, typeof (MemberDto)},
{19, typeof (Member2MemberGroupDto)},
{20, typeof (ContentXmlDto)},
{21, typeof (PreviewXmlDto)},
{22, typeof (PropertyTypeGroupDto)},
{23, typeof (PropertyTypeDto)},
{24, typeof (PropertyDataDto)},
{25, typeof (RelationTypeDto)},
{26, typeof (RelationDto)},
{28, typeof (TagDto)},
{29, typeof (TagRelationshipDto)},
@@ -91,6 +92,7 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
{52, typeof (UserGroup2NodePermissionDto) },
{53, typeof (UserGroup2AppDto) },
{54, typeof (UserStartNodeDto) },
{55, typeof (UserLoginDto)}
};
#endregion
@@ -148,6 +148,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
return new Version(7, 6, 0);
}
//if the error is for cmsMedia it must be the previous version to 7.8 since that is when it is added
if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("cmsMedia"))))
{
return new Version(7, 7, 0);
}
return UmbracoVersion.Current;
}
@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
{
[Migration("7.8.0", 1, Constants.System.UmbracoMigrationName)]
public class AddCmsMediaTable : MigrationBase
{
public AddCmsMediaTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger)
{
}
public override void Up()
{
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
if (tables.InvariantContains("cmsMedia") == false)
{
Create.Table<MediaDto>();
MigrateMediaPaths();
}
}
private void MigrateMediaPaths()
{
Execute.Code(database =>
{
//Due to how much data there can be and that this query will result in executing an Index Scan on the cmsPropertyData PK
//which will literally iterate over every row in there (assume there's 1,000,000 and you can see why this will perform
//horribly) ultimately we need to fix this: http://issues.umbraco.org/issue/U4-10286
//so in the meantime I've found an ugly query to work around this Index Scan issue which is to use a sub-query of Ids.
//Doing this results in the same Index Scan taking place but it only visits the explicit rows that are provided, not every row
//in the cmsPropertyData table. This results in a tremendously faster query when there is tons of data.
//The will return any media property in nvarchar or ntext that is an umbracoFile that is not null.
//This will give us the least amount of data returned to work with.
var sql = @"SELECT cmsPropertyData.dataNvarchar, cmsPropertyData.dataNtext, umbracoNode.id, cmsContentVersion.VersionId
FROM cmsPropertyData
INNER JOIN cmsPropertyType ON cmsPropertyType.id = cmsPropertyData.propertytypeid
INNER JOIN umbracoNode ON umbracoNode.id = cmsPropertyData.contentNodeId
INNER JOIN cmsContentVersion ON cmsContentVersion.ContentId = umbracoNode.id
WHERE cmsPropertyType.Alias = (@alias)
AND umbracoNode.id IN (SELECT umbracoNode.id
FROM umbracoNode
INNER JOIN cmsContent ON cmsContent.nodeId = umbracoNode.id
INNER JOIN cmsContentType ON cmsContentType.nodeId = cmsContent.contentType
INNER JOIN cmsPropertyType ON cmsPropertyType.contentTypeId = cmsContentType.nodeId
WHERE cmsPropertyType.Alias = (@alias) AND umbracoNode.nodeObjectType = (@nodeObjectType))
AND (cmsPropertyData.dataNvarchar IS NOT NULL OR cmsPropertyData.dataNtext IS NOT NULL)";
var paths = new List<MediaDto>();
//using QUERY = a db cursor, we won't load this all into memory first, just row by row
foreach (var row in database.Query<dynamic>(sql, new {alias = "umbracoFile", nodeObjectType = Constants.ObjectTypes.Media}))
{
var id = (int) row.id;
var versionId = (Guid)row.VersionId;
string mediaPath = null;
//if there's values in dataNvarchar then ensure there's a media path match and extract it
if (row.dataNvarchar != null && MediaFactory.TryMatch((string) row.dataNvarchar, out mediaPath))
{
paths.Add(new MediaDto
{
MediaPath = mediaPath,
NodeId = id,
VersionId = versionId
});
}
//if there's values in dataNtext then ensure there's a media path match and extract it
else if (row.dataNtext != null && MediaFactory.TryMatch((string) row.dataNtext, out mediaPath))
{
paths.Add(new MediaDto
{
MediaPath = mediaPath,
NodeId = id,
VersionId = versionId
});
}
}
//now we need to insert paths for each item
database.BulkInsertRecords(paths, SqlSyntax);
return null;
});
}
public override void Down()
{
}
}
}
@@ -0,0 +1,48 @@
using System.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
{
[Migration("7.8.0", 0, Constants.System.UmbracoMigrationName)]
public class AddIndexToPropertyTypeAliasColumn : MigrationBase
{
public AddIndexToPropertyTypeAliasColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{ }
public override void Up()
{
Execute.Code(database =>
{
var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database);
//make sure it doesn't already exist
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsPropertyTypeAlias")) == false)
{
var localContext = new LocalMigrationContext(Context.CurrentDatabaseProvider, database, SqlSyntax, Logger);
//we can apply the index
localContext.Create.Index("IX_cmsPropertyTypeAlias").OnTable("cmsPropertyType")
.OnColumn("Alias")
.Ascending()
.WithOptions()
.NonClustered();
return localContext.GetSql();
}
return null;
});
}
public override void Down()
{
Delete.Index("IX_cmsPropertyTypeAlias").OnTable("cmsPropertyType");
}
}
}
@@ -0,0 +1,32 @@
using System.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
{
[Migration("7.8.0", 2, Constants.System.UmbracoMigrationName)]
public class AddInstructionCountColumn : MigrationBase
{
public AddInstructionCountColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{
}
public override void Up()
{
//Don't exeucte if the column is already there
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
if (columns.Any(x => x.TableName.InvariantEquals("umbracoCacheInstruction") && x.ColumnName.InvariantEquals("instructionCount")) == false)
Create.Column("instructionCount")
.OnTable("umbracoCacheInstruction")
.AsInt32()
.WithDefaultValue(1)
.NotNullable();
}
public override void Down()
{
}
}
}
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.DatabaseAnnotations;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
{
[Migration("7.8.0", 1, Constants.System.UmbracoMigrationName)]
public class AddTourDataUserColumn : MigrationBase
{
public AddTourDataUserColumn(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{
}
public override void Up()
{
//Don't exeucte if the column is already there
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
if (columns.Any(x => x.TableName.InvariantEquals("umbracoUser") && x.ColumnName.InvariantEquals("tourData")) == false)
{
var textType = SqlSyntax.GetSpecialDbType(SpecialDbTypes.NTEXT);
Create.Column("tourData").OnTable("umbracoUser").AsCustom(textType).Nullable();
}
}
public override void Down()
{
}
}
}
@@ -0,0 +1,29 @@
using System.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenEightZero
{
[Migration("7.8.0", 4, Constants.System.UmbracoMigrationName)]
public class AddUserLoginTable : MigrationBase
{
public AddUserLoginTable(ISqlSyntaxProvider sqlSyntax, ILogger logger) : base(sqlSyntax, logger)
{
}
public override void Up()
{
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
if (tables.InvariantContains("umbracoUserLogin") == false)
{
Create.Table<UserLoginDto>();
}
}
public override void Down()
{
}
}
}
@@ -1,6 +1,5 @@
using System;
using System.Data;
using System.Data.SqlClient;
using Umbraco.Core.Persistence.FaultHandling;
namespace Umbraco.Core.Persistence
@@ -1,16 +1,12 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data.SqlServerCe;
using System.Linq;
using System.Text.RegularExpressions;
using MySql.Data.MySqlClient;
using StackExchange.Profiling.Data;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence
@@ -38,8 +38,12 @@ namespace Umbraco.Core.Persistence
_tableDefinition = DefinitionFactory.GetTableDefinition(sqlSyntaxProvider, pd.type);
if (_tableDefinition == null) throw new InvalidOperationException("No table definition found for type " + pd.type);
_readerColumns = pd.Columns.Select(x => x.Value).ToArray();
//Only return real columns, do not include columns that are result columns
_readerColumns = pd.Columns
.Where(x => x.Value.ResultColumn == false)
.Select(x => x.Value)
.ToArray();
_sqlSyntaxProvider = sqlSyntaxProvider;
_enumerator = dataSource.GetEnumerator();
_columnDefinitions = _tableDefinition.Columns.ToArray();
@@ -1,29 +1,113 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Persistence.Repositories
{
internal class AuditRepository : PetaPocoRepositoryBase<int, AuditItem>, IAuditRepository
internal class AuditRepository : PetaPocoRepositoryBase<int, IAuditItem>, IAuditRepository
{
public AuditRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
}
protected override void PersistNewItem(AuditItem entity)
/// <summary>
/// Return the audit items as paged result
/// </summary>
/// <param name="query">
/// The query coming from the service
/// </param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="orderDirection"></param>
/// <param name="auditTypeFilter">
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
/// so we need to do that here
/// </param>
/// <param name="customFilter">
/// A user supplied custom filter
/// </param>
/// <returns></returns>
public IEnumerable<IAuditItem> GetPagedResultsByQuery(IQuery<IAuditItem> query, long pageIndex, int pageSize,
out long totalRecords, Direction orderDirection,
AuditType[] auditTypeFilter,
IQuery<IAuditItem> customFilter)
{
throw new NotImplementedException();
if (auditTypeFilter == null) auditTypeFilter = new AuditType[0];
var sql = GetBaseQuery(false);
if (query == null) query = new Query<IAuditItem>();
var translatorIds = new SqlTranslator<IAuditItem>(sql, query);
var translatedQuery = translatorIds.Translate();
var customFilterWheres = customFilter != null ? customFilter.GetWhereClauses().ToArray() : null;
var hasCustomFilter = customFilterWheres != null && customFilterWheres.Length > 0;
if (hasCustomFilter)
{
var filterSql = new Sql();
var first = true;
foreach (var filterClaus in customFilterWheres)
{
if (first == false)
{
filterSql.Append(" AND ");
}
filterSql.Append(string.Format("({0})", filterClaus.Item1), filterClaus.Item2);
first = false;
}
translatedQuery = GetFilteredSqlForPagedResults(translatedQuery, filterSql);
}
if (auditTypeFilter.Length > 0)
{
var filterSql = new Sql();
var first = true;
foreach (var filterClaus in auditTypeFilter)
{
if (first == false || hasCustomFilter)
{
filterSql.Append(" AND ");
}
filterSql.Append("(logHeader = @logHeader)", new {logHeader = filterClaus.ToString() });
first = false;
}
translatedQuery = GetFilteredSqlForPagedResults(translatedQuery, filterSql);
}
if (orderDirection == Direction.Descending)
translatedQuery.OrderByDescending("Datestamp");
else
translatedQuery.OrderBy("Datestamp");
// Get page of results and total count
var pagedResult = Database.Page<LogDto>(pageIndex + 1, pageSize, translatedQuery);
totalRecords = pagedResult.TotalItems;
var pages = pagedResult.Items.Select(
dto => new AuditItem(dto.Id, dto.Comment, Enum<AuditType>.Parse(dto.Header), dto.UserId)).ToArray();
//Mapping the DateStamp
for (int i = 0; i < pages.Length; i++)
{
pages[i].CreateDate = pagedResult.Items[i].Datestamp;
}
return pages;
}
#region Not Implemented - not needed
protected override void PersistUpdatedItem(AuditItem entity)
protected override void PersistUpdatedItem(IAuditItem entity)
{
Database.Insert(new LogDto
{
@@ -35,26 +119,40 @@ namespace Umbraco.Core.Persistence.Repositories
});
}
protected override AuditItem PerformGet(int id)
{
throw new NotImplementedException();
}
protected override IEnumerable<AuditItem> PerformGetAll(params int[] ids)
{
throw new NotImplementedException();
}
protected override IEnumerable<AuditItem> PerformGetByQuery(IQuery<AuditItem> query)
{
throw new NotImplementedException();
}
protected override Sql GetBaseQuery(bool isCount)
{
var sql = new Sql()
.Select(isCount ? "COUNT(*)" : "umbracoLog.id, umbracoLog.userId, umbracoLog.NodeId, umbracoLog.Datestamp, umbracoLog.logHeader, umbracoLog.logComment, umbracoUser.userName, umbracoUser.avatar as userAvatar")
.From<LogDto>(SqlSyntax);
if (isCount == false)
{
sql = sql.LeftJoin<UserDto>(SqlSyntax).On<UserDto, LogDto>(SqlSyntax, dto => dto.Id, dto => dto.UserId);
}
return sql;
}
#region Not Implemented - not needed currently
protected override void PersistNewItem(IAuditItem entity)
{
throw new NotImplementedException();
}
protected override IAuditItem PerformGet(int id)
{
throw new NotImplementedException();
}
protected override IEnumerable<IAuditItem> PerformGetAll(params int[] ids)
{
throw new NotImplementedException();
}
protected override IEnumerable<IAuditItem> PerformGetByQuery(IQuery<IAuditItem> query)
{
throw new NotImplementedException();
}
protected override string GetBaseWhereClause()
{
throw new NotImplementedException();
@@ -71,6 +169,33 @@ namespace Umbraco.Core.Persistence.Repositories
}
#endregion
private Sql GetFilteredSqlForPagedResults(Sql sql, Sql filterSql)
{
Sql filteredSql;
// Apply filter
if (filterSql != null)
{
var sqlFilter = " WHERE " + filterSql.SQL.TrimStart("AND ");
//NOTE: this is certainly strange - NPoco handles this much better but we need to re-create the sql
// instance a couple of times to get the parameter order correct, for some reason the first
// time the arguments don't show up correctly but the SQL argument parameter names are actually updated
// accordingly - so we re-create it again. In v8 we don't need to do this and it's already taken care of.
filteredSql = new Sql(sql.SQL, sql.Arguments);
var args = filteredSql.Arguments.Concat(filterSql.Arguments).ToArray();
filteredSql = new Sql(
string.Format("{0} {1}", filteredSql.SQL, sqlFilter),
args);
filteredSql = new Sql(filteredSql.SQL, args);
}
else
{
//copy to var so that the original isn't changed
filteredSql = new Sql(sql.SQL, sql.Arguments);
}
return filteredSql;
}
}
}
@@ -901,7 +901,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
public void AddOrUpdatePermissions(ContentPermissionSet permission)
{
_permissionRepository.AddOrUpdate(permission);
}
}
/// <summary>
/// Used to remove the content xml for a content item
@@ -1075,8 +1075,8 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
}
#endregion
}
#endregion
#endregion
protected override string GetDatabaseFieldNameForOrderBy(string orderBy)
{
//Some custom ones
@@ -1,11 +1,37 @@
using System.Collections;
using System.Collections.Generic;
using Umbraco.Core.Auditing;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.Repositories
{
public interface IAuditRepository : IRepository<int, AuditItem>
public interface IAuditRepository : IRepository<int, IAuditItem>
{
/// <summary>
/// Return the audit items as paged result
/// </summary>
/// <param name="query">
/// The query coming from the service
/// </param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="orderDirection"></param>
/// <param name="auditTypeFilter">
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
/// so we need to do that here
/// </param>
/// <param name="customFilter">
/// A user supplied custom filter
/// </param>
/// <returns></returns>
IEnumerable<IAuditItem> GetPagedResultsByQuery(
IQuery<IAuditItem> query,
long pageIndex, int pageSize, out long totalRecords,
Direction orderDirection,
AuditType[] auditTypeFilter,
IQuery<IAuditItem> customFilter);
}
}
@@ -9,6 +9,8 @@ namespace Umbraco.Core.Persistence.Repositories
{
public interface IMediaRepository : IRepositoryVersionable<int, IMedia>, IRecycleBinRepository<IMedia>, IReadRepository<Guid, IMedia>, IDeleteMediaFilesRepository
{
IMedia GetMediaByPath(string mediaPath);
/// <summary>
/// Used to add/update published xml for the media item
/// </summary>
@@ -91,7 +91,12 @@ namespace Umbraco.Core.Persistence.Repositories
IProfile GetProfile(string username);
IProfile GetProfile(int id);
IDictionary<UserState, int> GetUserStates();
IDictionary<UserState, int> GetUserStates();
Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true);
bool ValidateLoginSession(int userId, Guid sessionId);
int ClearLoginSessions(int userId);
int ClearLoginSessions(TimeSpan timespan);
void ClearLoginSession(Guid sessionId);
}
}
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
@@ -87,7 +87,7 @@ namespace Umbraco.Core.Persistence.Repositories
#region Overrides of PetaPocoRepositoryBase<int,IMedia>
protected override Sql GetBaseQuery(BaseQueryType queryType)
private Sql GetBaseQuery(BaseQueryType queryType, bool includeFilePaths)
{
var sql = new Sql();
sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsContentVersion.contentId" : "*"))
@@ -95,14 +95,26 @@ namespace Umbraco.Core.Persistence.Repositories
.InnerJoin<ContentDto>(SqlSyntax)
.On<ContentVersionDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
.InnerJoin<NodeDto>(SqlSyntax)
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId, SqlSyntax)
//TODO: IF we want to enable querying on content type information this will need to be joined
//.InnerJoin<ContentTypeDto>(SqlSyntax)
//.On<ContentDto, ContentTypeDto>(SqlSyntax, left => left.ContentTypeId, right => right.NodeId, SqlSyntax);
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId);
if (includeFilePaths)
{
sql.InnerJoin<MediaDto>(SqlSyntax)
.On<MediaDto, ContentVersionDto>(SqlSyntax, left => left.VersionId, right => right.VersionId);
}
//TODO: IF we want to enable querying on content type information this will need to be joined
//.InnerJoin<ContentTypeDto>(SqlSyntax)
//.On<ContentDto, ContentTypeDto>(SqlSyntax, left => left.ContentTypeId, right => right.NodeId, SqlSyntax);
sql.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
return sql;
}
protected override Sql GetBaseQuery(BaseQueryType queryType)
{
return GetBaseQuery(queryType, false);
}
protected override Sql GetBaseQuery(bool isCount)
{
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle);
@@ -128,6 +140,7 @@ namespace Umbraco.Core.Persistence.Repositories
"DELETE FROM cmsDocument WHERE nodeId = @Id",
"DELETE FROM cmsPropertyData WHERE contentNodeId = @Id",
"DELETE FROM cmsPreviewXml WHERE nodeId = @Id",
"DELETE FROM cmsMedia WHERE nodeId = @Id",
"DELETE FROM cmsContentVersion WHERE ContentId = @Id",
"DELETE FROM cmsContentXml WHERE nodeId = @Id",
"DELETE FROM cmsContent WHERE nodeId = @Id",
@@ -367,7 +380,7 @@ namespace Umbraco.Core.Persistence.Repositories
//Ensure that strings don't contain characters that are invalid in XML
entity.SanitizeEntityPropertiesForXmlStorage();
var factory = new MediaFactory(NodeObjectTypeId, entity.Id);
var factory = new MediaFactory(NodeObjectTypeId);
var dto = factory.BuildDto(entity);
//NOTE Should the logic below have some kind of fallback for empty parent ids ?
@@ -380,7 +393,7 @@ namespace Umbraco.Core.Persistence.Repositories
var sortOrder = maxSortOrder + 1;
//Create the (base) node data - umbracoNode
var nodeDto = dto.ContentDto.NodeDto;
var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto;
nodeDto.Path = parent.Path;
nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture));
nodeDto.SortOrder = sortOrder;
@@ -415,15 +428,21 @@ namespace Umbraco.Core.Persistence.Repositories
entity.Level = level;
//Create the Content specific data - cmsContent
var contentDto = dto.ContentDto;
var contentDto = dto.ContentVersionDto.ContentDto;
contentDto.NodeId = nodeDto.NodeId;
Database.Insert(contentDto);
//Create the first version - cmsContentVersion
//Assumes a new Version guid and Version date (modified date) has been set
var contentVersionDto = dto.ContentVersionDto;
contentVersionDto.NodeId = nodeDto.NodeId;
Database.Insert(contentVersionDto);
//Create the Media specific data for this version - cmsMedia
//Assumes a new Version guid has been generated
dto.NodeId = nodeDto.NodeId;
Database.Insert(dto);
//Create the PropertyData for this version - cmsPropertyData
var propertyFactory = new PropertyFactory(entity.ContentType.CompositionPropertyTypes.ToArray(), entity.Version, entity.Id);
var propertyDataDtos = propertyFactory.BuildDto(entity.Properties);
@@ -471,14 +490,14 @@ namespace Umbraco.Core.Persistence.Repositories
entity.SortOrder = maxSortOrder + 1;
}
var factory = new MediaFactory(NodeObjectTypeId, entity.Id);
var factory = new MediaFactory(NodeObjectTypeId);
//Look up Content entry to get Primary for updating the DTO
var contentDto = Database.SingleOrDefault<ContentDto>("WHERE nodeId = @Id", new { Id = entity.Id });
var contentDto = Database.First<ContentDto>("WHERE nodeId = @Id", new { Id = entity.Id });
factory.SetPrimaryKey(contentDto.PrimaryKey);
var dto = factory.BuildDto(entity);
//Updates the (base) node data - umbracoNode
var nodeDto = dto.ContentDto.NodeDto;
var nodeDto = dto.ContentVersionDto.ContentDto.NodeDto;
nodeDto.ValidatePathWithException();
var o = Database.Update(nodeDto);
@@ -486,15 +505,18 @@ namespace Umbraco.Core.Persistence.Repositories
if (contentDto.ContentTypeId != entity.ContentTypeId)
{
//Create the Content specific data - cmsContent
var newContentDto = dto.ContentDto;
var newContentDto = dto.ContentVersionDto.ContentDto;
Database.Update(newContentDto);
}
//In order to update the ContentVersion we need to retrieve its primary key id
var contentVerDto = Database.SingleOrDefault<ContentVersionDto>("WHERE VersionId = @Version", new { Version = entity.Version });
dto.Id = contentVerDto.Id;
var contentVerDto = Database.First<ContentVersionDto>("WHERE VersionId = @Version", new { Version = entity.Version });
dto.ContentVersionDto.Id = contentVerDto.Id;
//Updates the current version - cmsContentVersion
//Assumes a Version guid exists and Version date (modified date) has been set/updated
Database.Update(dto.ContentVersionDto);
//now update the media entry
Database.Update(dto);
//Create the PropertyData for this version - cmsPropertyData
@@ -639,7 +661,39 @@ namespace Umbraco.Core.Persistence.Repositories
}
#endregion
}
#endregion
#endregion
/// <summary>
/// Gets an <see cref="IMedia"/> object from the path stored in the for the media item.
/// </summary>
/// <param name="mediaPath">Path of the media item to retrieve (for example: /media/1024/koala_403x328.jpg)</param>
/// <returns><see cref="IMedia"/></returns>
public IMedia GetMediaByPath(string mediaPath)
{
var umbracoFileValue = mediaPath;
const string pattern = ".*[_][0-9]+[x][0-9]+[.].*";
var isResized = Regex.IsMatch(mediaPath, pattern);
// If the image has been resized we strip the "_403x328" of the original "/media/1024/koala_403x328.jpg" url.
if (isResized)
{
var underscoreIndex = mediaPath.LastIndexOf('_');
var dotIndex = mediaPath.LastIndexOf('.');
umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex));
}
var sql = GetBaseQuery(BaseQueryType.FullSingle, true);
sql.Where<MediaDto>(mediaDto => mediaDto.MediaPath == umbracoFileValue, SqlSyntax);
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
var dto = Database.Fetch<ContentVersionDto, ContentDto, NodeDto>(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault();
if (dto == null)
return null;
var content = CreateMediaFromDto(dto, sql);
return content;
}
/// <summary>
/// Gets paged media results
@@ -703,8 +757,8 @@ namespace Umbraco.Core.Persistence.Repositories
if (EnsureUniqueNaming == false)
return nodeName;
var names = Database.Fetch<SimilarNodeName>("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId",
new { objectType = NodeObjectTypeId, parentId });
var names = Database.Fetch<SimilarNodeName>("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId",
new { objectType = NodeObjectTypeId, parentId });
return SimilarNodeName.GetUniqueName(names, id, nodeName);
}
@@ -1,14 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
@@ -53,15 +48,16 @@ namespace Umbraco.Core.Persistence.Repositories
@"DELETE FROM umbracoRedirectUrl WHERE umbracoRedirectUrl.id IN(
SELECT TB1.id FROM umbracoRedirectUrl as TB1
INNER JOIN umbracoNode as TB2 ON TB1.contentKey = TB2.uniqueId
WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)",
WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)",
FormatDeleteStatement("umbracoUserStartNode", "startNode"),
FormatUpdateStatement("umbracoUserGroup", "startContentId"),
FormatUpdateStatement("umbracoUserGroup", "startMediaId"),
FormatUpdateStatement("umbracoUserGroup", "startMediaId"),
FormatDeleteStatement("umbracoRelation", "parentId"),
FormatDeleteStatement("umbracoRelation", "childId"),
FormatDeleteStatement("cmsTagRelationship", "nodeId"),
FormatDeleteStatement("umbracoDomains", "domainRootStructureID"),
FormatDeleteStatement("cmsDocument", "nodeId"),
FormatDeleteStatement("cmsMedia", "nodeId"),
FormatDeleteStatement("cmsPropertyData", "contentNodeId"),
FormatDeleteStatement("cmsPreviewXml", "nodeId"),
FormatDeleteStatement("cmsContentVersion", "ContentId"),
@@ -4,7 +4,6 @@ using System.Linq;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Scoping;
@@ -6,6 +6,7 @@ using System.Linq.Expressions;
using System.Text;
using System.Web.Security;
using Newtonsoft.Json;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Membership;
@@ -187,8 +188,8 @@ UNION
SELECT '5CountOfInvited' AS colName, COUNT(id) AS num FROM umbracoUser WHERE lastLoginDate IS NULL AND userDisabled = 1 AND invitedDate IS NOT NULL
ORDER BY colName";
var result = Database.Fetch<dynamic>(sql);
var result = Database.Fetch<dynamic>(sql);
return new Dictionary<UserState, int>
{
{UserState.All, result[0].num},
@@ -199,6 +200,83 @@ ORDER BY colName";
};
}
public Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true)
{
//TODO: I know this doesn't follow the normal repository conventions which would require us to crete a UserSessionRepository
//and also business logic models for these objects but that's just so overkill for what we are doing
//and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore
var now = DateTime.UtcNow;
var dto = new UserLoginDto
{
UserId = userId,
IpAddress = requestingIpAddress,
LoggedInUtc = now,
LastValidatedUtc = now,
LoggedOutUtc = null,
SessionId = Guid.NewGuid()
};
Database.Insert(dto);
if (cleanStaleSessions)
{
ClearLoginSessions(TimeSpan.FromDays(15));
}
return dto.SessionId;
}
public bool ValidateLoginSession(int userId, Guid sessionId)
{
var found = Database.FirstOrDefault<UserLoginDto>("WHERE sessionId=@sessionId", new {sessionId = sessionId});
if (found == null || found.UserId != userId || found.LoggedOutUtc.HasValue)
return false;
//now detect if there's been a timeout
if (DateTime.UtcNow - found.LastValidatedUtc > TimeSpan.FromMinutes(GlobalSettings.TimeOutInMinutes))
{
//timeout detected, update the record
ClearLoginSession(sessionId);
return false;
}
//update the validate date
found.LastValidatedUtc = DateTime.UtcNow;
Database.Update(found);
return true;
}
public int ClearLoginSessions(int userId)
{
//TODO: I know this doesn't follow the normal repository conventions which would require us to crete a UserSessionRepository
//and also business logic models for these objects but that's just so overkill for what we are doing
//and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore
var count = Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoUserLogin WHERE userId=@userId", new { userId = userId });
Database.Execute("DELETE FROM umbracoUserLogin WHERE userId=@userId", new {userId = userId});
return count;
}
public int ClearLoginSessions(TimeSpan timespan)
{
//TODO: I know this doesn't follow the normal repository conventions which would require us to crete a UserSessionRepository
//and also business logic models for these objects but that's just so overkill for what we are doing
//and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore
var fromDate = DateTime.UtcNow - timespan;
var count = Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoUserLogin WHERE lastValidatedUtc=@fromDate", new { fromDate = fromDate });
Database.Execute("DELETE FROM umbracoUserLogin WHERE lastValidatedUtc=@fromDate", new { fromDate = fromDate });
return count;
}
public void ClearLoginSession(Guid sessionId)
{
//TODO: I know this doesn't follow the normal repository conventions which would require us to crete a UserSessionRepository
//and also business logic models for these objects but that's just so overkill for what we are doing
//and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore
Database.Execute("UPDATE umbracoUserLogin SET loggedOutUtc=@now WHERE sessionId=@sessionId",
new { now = DateTime.UtcNow, sessionId = sessionId });
}
protected override IEnumerable<IUser> PerformGetAll(params int[] ids)
{
var sql = GetQueryWithGroups();
@@ -401,7 +479,8 @@ ORDER BY colName";
{"updateDate", "UpdateDate"},
{"avatar", "Avatar"},
{"emailConfirmedDate", "EmailConfirmedDate"},
{"invitedDate", "InvitedDate"}
{"invitedDate", "InvitedDate"},
{"tourData", "TourData"}
};
//create list of properties that have changed
@@ -834,14 +913,6 @@ ORDER BY colName";
{
return dtos.Select(UserFactory.BuildEntity);
}
//private IEnumerable<IUserGroup> ConvertFromDtos(IEnumerable<UserGroupDto> dtos)
//{
// return dtos.Select(dto =>
// {
// var userGroupFactory = new UserGroupFactory();
// return userGroupFactory.BuildEntity(dto);
// });
//}
}
}
@@ -11,12 +11,14 @@ using System.Security.Principal;
using System.Threading;
using System.Web;
using System.Web.Security;
using Microsoft.AspNet.Identity;
using AutoMapper;
using Microsoft.Owin;
using Newtonsoft.Json;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Logging;
using IUser = Umbraco.Core.Models.Membership.IUser;
namespace Umbraco.Core.Security
{
@@ -241,8 +243,11 @@ namespace Umbraco.Core.Security
/// <param name="userdata"></param>
public static FormsAuthenticationTicket CreateUmbracoAuthTicket(this HttpContextBase http, UserData userdata)
{
//ONLY used by BasePage.doLogin!
if (http == null) throw new ArgumentNullException("http");
if (userdata == null) throw new ArgumentNullException("userdata");
if (userdata == null) throw new ArgumentNullException("userdata");
var userDataString = JsonConvert.SerializeObject(userdata);
return CreateAuthTicketAndCookie(
http,
@@ -254,14 +259,7 @@ namespace Umbraco.Core.Security
1440,
UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName,
UmbracoConfig.For.UmbracoSettings().Security.AuthCookieDomain);
}
internal static FormsAuthenticationTicket CreateUmbracoAuthTicket(this HttpContext http, UserData userdata)
{
if (http == null) throw new ArgumentNullException("http");
if (userdata == null) throw new ArgumentNullException("userdata");
return new HttpContextWrapper(http).CreateUmbracoAuthTicket(userdata);
}
}
/// <summary>
/// returns the number of seconds the user has until their auth session times out
@@ -331,7 +329,23 @@ namespace Umbraco.Core.Security
/// <param name="http"></param>
/// <param name="cookieName"></param>
private static void Logout(this HttpContextBase http, string cookieName)
{
{
//We need to clear the sessionId from the database. This is legacy code to do any logging out and shouldn't really be used at all but in any case
//we need to make sure the session is cleared. Due to the legacy nature of this it means we need to use singletons
if (http.User != null)
{
var claimsIdentity = http.User.Identity as ClaimsIdentity;
if (claimsIdentity != null)
{
var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
Guid guidSession;
if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out guidSession))
{
ApplicationContext.Current.Services.UserService.ClearLoginSession(guidSession);
}
}
}
if (http == null) throw new ArgumentNullException("http");
//clear the preview cookie and external login
var cookies = new[] { cookieName, Constants.Web.PreviewCookieName, Constants.Security.BackOfficeExternalCookieName };
@@ -2,16 +2,29 @@
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.Identity;
using Umbraco.Core.Models.Identity;
using Umbraco.Core.Services;
namespace Umbraco.Core.Security
{
public class BackOfficeClaimsIdentityFactory<T> : ClaimsIdentityFactory<T, int>
where T: BackOfficeIdentityUser
{
private readonly ApplicationContext _appCtx;
[Obsolete("Use the overload specifying all dependencies instead")]
public BackOfficeClaimsIdentityFactory()
:this(ApplicationContext.Current)
{
}
public BackOfficeClaimsIdentityFactory(ApplicationContext appCtx)
{
if (appCtx == null) throw new ArgumentNullException("appCtx");
_appCtx = appCtx;
SecurityStampClaimType = Constants.Security.SessionIdClaimType;
UserNameClaimType = ClaimTypes.Name;
}
@@ -24,9 +37,9 @@ namespace Umbraco.Core.Security
public override async Task<ClaimsIdentity> CreateAsync(UserManager<T, int> manager, T user, string authenticationType)
{
var baseIdentity = await base.CreateAsync(manager, user, authenticationType);
var umbracoIdentity = new UmbracoBackOfficeIdentity(baseIdentity,
//set a new session id
//NOTE - there is no session id assigned here, this is just creating the identity, a session id will be generated when the cookie is written
new UserData
{
Id = user.Id,
@@ -37,15 +50,22 @@ namespace Umbraco.Core.Security
Roles = user.Roles.Select(x => x.RoleId).ToArray(),
StartContentNodes = user.CalculatedContentStartNodeIds,
StartMediaNodes = user.CalculatedMediaStartNodeIds,
SessionId = user.SecurityStamp
SecurityStamp = user.SecurityStamp
});
return umbracoIdentity;
}
}
}
public class BackOfficeClaimsIdentityFactory : BackOfficeClaimsIdentityFactory<BackOfficeIdentityUser>
{
{
[Obsolete("Use the overload specifying all dependencies instead")]
public BackOfficeClaimsIdentityFactory()
{
}
public BackOfficeClaimsIdentityFactory(ApplicationContext appCtx) : base(appCtx)
{
}
}
}
@@ -1,21 +1,83 @@
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Globalization;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Semver;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models.Identity;
namespace Umbraco.Core.Security
{
public class BackOfficeCookieAuthenticationProvider : CookieAuthenticationProvider
{
private readonly ApplicationContext _appCtx;
[Obsolete("Use the ctor specifying all dependencies")]
[EditorBrowsable(EditorBrowsableState.Never)]
public BackOfficeCookieAuthenticationProvider()
: this(ApplicationContext.Current)
{
}
public BackOfficeCookieAuthenticationProvider(ApplicationContext appCtx)
{
if (appCtx == null) throw new ArgumentNullException("appCtx");
_appCtx = appCtx;
}
private static readonly SemVersion MinUmbracoVersionSupportingLoginSessions = new SemVersion(7, 8);
public override void ResponseSignIn(CookieResponseSignInContext context)
{
var backOfficeIdentity = context.Identity as UmbracoBackOfficeIdentity;
if (backOfficeIdentity != null)
{
//generate a session id and assign it
//create a session token - if we are configured and not in an upgrade state then use the db, otherwise just generate one
//NOTE - special check because when we are upgrading to 7.8 we cannot create a session since the db isn't ready and we'll get exceptions
var canAcquireSession = _appCtx.IsUpgrading == false || _appCtx.CurrentVersion() >= MinUmbracoVersionSupportingLoginSessions;
var session = canAcquireSession
? _appCtx.Services.UserService.CreateLoginSession((int)backOfficeIdentity.Id, context.OwinContext.GetCurrentRequestIpAddress())
: Guid.NewGuid();
backOfficeIdentity.UserData.SessionId = session.ToString();
}
base.ResponseSignIn(context);
}
public override void ResponseSignOut(CookieResponseSignOutContext context)
{
//Clear the user's session on sign out
if (context != null && context.OwinContext != null && context.OwinContext.Authentication != null
&& context.OwinContext.Authentication.User != null && context.OwinContext.Authentication.User.Identity != null)
{
var claimsIdentity = context.OwinContext.Authentication.User.Identity as ClaimsIdentity;
var sessionId = claimsIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
Guid guidSession;
if (sessionId.IsNullOrWhiteSpace() == false && Guid.TryParse(sessionId, out guidSession))
{
_appCtx.Services.UserService.ClearLoginSession(guidSession);
}
}
base.ResponseSignOut(context);
//Make sure the definitely all of these cookies are cleared when signing out with cookies
context.Response.Cookies.Append(SessionIdValidator.CookieName, "", new CookieOptions
{
Expires = DateTime.Now.AddYears(-1),
Path = "/"
});
context.Response.Cookies.Append(UmbracoConfig.For.UmbracoSettings().Security.AuthCookieName, "", new CookieOptions
{
Expires = DateTime.Now.AddYears(-1),
@@ -34,21 +96,45 @@ namespace Umbraco.Core.Security
}
/// <summary>
/// Ensures that the culture is set correctly for the current back office user
/// Ensures that the culture is set correctly for the current back office user and that the user's session token is valid
/// </summary>
/// <param name="context"/>
/// <returns/>
public override Task ValidateIdentity(CookieValidateIdentityContext context)
public override async Task ValidateIdentity(CookieValidateIdentityContext context)
{
EnsureCulture(context);
await EnsureValidSessionId(context);
await base.ValidateIdentity(context);
}
/// <summary>
/// Ensures that the user has a valid session id
/// </summary>
/// <remarks>
/// So that we are not overloading the database this throttles it's check to every minute
/// </remarks>
protected virtual async Task EnsureValidSessionId(CookieValidateIdentityContext context)
{
if (_appCtx.IsConfigured && _appCtx.IsUpgrading == false)
await SessionIdValidator.ValidateSessionAsync(TimeSpan.FromMinutes(1), context);
}
private void EnsureCulture(CookieValidateIdentityContext context)
{
var umbIdentity = context.Identity as UmbracoBackOfficeIdentity;
if (umbIdentity != null && umbIdentity.IsAuthenticated)
{
Thread.CurrentThread.CurrentCulture =
Thread.CurrentThread.CurrentUICulture =
new CultureInfo(umbIdentity.Culture);
UserCultures.GetOrAdd(umbIdentity.Culture, s => new CultureInfo(s));
}
return base.ValidateIdentity(context);
}
/// <summary>
/// Used so that we aren't creating a new CultureInfo object for every single request
/// </summary>
private static readonly ConcurrentDictionary<string, CultureInfo> UserCultures = new ConcurrentDictionary<string, CultureInfo>();
}
}
@@ -223,6 +223,9 @@ namespace Umbraco.Core.Security
user.AccessFailedCount = 0;
await UserManager.UpdateAsync(user);
//set the current request's principal to the identity just signed in!
_request.User = new ClaimsPrincipal(userIdentity);
_logger.WriteCore(TraceEventType.Information, 0,
string.Format(
"Login attempt succeeded for username {0} from IP address {1}",
@@ -152,7 +152,6 @@ namespace Umbraco.Core.Security
//NOTE: This method is mostly here for backwards compat
base.InitUserManager(manager, membershipProvider, options.DataProtectionProvider, contentSectionConfig);
}
}
/// <summary>
@@ -169,7 +168,7 @@ namespace Umbraco.Core.Security
#region What we support do not currently
//NOTE: Not sure if we really want/need to ever support this
//TODO: We could support this - but a user claims will mostly just be what is in the auth cookie
public override bool SupportsUserClaim
{
get { return false; }
@@ -271,6 +270,22 @@ namespace Umbraco.Core.Security
//manager.SmsService = new SmsService();
}
/// <summary>
/// Used to validate a user's session
/// </summary>
/// <param name="userId"></param>
/// <param name="sessionId"></param>
/// <returns></returns>
public virtual async Task<bool> ValidateSessionIdAsync(int userId, string sessionId)
{
var userSessionStore = Store as IUserSessionStore<BackOfficeIdentityUser, int>;
//if this is not set, for backwards compat (which would be super rare), we'll just approve it
if (userSessionStore == null)
return true;
return await userSessionStore.ValidateSessionIdAsync(userId, sessionId);
}
/// <summary>
/// This will determine which password hasher to use based on what is defined in config
/// </summary>
@@ -674,4 +689,5 @@ namespace Umbraco.Core.Security
return httpContext.GetCurrentRequestIpAddress();
}
}
}
@@ -26,12 +26,13 @@ namespace Umbraco.Core.Security
IUserRoleStore<BackOfficeIdentityUser, int>,
IUserSecurityStampStore<BackOfficeIdentityUser, int>,
IUserLockoutStore<BackOfficeIdentityUser, int>,
IUserTwoFactorStore<BackOfficeIdentityUser, int>
IUserTwoFactorStore<BackOfficeIdentityUser, int>,
IUserSessionStore<BackOfficeIdentityUser, int>
//TODO: This would require additional columns/tables for now people will need to implement this on their own
//IUserPhoneNumberStore<BackOfficeIdentityUser, int>,
//TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation
//IQueryableUserStore<BackOfficeIdentityUser, int>
//TODO: This would require additional columns/tables for now people will need to implement this on their own
//IUserPhoneNumberStore<BackOfficeIdentityUser, int>,
//TODO: To do this we need to implement IQueryable - we'll have an IQuerable implementation soon with the UmbracoLinqPadDriver implementation
//IQueryableUserStore<BackOfficeIdentityUser, int>
{
private readonly IUserService _userService;
private readonly IEntityService _entityService;
@@ -753,6 +754,15 @@ namespace Umbraco.Core.Security
if (_disposed)
throw new ObjectDisposedException(GetType().Name);
}
public Task<bool> ValidateSessionIdAsync(int userId, string sessionId)
{
Guid guidSessionId;
if (Guid.TryParse(sessionId, out guidSessionId))
{
return Task.FromResult(_userService.ValidateLoginSession(userId, guidSessionId));
}
return Task.FromResult(false);
}
}
}
@@ -0,0 +1,17 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
namespace Umbraco.Core.Security
{
/// <summary>
/// An IUserStore interface part to implement if the store supports validating user session Ids
/// </summary>
/// <typeparam name="TUser"></typeparam>
/// <typeparam name="TKey"></typeparam>
public interface IUserSessionStore<TUser, in TKey> : IUserStore<TUser, TKey>, IDisposable
where TUser : class, IUser<TKey>
{
Task<bool> ValidateSessionIdAsync(int userId, string sessionId);
}
}
@@ -1,4 +1,5 @@
using System;
using System.Web;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Umbraco.Core.Models.Identity;
@@ -7,6 +8,37 @@ namespace Umbraco.Core.Security
{
public static class OwinExtensions
{
public static string GetCurrentRequestIpAddress(this IOwinContext owinContext)
{
if (owinContext == null)
{
return "Unknown, owinContext is null";
}
if (owinContext.Request == null)
{
return "Unknown, owinContext.Request is null";
}
var httpContext = owinContext.TryGetHttpContext();
if (httpContext == false)
{
return "Unknown, cannot resolve HttpContext from owinContext";
}
return httpContext.Result.GetCurrentRequestIpAddress();
}
/// <summary>
/// Nasty little hack to get httpcontextbase from an owin context
/// </summary>
/// <param name="owinContext"></param>
/// <returns></returns>
internal static Attempt<HttpContextBase> TryGetHttpContext(this IOwinContext owinContext)
{
var ctx = owinContext.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
return ctx == null ? Attempt<HttpContextBase>.Fail() : Attempt.Succeed(ctx);
}
/// <summary>
/// Gets the back office sign in manager out of OWIN
/// </summary>
@@ -0,0 +1,118 @@
using System;
using System.Collections.Concurrent;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Infrastructure;
using Microsoft.Owin.Security.Cookies;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models.Identity;
namespace Umbraco.Core.Security
{
/// <summary>
/// Static helper class used to configure a CookieAuthenticationProvider to validate a cookie against a user's session id
/// </summary>
/// <remarks>
/// This uses another cookie to track the last checked time which is done for a few reasons:
/// * We can't use the user's auth ticket to do thsi because we'd be re-issuing the auth ticket all of the time and it would never expire
/// plus the auth ticket size is much larger than this small value
/// * This will execute quite often (every minute per user) and in some cases there might be several requests that end up re-issuing the cookie so the cookie value should be small
/// * We want to avoid the user lookup if it's not required so that will only happen when the time diff is great enough in the cookie
/// </remarks>
internal static class SessionIdValidator
{
public const string CookieName = "UMB_UCONTEXT_C";
public static async Task ValidateSessionAsync(TimeSpan validateInterval, CookieValidateIdentityContext context)
{
if (context.Request.Uri.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath) == false)
return;
var valid = await ValidateSessionAsync(validateInterval, context.OwinContext, context.Options.CookieManager, context.Options.SystemClock, context.Properties.IssuedUtc, context.Identity);
if (valid == false)
{
context.RejectIdentity();
context.OwinContext.Authentication.SignOut(context.Options.AuthenticationType);
}
}
public static async Task<bool> ValidateSessionAsync(
TimeSpan validateInterval,
IOwinContext owinCtx,
ICookieManager cookieManager,
ISystemClock systemClock,
DateTimeOffset? authTicketIssueDate,
ClaimsIdentity currentIdentity)
{
if (owinCtx == null) throw new ArgumentNullException("owinCtx");
if (cookieManager == null) throw new ArgumentNullException("cookieManager");
if (systemClock == null) throw new ArgumentNullException("systemClock");
DateTimeOffset? issuedUtc = null;
var currentUtc = systemClock.UtcNow;
//read the last checked time from a custom cookie
var lastCheckedCookie = cookieManager.GetRequestCookie(owinCtx, CookieName);
if (lastCheckedCookie.IsNullOrWhiteSpace() == false)
{
DateTimeOffset parsed;
if (DateTimeOffset.TryParse(lastCheckedCookie, out parsed))
{
issuedUtc = parsed;
}
}
//no cookie, use the issue time of the auth ticket
if (issuedUtc.HasValue == false)
{
issuedUtc = authTicketIssueDate;
}
// Only validate if enough time has elapsed
var validate = issuedUtc.HasValue == false;
if (issuedUtc.HasValue)
{
var timeElapsed = currentUtc.Subtract(issuedUtc.Value);
validate = timeElapsed > validateInterval;
}
if (validate == false)
return true;
var manager = owinCtx.GetUserManager<BackOfficeUserManager>();
if (manager == null)
return false;
var userId = currentIdentity.GetUserId<int>();
var user = await manager.FindByIdAsync(userId);
if (user == null)
return false;
var sessionId = currentIdentity.FindFirstValue(Constants.Security.SessionIdClaimType);
if (await manager.ValidateSessionIdAsync(userId, sessionId) == false)
return false;
//we will re-issue the cookie last checked cookie
cookieManager.AppendResponseCookie(
owinCtx,
CookieName,
DateTimeOffset.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffffffzzz"),
new CookieOptions
{
HttpOnly = true,
Secure = GlobalSettings.UseSSL || owinCtx.Request.IsSecure,
Path = "/"
});
return true;
}
}
}
@@ -36,6 +36,7 @@ namespace Umbraco.Core.Security
var username = identity.GetUserName();
var session = identity.FindFirstValue(Constants.Security.SessionIdClaimType);
var securityStamp = identity.FindFirstValue(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType);
var startContentId = identity.FindFirstValue(Constants.Security.StartContentNodeIdClaimType);
var startMediaId = identity.FindFirstValue(Constants.Security.StartMediaNodeIdClaimType);
@@ -66,8 +67,9 @@ namespace Umbraco.Core.Security
var roles = identity.FindAll(x => x.Type == DefaultRoleClaimType).Select(role => role.Value).ToList();
var allowedApps = identity.FindAll(x => x.Type == Constants.Security.AllowedApplicationsClaimType).Select(app => app.Value).ToList();
var userData = new UserData(session)
{
var userData = new UserData
{
SecurityStamp = securityStamp,
SessionId = session,
AllowedApplications = allowedApps.ToArray(),
Culture = culture,
@@ -189,7 +191,8 @@ namespace Umbraco.Core.Security
Constants.Security.StartContentNodeIdClaimType,
Constants.Security.StartMediaNodeIdClaimType,
ClaimTypes.Locality,
Constants.Security.SessionIdClaimType
Constants.Security.SessionIdClaimType,
Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType
};
}
}
@@ -226,7 +229,7 @@ namespace Umbraco.Core.Security
// by the SecurityStampValidator, see: https://katanaproject.codeplex.com/workitem/444
if (HasClaim(x => x.Type == Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType) == false)
{
AddClaim(new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, SessionId, ClaimValueTypes.String, Issuer, Issuer, this));
AddClaim(new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, SecurityStamp, ClaimValueTypes.String, Issuer, Issuer, this));
}
}
@@ -307,6 +310,11 @@ namespace Umbraco.Core.Security
get { return UserData.SessionId; }
}
public string SecurityStamp
{
get { return UserData.SecurityStamp; }
}
public string[] Roles
{
get { return UserData.Roles; }
+9 -3
View File
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Security
/// Use this constructor to create/assign new UserData to the ticket
/// </summary>
/// <param name="sessionId">
/// The security stamp for the user
/// The current sessionId for the user
/// </param>
public UserData(string sessionId)
{
@@ -30,11 +30,17 @@ namespace Umbraco.Core.Security
}
/// <summary>
/// This is the 'security stamp' for validation
/// This is the 'sessionId' for validation
/// </summary>
[DataMember(Name = "sessionId")]
public string SessionId { get; set; }
public string SessionId { get; set; }
/// <summary>
/// This is the 'security stamp' for validation
/// </summary>
[DataMember(Name = "securityStamp")]
public string SecurityStamp { get; set; }
[DataMember(Name = "id")]
public object Id { get; set; }
+85
View File
@@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Services
@@ -22,5 +26,86 @@ namespace Umbraco.Core.Services
uow.Commit();
}
}
/// <summary>
/// Returns paged items in the audit trail for a given entity
/// </summary>
/// <param name="entityId"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="orderDirection">
/// By default this will always be ordered descending (newest first)
/// </param>
/// <param name="auditTypeFilter">
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
/// so we need to do that here
/// </param>
/// <param name="customFilter">
/// Optional filter to be applied
/// </param>
/// <returns></returns>
public IEnumerable<IAuditItem> GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize, out long totalRecords,
Direction orderDirection = Direction.Descending,
AuditType[] auditTypeFilter = null,
IQuery<IAuditItem> customFilter = null)
{
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
Mandate.ParameterCondition(pageSize > 0, "pageSize");
if (entityId == Constants.System.Root || entityId <= 0)
{
totalRecords = 0;
return Enumerable.Empty<IAuditItem>();
}
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
{
var repository = RepositoryFactory.CreateAuditRepository(uow);
var query = Query<IAuditItem>.Builder.Where(x => x.Id == entityId);
return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
}
}
/// <summary>
/// Returns paged items in the audit trail for a given user
/// </summary>
/// <param name="userId"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="orderDirection">
/// By default this will always be ordered descending (newest first)
/// </param>
/// <param name="auditTypeFilter">
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
/// so we need to do that here
/// </param>
/// <param name="customFilter">
/// Optional filter to be applied
/// </param>
/// <returns></returns>
public IEnumerable<IAuditItem> GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords, Direction orderDirection = Direction.Descending, AuditType[] auditTypeFilter = null, IQuery<IAuditItem> customFilter = null)
{
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
Mandate.ParameterCondition(pageSize > 0, "pageSize");
if (userId < 0)
{
totalRecords = 0;
return Enumerable.Empty<IAuditItem>();
}
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
{
var repository = RepositoryFactory.CreateAuditRepository(uow);
var query = Query<IAuditItem>.Builder.Where(x => x.UserId == userId);
return repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, orderDirection, auditTypeFilter, customFilter);
}
}
}
}
@@ -2453,7 +2453,6 @@ namespace Umbraco.Core.Services
Audit(uow, AuditType.Publish, "Save and Publish performed by user", userId, content.Id);
uow.Commit();
return Attempt.If(publishStatus.StatusType == PublishStatusType.Success, publishStatus);
}
}
@@ -1,9 +1,58 @@
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Services
{
public interface IAuditService : IService
{
void Add(AuditType type, string comment, int userId, int objectId);
/// <summary>
/// Returns paged items in the audit trail for a given entity
/// </summary>
/// <param name="entityId"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="orderDirection">
/// By default this will always be ordered descending (newest first)
/// </param>
/// <param name="auditTypeFilter">
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
/// so we need to do that here
/// </param>
/// <param name="customFilter">
/// Optional filter to be applied
/// </param>
/// <returns></returns>
IEnumerable<IAuditItem> GetPagedItemsByEntity(int entityId, long pageIndex, int pageSize, out long totalRecords,
Direction orderDirection = Direction.Descending,
AuditType[] auditTypeFilter = null,
IQuery<IAuditItem> customFilter = null);
/// <summary>
/// Returns paged items in the audit trail for a given user
/// </summary>
/// <param name="userId"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="totalRecords"></param>
/// <param name="orderDirection">
/// By default this will always be ordered descending (newest first)
/// </param>
/// <param name="auditTypeFilter">
/// Since we currently do not have enum support with our expression parser, we cannot query on AuditType in the query or the custom filter
/// so we need to do that here
/// </param>
/// <param name="customFilter">
/// Optional filter to be applied
/// </param>
/// <returns></returns>
IEnumerable<IAuditItem> GetPagedItemsByUser(int userId, long pageIndex, int pageSize, out long totalRecords,
Direction orderDirection = Direction.Descending,
AuditType[] auditTypeFilter = null,
IQuery<IAuditItem> customFilter = null);
}
}
+30 -1
View File
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models;
@@ -11,7 +12,35 @@ namespace Umbraco.Core.Services
/// Defines the UserService, which is an easy access to operations involving <see cref="IProfile"/> and eventually Users.
/// </summary>
public interface IUserService : IMembershipUserService
{
{
/// <summary>
/// Creates a database entry for starting a new login session for a user
/// </summary>
/// <param name="userId"></param>
/// <param name="requestingIpAddress"></param>
/// <returns></returns>
Guid CreateLoginSession(int userId, string requestingIpAddress);
/// <summary>
/// Validates that a user login session is valid/current and hasn't been closed
/// </summary>
/// <param name="userId"></param>
/// <param name="sessionId"></param>
/// <returns></returns>
bool ValidateLoginSession(int userId, Guid sessionId);
/// <summary>
/// Removes the session's validity
/// </summary>
/// <param name="sessionId"></param>
void ClearLoginSession(Guid sessionId);
/// <summary>
/// Removes all valid sessions for the user
/// </summary>
/// <param name="userId"></param>
int ClearLoginSessions(int userId);
/// <summary>
/// This is basically facets of UserStates key = state, value = count
/// </summary>
+5 -62
View File
@@ -4,7 +4,6 @@ using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
@@ -12,7 +11,6 @@ using Umbraco.Core.Events;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
@@ -43,7 +41,7 @@ namespace Umbraco.Core.Services
if (userService == null) throw new ArgumentNullException("userService");
_dataTypeService = dataTypeService;
_userService = userService;
}
}
/// <summary>
/// Creates an <see cref="IMedia"/> object using the alias of the <see cref="IMediaType"/>
@@ -63,7 +61,7 @@ namespace Umbraco.Core.Services
{
var parent = GetById(parentId);
return CreateMedia(name, parent, mediaTypeAlias, userId);
}
}
/// <summary>
/// Creates an <see cref="IMedia"/> object using the alias of the <see cref="IMediaType"/>
@@ -731,65 +729,10 @@ namespace Umbraco.Core.Services
/// <returns><see cref="IMedia"/></returns>
public IMedia GetMediaByPath(string mediaPath)
{
var umbracoFileValue = mediaPath;
const string Pattern = ".*[_][0-9]+[x][0-9]+[.].*";
var isResized = Regex.IsMatch(mediaPath, Pattern);
// If the image has been resized we strip the "_403x328" of the original "/media/1024/koala_403x328.jpg" url.
if (isResized)
{
var underscoreIndex = mediaPath.LastIndexOf('_');
var dotIndex = mediaPath.LastIndexOf('.');
umbracoFileValue = string.Concat(mediaPath.Substring(0, underscoreIndex), mediaPath.Substring(dotIndex));
}
Func<string, Sql> createSql = url => new Sql().Select("*")
.From<PropertyDataDto>()
.InnerJoin<PropertyTypeDto>()
.On<PropertyDataDto, PropertyTypeDto>(left => left.PropertyTypeId, right => right.Id)
.Where<PropertyTypeDto>(x => x.Alias == "umbracoFile")
.Where<PropertyDataDto>(x => x.VarChar == url);
var sql = createSql(umbracoFileValue);
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
{
var propertyDataDto = uow.Database.Fetch<PropertyDataDto, PropertyTypeDto>(sql).FirstOrDefault();
// If the stripped-down url returns null, we try again with the original url.
// Previously, the function would fail on e.g. "my_x_image.jpg"
if (propertyDataDto == null)
{
sql = createSql(mediaPath);
propertyDataDto = uow.Database.Fetch<PropertyDataDto, PropertyTypeDto>(sql).FirstOrDefault();
}
// If no reults far, try getting from a json value stored in the ntext column query
if (propertyDataDto == null)
{
var ntextQuery = new Sql().Select("*")
.From<PropertyDataDto>()
.InnerJoin<PropertyTypeDto>()
.On<PropertyDataDto, PropertyTypeDto>(left => left.PropertyTypeId, right => right.Id)
.Where<PropertyTypeDto>(x => x.Alias == "umbracoFile")
.Where("dataNtext LIKE @0", "%" + umbracoFileValue + "%");
propertyDataDto = uow.Database.Fetch<PropertyDataDto, PropertyTypeDto>(ntextQuery).FirstOrDefault();
}
// If still no results, try getting from a json value stored in the nvarchar column
if (propertyDataDto == null)
{
var nvarcharQuery = new Sql().Select("*")
.From<PropertyDataDto>()
.InnerJoin<PropertyTypeDto>()
.On<PropertyDataDto, PropertyTypeDto>(left => left.PropertyTypeId, right => right.Id)
.Where<PropertyTypeDto>(x => x.Alias == "umbracoFile")
.Where("dataNvarchar LIKE @0", "%" + umbracoFileValue + "%");
propertyDataDto = uow.Database.Fetch<PropertyDataDto, PropertyTypeDto>(nvarcharQuery).FirstOrDefault();
}
return propertyDataDto == null ? null : GetById(propertyDataDto.NodeId);
var repository = RepositoryFactory.CreateMediaRepository(uow);
return repository.GetMediaByPath(mediaPath);
}
}
@@ -1021,7 +964,7 @@ namespace Umbraco.Core.Services
if (raiseEvents)
{
saveEventArgs.CanCancel = false;
saveEventArgs.CanCancel = false;
uow.Events.Dispatch(Saved, this, saveEventArgs);
}
+41
View File
@@ -558,6 +558,47 @@ namespace Umbraco.Core.Services
}
}
public Guid CreateLoginSession(int userId, string requestingIpAddress)
{
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateUserRepository(uow);
var session = repository.CreateLoginSession(userId, requestingIpAddress);
uow.Commit();
return session;
}
}
public int ClearLoginSessions(int userId)
{
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateUserRepository(uow);
var count = repository.ClearLoginSessions(userId);
uow.Commit();
return count;
}
}
public void ClearLoginSession(Guid sessionId)
{
using (var uow = UowProvider.GetUnitOfWork())
{
var repository = RepositoryFactory.CreateUserRepository(uow);
repository.ClearLoginSession(sessionId);
uow.Commit();
}
}
public bool ValidateLoginSession(int userId, Guid sessionId)
{
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
{
var repository = RepositoryFactory.CreateUserRepository(uow);
return repository.ValidateLoginSession(userId, sessionId);
}
}
public IDictionary<UserState, int> GetUserStates()
{
using (var uow = UowProvider.GetUnitOfWork(readOnly: true))
@@ -87,7 +87,8 @@ namespace Umbraco.Core.Sync
{
UtcStamp = DateTime.UtcNow,
Instructions = JsonConvert.SerializeObject(instructions, Formatting.None),
OriginIdentity = LocalIdentity
OriginIdentity = LocalIdentity,
InstructionCount = instructions.Sum(x => x.JsonIdCount)
};
ApplicationContext.DatabaseContext.Database.Insert(dto);
@@ -165,10 +166,10 @@ namespace Umbraco.Core.Sync
}
else
{
//check for how many instructions there are to process
//TODO: In 7.6 we need to store the count of instructions per row since this is not affective because there can be far more than one (if not thousands)
// of instructions in a single row.
var count = _appContext.DatabaseContext.Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId});
//check for how many instructions there are to process, each row contains a count of the number of instructions contained in each
//row so we will sum these numbers to get the actual count.
var count = _appContext.DatabaseContext.Database
.ExecuteScalar<int>("SELECT SUM(instructionCount) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId});
if (count > Options.MaxProcessingInstructionCount)
{
//too many instructions, proceed to cold boot
@@ -205,7 +206,7 @@ namespace Umbraco.Core.Sync
/// <summary>
/// Synchronize the server (throttled).
/// </summary>
protected void Sync()
protected internal void Sync()
{
lock (_locko)
{
+35 -4
View File
@@ -18,7 +18,10 @@ namespace Umbraco.Core.Sync
// need this public, parameter-less constructor so the web service messenger
// can de-serialize the instructions it receives
public RefreshInstruction()
{ }
{
//set default - this value is not used for reading after it's been deserialized, it's only used for persisting the instruction to the db
JsonIdCount = 1;
}
// need this public one so it can be de-serialized - used by the Json thing
// otherwise, should use GetInstructions(...)
@@ -30,12 +33,16 @@ namespace Umbraco.Core.Sync
IntId = intId;
JsonIds = jsonIds;
JsonPayload = jsonPayload;
//set default - this value is not used for reading after it's been deserialized, it's only used for persisting the instruction to the db
JsonIdCount = 1;
}
private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType)
{
RefresherId = refresher.UniqueIdentifier;
RefreshType = refreshType;
//set default - this value is not used for reading after it's been deserialized, it's only used for persisting the instruction to the db
JsonIdCount = 1;
}
private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, Guid guidId)
@@ -50,9 +57,21 @@ namespace Umbraco.Core.Sync
IntId = intId;
}
private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, string json)
/// <summary>
/// A private constructor to create a new instance
/// </summary>
/// <param name="refresher"></param>
/// <param name="refreshType"></param>
/// <param name="json"></param>
/// <param name="idCount">
/// When the refresh method is <see cref="RefreshMethodType.RefreshByIds"/> we know how many Ids are being refreshed so we know the instruction
/// count which will be taken into account when we store this count in the database.
/// </param>
private RefreshInstruction(ICacheRefresher refresher, RefreshMethodType refreshType, string json, int idCount = 1)
: this(refresher, refreshType)
{
JsonIdCount = idCount;
if (refreshType == RefreshMethodType.RefreshByJson)
JsonPayload = json;
else
@@ -77,8 +96,12 @@ namespace Umbraco.Core.Sync
case MessageType.RefreshById:
if (idType == null)
throw new InvalidOperationException("Cannot refresh by id if idType is null.");
if (idType == typeof (int)) // bulk of ints is supported
return new[] { new RefreshInstruction(refresher, RefreshMethodType.RefreshByIds, JsonConvert.SerializeObject(ids.Cast<int>().ToArray())) };
if (idType == typeof(int))
{
// bulk of ints is supported
var intIds = ids.Cast<int>().ToArray();
return new[] { new RefreshInstruction(refresher, RefreshMethodType.RefreshByIds, JsonConvert.SerializeObject(intIds), intIds.Length) };
}
// else must be guids, bulk of guids is not supported, iterate
return ids.Select(x => new RefreshInstruction(refresher, RefreshMethodType.RefreshByGuid, (Guid) x));
@@ -121,6 +144,14 @@ namespace Umbraco.Core.Sync
/// </summary>
public string JsonIds { get; set; }
/// <summary>
/// Gets or sets the number of Ids contained in the JsonIds json value
/// </summary>
/// <remarks>
/// This is used to determine the instruction count per row
/// </remarks>
public int JsonIdCount { get; set; }
/// <summary>
/// Gets or sets the payload data value.
/// </summary>
+17 -4
View File
@@ -52,10 +52,6 @@
<HintPath>..\packages\HtmlAgilityPack.1.4.9.5\lib\Net45\HtmlAgilityPack.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="ImageProcessor, Version=2.5.6.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ImageProcessor.2.5.6\lib\net45\ImageProcessor.dll</HintPath>
</Reference>
@@ -117,6 +113,8 @@
</Reference>
<Reference Include="System.DirectoryServices.AccountManagement" />
<Reference Include="System.Drawing" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.Linq.Expressions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.WebRequest" />
@@ -248,6 +246,7 @@
<Compile Include="Configuration\UmbracoConfigurationSection.cs" />
<Compile Include="Configuration\UmbracoSettings\AppCodeFileExtensionsCollection.cs" />
<Compile Include="Configuration\UmbracoSettings\AppCodeFileExtensionsElement.cs" />
<Compile Include="Configuration\UmbracoSettings\BackOfficeElement.cs" />
<Compile Include="Configuration\UmbracoSettings\CharCollection.cs" />
<Compile Include="Configuration\UmbracoSettings\CharElement.cs" />
<Compile Include="Configuration\CommaDelimitedConfigurationElement.cs" />
@@ -256,6 +255,7 @@
<Compile Include="Configuration\UmbracoSettings\ContentErrorPageElement.cs" />
<Compile Include="Configuration\UmbracoSettings\ContentErrorsElement.cs" />
<Compile Include="Configuration\UmbracoSettings\ContentSectionExtensions.cs" />
<Compile Include="Configuration\UmbracoSettings\IBackOfficeSection.cs" />
<Compile Include="Configuration\UmbracoSettings\ImagingAutoFillPropertiesCollection.cs" />
<Compile Include="Configuration\UmbracoSettings\ImagingAutoFillUploadFieldElement.cs" />
<Compile Include="Configuration\UmbracoSettings\ContentImagingElement.cs" />
@@ -291,6 +291,7 @@
<Compile Include="Configuration\UmbracoSettings\ISecuritySection.cs" />
<Compile Include="Configuration\UmbracoSettings\IServer.cs" />
<Compile Include="Configuration\UmbracoSettings\ITemplatesSection.cs" />
<Compile Include="Configuration\UmbracoSettings\ITourSection.cs" />
<Compile Include="Configuration\UmbracoSettings\IUmbracoSettingsSection.cs" />
<Compile Include="Configuration\UmbracoSettings\IViewStateMoverModuleSection.cs" />
<Compile Include="Configuration\UmbracoSettings\IWebRoutingSection.cs" />
@@ -321,6 +322,7 @@
<Compile Include="Configuration\UmbracoSettings\ServerCollection.cs" />
<Compile Include="Configuration\UmbracoSettings\ServerElement.cs" />
<Compile Include="Configuration\UmbracoSettings\TemplatesElement.cs" />
<Compile Include="Configuration\UmbracoSettings\TourConfigElement.cs" />
<Compile Include="Configuration\UmbracoSettings\UmbracoConfigurationElement.cs" />
<Compile Include="Configuration\UmbracoSettings\UmbracoSettingsSection.cs" />
<Compile Include="Configuration\UmbracoSettings\UrlReplacingElement.cs" />
@@ -368,6 +370,7 @@
<Compile Include="IHttpContextAccessor.cs" />
<Compile Include="Models\EntityBase\EntityPath.cs" />
<Compile Include="Models\EntityBase\IDeletableEntity.cs" />
<Compile Include="Models\IAuditItem.cs" />
<Compile Include="Models\IUserControl.cs" />
<Compile Include="Models\Membership\ContentPermissionSet.cs" />
<Compile Include="Models\Membership\EntityPermissionCollection.cs" />
@@ -379,6 +382,8 @@
<Compile Include="Models\Membership\UserState.cs" />
<Compile Include="Models\Membership\UserType.cs" />
<Compile Include="Models\PublishedContent\PublishedContentTypeConverter.cs" />
<Compile Include="Models\Rdbms\MediaDto.cs" />
<Compile Include="Models\Rdbms\UserLoginDto.cs" />
<Compile Include="Models\Rdbms\UserStartNodeDto.cs" />
<Compile Include="Models\UserControl.cs" />
<Compile Include="OrderedHashSet.cs" />
@@ -540,12 +545,18 @@
<Compile Include="Persistence\LockedRepository.cs" />
<Compile Include="Persistence\LockingRepository.cs" />
<Compile Include="Persistence\Mappers\AccessMapper.cs" />
<Compile Include="Persistence\Mappers\AuditMapper.cs" />
<Compile Include="Persistence\Mappers\DomainMapper.cs" />
<Compile Include="Persistence\Mappers\ExternalLoginMapper.cs" />
<Compile Include="Persistence\Mappers\MigrationEntryMapper.cs" />
<Compile Include="Persistence\Mappers\UserGroupMapper.cs" />
<Compile Include="Persistence\Migrations\LocalMigrationContext.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionFourOneZero\AddPreviewXmlTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddTourDataUserColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddIndexToPropertyTypeAliasColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddInstructionCountColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddCmsMediaTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenEightZero\AddUserLoginTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\AddIndexToDictionaryKeyColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\EnsureContentTemplatePermissions.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenSevenZero\ReduceDictionaryKeyColumnsSize.cs" />
@@ -693,11 +704,13 @@
<Compile Include="Security\IBackOfficeUserPasswordChecker.cs" />
<Compile Include="Security\IMembershipProviderPasswordHasher.cs" />
<Compile Include="Security\IUserAwarePasswordHasher.cs" />
<Compile Include="Security\IUserSessionStore.cs" />
<Compile Include="Security\MachineKeyGenerator.cs" />
<Compile Include="Security\MembershipProviderPasswordHasher.cs" />
<Compile Include="Security\EmailService.cs" />
<Compile Include="Security\MembershipProviderPasswordValidator.cs" />
<Compile Include="Security\OwinExtensions.cs" />
<Compile Include="Security\SessionIdValidator.cs" />
<Compile Include="Security\UmbracoEmailMessage.cs" />
<Compile Include="Security\UserAwareMembershipProviderPasswordHasher.cs" />
<Compile Include="SemVersionExtensions.cs" />
-1
View File
@@ -17,6 +17,5 @@
<package id="Newtonsoft.Json" version="10.0.2" targetFramework="net45" />
<package id="Owin" version="1.0" targetFramework="net45" />
<package id="semver" version="1.1.2" targetFramework="net45" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net45" />
<package id="SqlServerCE" version="4.0.0.1" targetFramework="net45" />
</packages>
@@ -121,8 +121,7 @@ namespace Umbraco.Tests.Models.Mapping
{
AssertDisplayProperty(result, p, ApplicationContext);
}
Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count() - 1);
Assert.IsTrue(result.Tabs.Any(x => x.Label == ui.Text("general", "properties")));
Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count());
Assert.IsTrue(result.Tabs.First().IsActive);
Assert.IsTrue(result.Tabs.Except(new[] {result.Tabs.First()}).All(x => x.IsActive == false));
}
@@ -0,0 +1,47 @@
using System;
using System.IO;
using System.Linq;
using NUnit.Framework;
using Umbraco.Core.Packaging;
namespace Umbraco.Tests.Packaging
{
[TestFixture]
class PackageExtractionTests
{
private const string PackageFileName = "Document_Type_Picker_1.1.umb";
private static string GetTestPackagePath(string packageName)
{
const string testPackagesDirName = "Packaging\\Packages";
string path = Path.Combine(Core.Configuration.GlobalSettings.FullpathToRoot, testPackagesDirName, packageName);
return path;
}
[Test]
public void ReadFilesFromArchive_NumberOfFilesIs1_SearchingForPackageXmlFile()
{
// Arrange
var sut = new PackageExtraction();
// Act
var result = sut.ReadFilesFromArchive(GetTestPackagePath(PackageFileName), new[] { "Package.xml" });
// Assert
Assert.AreEqual(1, result.Count());
}
[Test]
public void FindMissingFiles_1_UnknownFile()
{
// Arrange
var sut = new PackageExtraction();
// Act
var result = sut.FindMissingFiles(GetTestPackagePath(PackageFileName), new[] { "DoesNotExists.XYZ" });
// Assert
Assert.AreEqual(1, result.Count());
}
}
}
@@ -40,5 +40,86 @@ namespace Umbraco.Tests.Persistence.Repositories
Assert.That(dtos.Any(), Is.True);
Assert.That(dtos.First().Comment, Is.EqualTo("This is a System audit trail"));
}
[Test]
public void Get_Paged_Items()
{
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
{
for (int i = 0; i < 100; i++)
{
repo.AddOrUpdate(new AuditItem(i, string.Format("Content {0} created", i), AuditType.New, 0));
repo.AddOrUpdate(new AuditItem(i, string.Format("Content {0} published", i), AuditType.Publish, 0));
}
unitOfWork.Commit();
}
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
{
long total;
var page = repo.GetPagedResultsByQuery(Query<IAuditItem>.Builder, 0, 10, out total, Direction.Descending, null, null);
Assert.AreEqual(10, page.Count());
Assert.AreEqual(200, total);
}
}
[Test]
public void Get_Paged_Items_With_AuditType_Filter()
{
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
{
for (int i = 0; i < 100; i++)
{
repo.AddOrUpdate(new AuditItem(i, string.Format("Content {0} created", i), AuditType.New, 0));
repo.AddOrUpdate(new AuditItem(i, string.Format("Content {0} published", i), AuditType.Publish, 0));
}
unitOfWork.Commit();
}
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
{
long total;
var page = repo.GetPagedResultsByQuery(Query<IAuditItem>.Builder, 0, 9, out total, Direction.Descending,
new[] {AuditType.Publish}, null)
.ToArray();
Assert.AreEqual(9, page.Length);
Assert.IsTrue(page.All(x => x.AuditType == AuditType.Publish));
Assert.AreEqual(100, total);
}
}
[Test]
public void Get_Paged_Items_With_Custom_Filter()
{
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
{
for (int i = 0; i < 100; i++)
{
repo.AddOrUpdate(new AuditItem(i, "Content created", AuditType.New, 0));
repo.AddOrUpdate(new AuditItem(i, "Content published", AuditType.Publish, 0));
}
unitOfWork.Commit();
}
using (var repo = new AuditRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
{
long total;
var page = repo.GetPagedResultsByQuery(Query<IAuditItem>.Builder, 0, 8, out total, Direction.Descending,
null, Query<IAuditItem>.Builder.Where(item => item.Comment == "Content created"))
.ToArray();
Assert.AreEqual(8, page.Length);
Assert.IsTrue(page.All(x => x.Comment == "Content created"));
Assert.AreEqual(100, total);
}
}
}
}
@@ -271,7 +271,7 @@ AnotherContentFinder
public void Resolves_Assigned_Mappers()
{
var foundTypes1 = _manager.ResolveAssignedMapperTypes();
Assert.AreEqual(28, foundTypes1.Count());
Assert.AreEqual(29, foundTypes1.Count());
}
[Test]
@@ -22,6 +22,7 @@ namespace Umbraco.Tests.Security
public void Create_From_Claims_Identity()
{
var sessionId = Guid.NewGuid().ToString();
var securityStamp = Guid.NewGuid().ToString();
var claimsIdentity = new ClaimsIdentity(new[]
{
//This is the id that 'identity' uses to check for the user id
@@ -36,12 +37,14 @@ namespace Umbraco.Tests.Security
new Claim(ClaimTypes.Locality, "en-us", ClaimValueTypes.String, TestIssuer, TestIssuer),
new Claim(Constants.Security.SessionIdClaimType, sessionId, Constants.Security.SessionIdClaimType, TestIssuer, TestIssuer),
new Claim(ClaimsIdentity.DefaultRoleClaimType, "admin", ClaimValueTypes.String, TestIssuer, TestIssuer),
new Claim(Microsoft.AspNet.Identity.Constants.DefaultSecurityStampClaimType, securityStamp, ClaimValueTypes.String, TestIssuer, TestIssuer),
});
var backofficeIdentity = UmbracoBackOfficeIdentity.FromClaimsIdentity(claimsIdentity);
Assert.AreEqual("1234", backofficeIdentity.Id);
Assert.AreEqual(sessionId, backofficeIdentity.SessionId);
Assert.AreEqual(securityStamp, backofficeIdentity.SecurityStamp);
Assert.AreEqual("testing", backofficeIdentity.Username);
Assert.AreEqual("hello world", backofficeIdentity.RealName);
Assert.AreEqual(1, backofficeIdentity.StartContentNodes.Length);
@@ -50,7 +53,7 @@ namespace Umbraco.Tests.Security
Assert.AreEqual("en-us", backofficeIdentity.Culture);
Assert.IsTrue(new[] { "admin" }.SequenceEqual(backofficeIdentity.Roles));
Assert.AreEqual(10, backofficeIdentity.Claims.Count());
Assert.AreEqual(11, backofficeIdentity.Claims.Count());
}
[Test]
@@ -93,6 +96,7 @@ namespace Umbraco.Tests.Security
var sessionId = Guid.NewGuid().ToString();
var userData = new UserData(sessionId)
{
SecurityStamp = sessionId,
AllowedApplications = new[] {"content", "media"},
Culture = "en-us",
Id = 1234,
@@ -113,6 +117,7 @@ namespace Umbraco.Tests.Security
var sessionId = Guid.NewGuid().ToString();
var userData = new UserData(sessionId)
{
SecurityStamp = sessionId,
AllowedApplications = new[] { "content", "media" },
Culture = "en-us",
Id = 1234,
@@ -139,6 +144,7 @@ namespace Umbraco.Tests.Security
var sessionId = Guid.NewGuid().ToString();
var userData = new UserData(sessionId)
{
SecurityStamp = sessionId,
AllowedApplications = new[] { "content", "media" },
Culture = "en-us",
Id = 1234,
@@ -162,6 +168,7 @@ namespace Umbraco.Tests.Security
var sessionId = Guid.NewGuid().ToString();
var userData = new UserData(sessionId)
{
SecurityStamp = sessionId,
AllowedApplications = new[] { "content", "media" },
Culture = "en-us",
Id = 1234,
@@ -24,9 +24,11 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
{
protected override Task<AuthenticationTicket> AuthenticateCoreAsync()
{
var sessionId = Guid.NewGuid().ToString();
var identity = new UmbracoBackOfficeIdentity(
new UserData(Guid.NewGuid().ToString())
new UserData(sessionId)
{
SecurityStamp = sessionId,
Id = 0,
Roles = new[] { "admin" },
AllowedApplications = new[] { "content", "media", "members" },
+1
View File
@@ -188,6 +188,7 @@
<Compile Include="DateTimeExtensionsTests.cs" />
<Compile Include="Cache\CacheRefresherEventHandlerTests.cs" />
<Compile Include="Models\Mapping\UserModelMapperTests.cs" />
<Compile Include="Packaging\PackageExtractionTests.cs" />
<Compile Include="Persistence\Repositories\SimilarNodeNameTests.cs" />
<Compile Include="TestHelpers\ControllerTesting\AuthenticateEverythingExtensions.cs" />
<Compile Include="TestHelpers\ControllerTesting\AuthenticateEverythingMiddleware.cs" />
@@ -405,7 +405,7 @@ namespace Umbraco.Tests.Web.Mvc
new Mock<IMemberTypeService>().Object,
new Mock<IMemberService>().Object,
new Mock<IUserService>().Object,
new Mock<ISectionService>().Object,
new Mock<ISectionService>().Object,
new Mock<IApplicationTreeService>().Object,
new Mock<ITagService>().Object,
new Mock<INotificationService>().Object,
+7 -2
View File
@@ -25,13 +25,14 @@
"jquery-migrate": "1.4.0",
"angular-dynamic-locale": "0.1.28",
"ng-file-upload": "~7.3.8",
"tinymce": "~4.5.3",
"tinymce": "~4.7.1",
"codemirror": "~5.3.0",
"angular-local-storage": "~0.2.3",
"moment": "~2.10.3",
"ace-builds": "^1.2.3",
"clipboard": "1.7.1",
"font-awesome": "~4.2",
"clipboard": "1.7.1"
"angular-moment": "^1.0.1"
},
"install": {
@@ -68,6 +69,10 @@
"bower_components/angular-local-storage/dist/angular-local-storage.min.js.map"
],
"angular-moment": [
"bower_components/angular-moment/angular-moment.min.js"
],
"tinymce": [
"bower_components/tinymce/tinymce.min.js"
],
+605
View File
@@ -0,0 +1,605 @@
module.exports = function (grunt) {
// Default task.
grunt.registerTask('default', ['jshint:dev', 'build', 'karma:unit']);
grunt.registerTask('dev', ['jshint:dev', 'build-dev', 'webserver', 'open:dev', 'watch']);
grunt.registerTask('docserve', ['docs:api', 'connect:docserver', 'open:docs', 'watch:docs']);
grunt.registerTask('vs', ['jshint:dev', 'build-dev', 'watch']);
//TODO: Too much watching, this brings windows to it's knees when in dev mode
//run by the watch task
grunt.registerTask('watch-js', ['jshint:dev', 'concat', 'copy:app', 'copy:mocks', 'copy:canvasdesigner', 'copy:vs', 'karma:unit']);
grunt.registerTask('watch-less', ['recess:build', 'recess:installer', 'recess:nonodes', 'recess:canvasdesigner', 'postcss', 'copy:canvasdesigner', 'copy:assets', 'copy:vs']);
grunt.registerTask('watch-html', ['copy:views', 'copy:vs']);
grunt.registerTask('watch-installer', ['concat:install', 'concat:installJs', 'copy:installer', 'copy:vs']);
grunt.registerTask('watch-canvasdesigner', ['copy:canvasdesigner', 'concat:canvasdesignerJs', 'copy:vs']);
grunt.registerTask('watch-test', ['jshint:dev', 'karma:unit']);
//triggered from grunt
grunt.registerTask('build', ['concat', 'recess:build', 'recess:installer', 'recess:nonodes', 'recess:canvasdesigner', 'postcss', 'bower-install-simple', 'bower', 'copy', 'clean:post']);
//triggered from grunt dev vs or grunt vs
grunt.registerTask('build-dev', ['clean:pre', 'concat', 'recess:build', 'recess:installer', 'recess:nonodes', 'postcss', 'bower-install-simple', 'bower', 'copy']);
//utillity tasks
grunt.registerTask('docs', ['ngdocs']);
grunt.registerTask('webserver', ['connect:devserver']);
// Print a timestamp (useful for when watching)
grunt.registerTask('timestamp', function () {
grunt.log.subhead(Date());
});
// Project configuration.
grunt.initConfig({
buildVersion: grunt.option('buildversion') || '7',
connect: {
devserver: {
options: {
port: 9990,
hostname: '0.0.0.0',
base: './build',
middleware: function(connect, options) {
return [
//uncomment to enable CSP
// util.csp(),
//util.rewrite(),
connect.favicon('images/favicon.ico'),
connect.static(options.base),
connect.directory(options.base)
];
}
}
},
testserver: {},
docserver: {
options: {
port: 8880,
hostname: '0.0.0.0',
base: './docs/api',
middleware: function(connect, options) {
return [
//uncomment to enable CSP
// util.csp(),
//util.rewrite(),
connect.static(options.base),
connect.directory(options.base)
];
}
}
},
},
open: {
dev: {
path: 'http://localhost:9990/belle/'
},
docs: {
path: 'http://localhost:8880/index.html'
}
},
distdir: 'build/belle',
vsdir: '../Umbraco.Web.UI/umbraco',
pkg: grunt.file.readJSON('package.json'),
banner:
'/*! <%= pkg.title || pkg.name %>\n' +
'<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' +
' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;\n' +
' * Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n */\n',
src: {
js: ['src/**/*.js', 'src/*.js'],
common: ['src/common/**/*.js'],
controllers: ['src/**/*.controller.js'],
specs: ['test/**/*.spec.js'],
scenarios: ['test/**/*.scenario.js'],
samples: ['sample files/*.js'],
html: ['src/index.html', 'src/install.html'],
everything: ['src/**/*.*', 'test/**/*.*', 'docs/**/*.*'],
tpl: {
app: ['src/views/**/*.html'],
common: ['src/common/**/*.tpl.html']
},
less: ['src/less/belle.less'], // recess:build doesn't accept ** in its file patterns
prod: ['<%= distdir %>/js/*.js']
},
clean: {
pre: ['<%= distdir %>/*'],
post: ['<%= distdir %>/js/*.dev.js']
},
copy: {
assets: {
files: [{ dest: '<%= distdir %>/assets', src: '**', expand: true, cwd: 'src/assets/' }]
},
config: {
files: [{ dest: '<%= distdir %>/../config', src: '**', expand: true, cwd: 'src/config/' }]
},
installer: {
files: [{ dest: '<%= distdir %>/views/install', src: '**/*.html', expand: true, cwd: 'src/installer/steps' }]
},
canvasdesigner: {
files: [
{ dest: '<%= distdir %>/preview', src: '**/*.html', expand: true, cwd: 'src/canvasdesigner' },
{ dest: '<%= distdir %>/preview/editors', src: '**/*.html', expand: true, cwd: 'src/canvasdesigner/editors' },
{ dest: '<%= distdir %>/assets/less', src: '**/*.less', expand: true, cwd: 'src/canvasdesigner/editors' },
{ dest: '<%= distdir %>/js', src: 'canvasdesigner.config.js', expand: true, cwd: 'src/canvasdesigner/config' },
{ dest: '<%= distdir %>/js', src: 'canvasdesigner.palettes.js', expand: true, cwd: 'src/canvasdesigner/config' },
{ dest: '<%= distdir %>/js', src: 'canvasdesigner.front.js', expand: true, cwd: 'src/canvasdesigner' }
]
},
vendor: {
files: [{ dest: '<%= distdir %>/lib', src: '**', expand: true, cwd: 'lib/' }]
},
views: {
files: [{ dest: '<%= distdir %>/views', src: ['**/*.*', '!**/*.controller.js'], expand: true, cwd: 'src/views' }]
},
app: {
files: [
{ dest: '<%= distdir %>/js', src: '*.js', expand: true, cwd: 'src/' }
]
},
mocks: {
files: [{ dest: '<%= distdir %>/js', src: '*.js', expand: true, cwd: 'src/common/mocks/' }]
},
vs: {
files: [
//everything except the index.html root file!
//then we need to figure out how to not copy all the test stuff either!?
{ dest: '<%= vsdir %>/assets', src: '**', expand: true, cwd: '<%= distdir %>/assets' },
{ dest: '<%= vsdir %>/js', src: '**', expand: true, cwd: '<%= distdir %>/js' },
{ dest: '<%= vsdir %>/views', src: '**', expand: true, cwd: '<%= distdir %>/views' },
{ dest: '<%= vsdir %>/preview', src: '**', expand: true, cwd: '<%= distdir %>/preview' },
{ dest: '<%= vsdir %>/lib', src: '**', expand: true, cwd: '<%= distdir %>/lib' }
]
}
},
karma: {
unit: { configFile: 'test/config/karma.conf.js', keepalive: true },
e2e: { configFile: 'test/config/e2e.js', keepalive: true },
watch: { configFile: 'test/config/unit.js', singleRun: false, autoWatch: true, keepalive: true }
},
concat: {
index: {
src: ['src/index.html'],
dest: '<%= distdir %>/index.html',
options: {
process: true
}
},
install: {
src: ['src/installer/installer.html'],
dest: '<%= distdir %>/installer.html',
options: {
process: true
}
},
installJs: {
src: ['src/installer/**/*.js'],
dest: '<%= distdir %>/js/umbraco.installer.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
},
canvasdesignerJs: {
src: ['src/canvasdesigner/canvasdesigner.global.js', 'src/canvasdesigner/canvasdesigner.controller.js', 'src/canvasdesigner/editors/*.js', 'src/canvasdesigner/lib/*.js'],
dest: '<%= distdir %>/js/canvasdesigner.panel.js'
},
controllers: {
src: ['src/controllers/**/*.controller.js', 'src/views/**/*.controller.js'],
dest: '<%= distdir %>/js/umbraco.controllers.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
},
services: {
src: ['src/common/services/*.js'],
dest: '<%= distdir %>/js/umbraco.services.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
},
security: {
src: ['src/common/security/*.js'],
dest: '<%= distdir %>/js/umbraco.security.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
},
resources: {
src: ['src/common/resources/*.js'],
dest: '<%= distdir %>/js/umbraco.resources.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
},
testing: {
src: ['src/common/mocks/*/*.js'],
dest: '<%= distdir %>/js/umbraco.testing.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
},
directives: {
src: ['src/common/directives/**/*.js'],
dest: '<%= distdir %>/js/umbraco.directives.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
},
filters: {
src: ['src/common/filters/*.js'],
dest: '<%= distdir %>/js/umbraco.filters.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
footer: "\n\n})();"
}
}
},
uglify: {
options: {
mangle: true
},
combine: {
files: {
'<%= distdir %>/js/umbraco.min.js': ['<%= distdir %>/js/umbraco.*.js']
}
}
},
recess: {
build: {
files: {
'<%= distdir %>/assets/css/<%= pkg.name %>.css':
['<%= src.less %>']
},
options: {
compile: true,
compress: true
}
},
nonodes: {
files: {
'<%= distdir %>/assets/css/nonodes.style.min.css':
['src/less/pages/nonodes.less']
},
options: {
compile: true,
compress: true
}
},
installer: {
files: {
'<%= distdir %>/assets/css/installer.css':
['src/less/installer.less']
},
options: {
compile: true,
compress: true
}
},
canvasdesigner: {
files: {
'<%= distdir %>/assets/css/canvasdesigner.css':
['src/less/canvas-designer.less', 'src/less/helveticons.less']
},
options: {
compile: true,
compress: true
}
}
},
postcss: {
options: {
processors: [
// add vendor prefixes
require('autoprefixer-core')({
browsers: 'last 2 versions'
})
]
},
dist: {
src: '<%= distdir %>/assets/css/<%= pkg.name %>.css'
}
},
ngTemplateCache: {
views: {
files: {
'<%= distdir %>/js/umbraco.views.js': 'src/views/**/*.html'
},
options: {
trim: 'src/',
module: 'umbraco.views'
}
}
},
watch: {
docs: {
files: ['docs/src/**/*.md'],
tasks: ['watch-docs', 'timestamp']
},
css: {
files: 'src/**/*.less',
tasks: ['watch-less', 'timestamp'],
options: {
livereload: true,
},
},
js: {
files: ['src/**/*.js', 'src/*.js'],
tasks: ['watch-js', 'timestamp'],
},
test: {
files: ['test/**/*.js'],
tasks: ['watch-test', 'timestamp'],
},
installer: {
files: ['src/installer/**/*.*'],
tasks: ['watch-installer', 'timestamp'],
},
canvasdesigner: {
files: ['src/canvasdesigner/**/*.*'],
tasks: ['watch-canvasdesigner', 'timestamp'],
},
html: {
files: ['src/views/**/*.html', 'src/*.html'],
tasks: ['watch-html', 'timestamp']
},
options: {
interval: 500
}
},
ngdocs: {
options: {
dest: 'docs/api',
startPage: '/api',
title: "Umbraco Backoffice UI API Documentation",
html5Mode: false,
styles: [
'docs/umb-docs.css'
],
image: "https://our.umbraco.org/assets/images/logo.svg"
},
api: {
src: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'],
title: 'API Documentation'
},
tutorials: {
src: [],
title: ''
}
},
eslint:{
src: ['<%= src.common %>','<%= src.controllers %>'],
options: {quiet: true}
},
jshint: {
dev: {
files: {
src: ['<%= src.common %>']
},
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: "nofunc",
newcap: true,
noarg: true,
sub: true,
boss: true,
//NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete
es5: true,
eqnull: true,
//NOTE: we need to use eval sometimes so ignore it
evil: true,
//NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those
scripturl: true,
//NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used
smarttabs: true,
globals: {}
}
},
build: {
files: {
src: ['<%= src.prod %>']
},
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: "nofunc",
newcap: true,
noarg: true,
sub: true,
boss: true,
//NOTE: This is required so it doesn't barf on reserved words like delete when doing $http.delete
es5: true,
eqnull: true,
//NOTE: we need to use eval sometimes so ignore it
evil: true,
//NOTE: we need to check for strings such as "javascript:" so don't throw errors regarding those
scripturl: true,
//NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used
smarttabs: true,
globalstrict: true,
globals: { $: false, jQuery: false, define: false, require: false, window: false }
}
}
},
bower: {
dev: {
dest: '<%= distdir %>/lib',
options: {
expand: true,
ignorePackages: ['bootstrap'],
packageSpecific: {
'moment': {
keepExpandedHierarchy: false,
files: ['min/moment-with-locales.js']
},
'typeahead.js': {
keepExpandedHierarchy: false,
files: ['dist/typeahead.bundle.min.js']
},
'underscore': {
files: ['underscore-min.js', 'underscore-min.map']
},
'rgrove-lazyload': {
files: ['lazyload.js']
},
'bootstrap-social': {
files: ['bootstrap-social.css']
},
'font-awesome': {
files: ['css/font-awesome.min.css', 'fonts/*']
},
"jquery": {
keepExpandedHierarchy: false,
files: ['dist/jquery.min.js', 'dist/jquery.min.map']
},
'jquery-ui': {
keepExpandedHierarchy: false,
files: ['jquery-ui.min.js']
},
'jquery-migrate': {
keepExpandedHierarchy: false,
files: ['jquery-migrate.min.js']
},
'tinymce': {
files: ['plugins/**', 'themes/**', 'tinymce.min.js']
},
'angular-dynamic-locale': {
files: ['tmhDynamicLocale.min.js', 'tmhDynamicLocale.min.js.map']
},
'ng-file-upload': {
keepExpandedHierarchy: false,
files: ['ng-file-upload.min.js']
},
'angular-local-storage': {
keepExpandedHierarchy: false,
files: ['dist/angular-local-storage.min.js']
},
'codemirror': {
files: [
'lib/codemirror.js',
'lib/codemirror.css',
'mode/css/*',
'mode/javascript/*',
'mode/xml/*',
'mode/htmlmixed/*',
'addon/search/*',
'addon/edit/*',
'addon/selection/*',
'addon/dialog/*'
]
},
'ace-builds': {
files: [
'src-min-noconflict/ace.js',
'src-min-noconflict/ext-language_tools.js',
'src-min-noconflict/ext-searchbox.js',
'src-min-noconflict/ext-settings_menu.js',
'src-min-noconflict/snippets/text.js',
'src-min-noconflict/snippets/javascript.js',
'src-min-noconflict/theme-chrome.js',
'src-min-noconflict/mode-razor.js',
'src-min-noconflict/mode-javascript.js',
'src-min-noconflict/worker-javascript.js',
]
},
'clipboard': {
keepExpandedHierarchy: false,
files: ['dist/clipboard.min.js']
},
'angular-moment': {
keepExpandedHierarchy: false,
files: ['angular-moment.min.js']
}
}
}
},
options: {
expand: true
}
},
"bower-install-simple": {
options: {
color: true
},
"dev": {}
}
});
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-recess');
grunt.loadNpmTasks('grunt-postcss');
grunt.loadNpmTasks('grunt-karma');
grunt.loadNpmTasks('grunt-open');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks("grunt-bower-install-simple");
grunt.loadNpmTasks('grunt-bower');
grunt.loadNpmTasks('grunt-ngdocs');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-hustler');
};
+2 -1
View File
@@ -11,7 +11,8 @@ var app = angular.module('umbraco', [
'ngMobile',
'tmh.dynamicLocale',
'ngFileUpload',
'LocalStorageModule'
'LocalStorageModule',
'angularMoment'
]);
var packages = angular.module("umbraco.packages", []);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

After

Width:  |  Height:  |  Size: 440 KiB

@@ -0,0 +1,116 @@
(function () {
"use strict";
function BackdropDirective($timeout, $http) {
function link(scope, el, attr, ctrl) {
var events = [];
scope.clickBackdrop = function(event) {
if(scope.disableEventsOnClick === true) {
event.preventDefault();
event.stopPropagation();
}
};
function onInit() {
if (scope.highlightElement) {
setHighlight();
}
}
function setHighlight () {
scope.loading = true;
$timeout(function () {
// The element to highlight
var highlightElement = angular.element(scope.highlightElement);
if(highlightElement && highlightElement.length > 0) {
var offset = highlightElement.offset();
var width = highlightElement.outerWidth();
var height = highlightElement.outerHeight();
// Rounding numbers
var topDistance = offset.top.toFixed();
var topAndHeight = (offset.top + height).toFixed();
var leftDistance = offset.left.toFixed();
var leftAndWidth = (offset.left + width).toFixed();
// The four rectangles
var rectTop = el.find(".umb-backdrop__rect--top");
var rectRight = el.find(".umb-backdrop__rect--right");
var rectBottom = el.find(".umb-backdrop__rect--bottom");
var rectLeft = el.find(".umb-backdrop__rect--left");
// Add the css
scope.rectTopCss = { "height": topDistance, "left": leftDistance + "px", opacity: scope.backdropOpacity };
scope.rectRightCss = { "left": leftAndWidth + "px", "top": topDistance + "px", "height": height, opacity: scope.backdropOpacity };
scope.rectBottomCss = { "height": "100%", "top": topAndHeight + "px", "left": leftDistance + "px", opacity: scope.backdropOpacity };
scope.rectLeftCss = { "width": leftDistance, opacity: scope.backdropOpacity };
// Prevent interaction in the highlighted area
if(scope.highlightPreventClick) {
var preventClickElement = el.find(".umb-backdrop__highlight-prevent-click");
preventClickElement.css({ "width": width, "height": height, "left": offset.left, "top": offset.top });
}
}
scope.loading = false;
});
}
function resize() {
setHighlight();
}
events.push(scope.$watch("highlightElement", function (newValue, oldValue) {
if(!newValue) {return;}
if(newValue === oldValue) {return;}
setHighlight();
}));
$(window).on("resize.umbBackdrop", resize);
scope.$on("$destroy", function () {
// unbind watchers
for (var e in events) {
events[e]();
}
$(window).off("resize.umbBackdrop");
});
onInit();
}
var directive = {
transclude: true,
restrict: "E",
replace: true,
templateUrl: "views/components/application/umb-backdrop.html",
link: link,
scope: {
backdropOpacity: "=?",
highlightElement: "=?",
highlightPreventClick: "=?",
disableEventsOnClick: "=?",
}
};
return directive;
}
angular.module("umbraco.directives").directive("umbBackdrop", BackdropDirective);
})();
@@ -0,0 +1,109 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbDrawer
@restrict E
@scope
@description
The drawer component is a global component and is already added to the umbraco markup. It is registered in globalState and can be opened and configured by raising events.
<h3>Markup example - how to open the drawer</h3>
<pre>
<div ng-controller="My.DrawerController as vm">
<umb-button
type="button"
label="Toggle drawer"
action="vm.toggleDrawer()">
</umb-button>
</div>
</pre>
<h3>Controller example - how to open the drawer</h3>
<pre>
(function () {
"use strict";
function DrawerController(appState) {
var vm = this;
vm.toggleDrawer = toggleDrawer;
function toggleDrawer() {
var showDrawer = appState.getDrawerState("showDrawer");
var model = {
firstName: "Super",
lastName: "Man"
};
appState.setDrawerState("view", "/App_Plugins/path/to/drawer.html");
appState.setDrawerState("model", model);
appState.setDrawerState("showDrawer", !showDrawer);
}
}
angular.module("umbraco").controller("My.DrawerController", DrawerController);
})();
</pre>
<h3>Use the following components in the custom drawer to render the content</h3>
<ul>
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerView}</li>
<li>{@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}</li>
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerContent}</li>
<li>{@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}</li>
</ul>
@param {string} view (<code>binding</code>): Set the drawer view
@param {string} model (<code>binding</code>): Pass in custom data to the drawer
**/
function Drawer($location, $routeParams, helpService, userService, localizationService, dashboardResource) {
return {
restrict: "E", // restrict to an element
replace: true, // replace the html element with the template
templateUrl: 'views/components/application/umbdrawer/umb-drawer.html',
transclude: true,
scope: {
view: "=?",
model: "=?"
},
link: function (scope, element, attr, ctrl) {
function onInit() {
setView();
}
function setView() {
if (scope.view) {
//we do this to avoid a hidden dialog to start loading unconfigured views before the first activation
var configuredView = scope.view;
if (scope.view.indexOf(".html") === -1) {
var viewAlias = scope.view.toLowerCase();
configuredView = "views/common/drawers/" + viewAlias + "/" + viewAlias + ".html";
}
if (configuredView !== scope.configuredView) {
scope.configuredView = configuredView;
}
}
}
onInit();
}
};
}
angular.module('umbraco.directives').directive("umbDrawer", Drawer);
@@ -0,0 +1,59 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbDrawerContent
@restrict E
@scope
@description
Use this directive to render drawer content
<h3>Markup example</h3>
<pre>
<umb-drawer-view>
<umb-drawer-header
title="Drawer Title"
description="Drawer description">
</umb-drawer-header>
<umb-drawer-content>
<!-- Your content here -->
<pre>{{ model | json }}</pre>
</umb-drawer-content>
<umb-drawer-footer>
<!-- Your content here -->
</umb-drawer-footer>
</umb-drawer-view>
</pre>
<h3>Use in combination with</h3>
<ul>
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerView}</li>
<li>{@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}</li>
<li>{@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}</li>
</ul>
**/
(function() {
'use strict';
function DrawerContentDirective() {
var directive = {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'views/components/application/umbdrawer/umb-drawer-content.html'
};
return directive;
}
angular.module('umbraco.directives').directive('umbDrawerContent', DrawerContentDirective);
})();
@@ -0,0 +1,58 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbDrawerFooter
@restrict E
@scope
@description
Use this directive to render a drawer footer
<h3>Markup example</h3>
<pre>
<umb-drawer-view>
<umb-drawer-header
title="Drawer Title"
description="Drawer description">
</umb-drawer-header>
<umb-drawer-content>
<!-- Your content here -->
<pre>{{ model | json }}</pre>
</umb-drawer-content>
<umb-drawer-footer>
<!-- Your content here -->
</umb-drawer-footer>
</umb-drawer-view>
</pre>
<h3>Use in combination with</h3>
<ul>
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerView}</li>
<li>{@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}</li>
<li>{@link umbraco.directives.directive:umbDrawerContent umbDrawerContent}</li>
</ul>
**/
(function() {
'use strict';
function DrawerFooterDirective() {
var directive = {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'views/components/application/umbdrawer/umb-drawer-footer.html'
};
return directive;
}
angular.module('umbraco.directives').directive('umbDrawerFooter', DrawerFooterDirective);
})();
@@ -0,0 +1,63 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbDrawerHeader
@restrict E
@scope
@description
Use this directive to render a drawer header
<h3>Markup example</h3>
<pre>
<umb-drawer-view>
<umb-drawer-header
title="Drawer Title"
description="Drawer description">
</umb-drawer-header>
<umb-drawer-content>
<!-- Your content here -->
<pre>{{ model | json }}</pre>
</umb-drawer-content>
<umb-drawer-footer>
<!-- Your content here -->
</umb-drawer-footer>
</umb-drawer-view>
</pre>
<h3>Use in combination with</h3>
<ul>
<li>{@link umbraco.directives.directive:umbDrawerView umbDrawerView}</li>
<li>{@link umbraco.directives.directive:umbDrawerContent umbDrawerContent}</li>
<li>{@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}</li>
</ul>
@param {string} title (<code>attribute</code>): Set a drawer title.
@param {string} description (<code>attribute</code>): Set a drawer description.
**/
(function() {
'use strict';
function DrawerHeaderDirective() {
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/application/umbdrawer/umb-drawer-header.html',
scope: {
"title": "@?",
"description": "@?"
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbDrawerHeader', DrawerHeaderDirective);
})();
@@ -0,0 +1,58 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbDrawerView
@restrict E
@scope
@description
Use this directive to render drawer view
<h3>Markup example</h3>
<pre>
<umb-drawer-view>
<umb-drawer-header
title="Drawer Title"
description="Drawer description">
</umb-drawer-header>
<umb-drawer-content>
<!-- Your content here -->
<pre>{{ model | json }}</pre>
</umb-drawer-content>
<umb-drawer-footer>
<!-- Your content here -->
</umb-drawer-footer>
</umb-drawer-view>
</pre>
<h3>Use in combination with</h3>
<ul>
<li>{@link umbraco.directives.directive:umbDrawerHeader umbDrawerHeader}</li>
<li>{@link umbraco.directives.directive:umbDrawerContent umbDrawerContent}</li>
<li>{@link umbraco.directives.directive:umbDrawerFooter umbDrawerFooter}</li>
</ul>
**/
(function() {
'use strict';
function DrawerViewDirective() {
var directive = {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'views/components/application/umbdrawer/umb-drawer-view.html'
};
return directive;
}
angular.module('umbraco.directives').directive('umbDrawerView', DrawerViewDirective);
})();
@@ -111,30 +111,13 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
scope.userDialog = null;
}
scope.helpClick = function(){
if(scope.userDialog) {
closeUserDialog();
}
if(!scope.helpDialog) {
scope.helpDialog = {
view: "help",
show: true,
close: function(oldModel) {
closeHelpDialog();
}
};
} else {
closeHelpDialog();
}
};
function closeHelpDialog() {
scope.helpDialog.show = false;
scope.helpDialog = null;
}
//toggle the help dialog by raising the global app state to toggle the help drawer
scope.helpClick = function () {
var showDrawer = appState.getDrawerState("showDrawer");
var drawer = { view: "help", show: !showDrawer };
appState.setDrawerState("view", drawer.view);
appState.setDrawerState("showDrawer", drawer.show);
};
scope.sectionClick = function (event, section) {
@@ -149,12 +132,10 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
if (scope.userDialog) {
closeUserDialog();
}
if (scope.helpDialog) {
closeHelpDialog();
}
navigationService.hideSearch();
navigationService.showTree(section.alias);
navigationService.showTree(section.alias);
//in some cases the section will have a custom route path specified, if there is one we'll use it
if (section.routePath) {
@@ -0,0 +1,366 @@
(function () {
'use strict';
function TourDirective($timeout, $http, $q, tourService, backdropService) {
function link(scope, el, attr, ctrl) {
var popover;
var pulseElement;
var pulseTimer;
scope.loadingStep = false;
scope.elementNotFound = false;
scope.model.nextStep = function() {
nextStep();
};
scope.model.endTour = function() {
unbindEvent();
tourService.endTour(scope.model);
backdropService.close();
};
scope.model.completeTour = function() {
unbindEvent();
tourService.completeTour(scope.model).then(function() {
backdropService.close();
});
};
scope.model.disableTour = function() {
unbindEvent();
tourService.disableTour(scope.model).then(function() {
backdropService.close();
});
}
function onInit() {
popover = el.find(".umb-tour__popover");
pulseElement = el.find(".umb-tour__pulse");
popover.hide();
scope.model.currentStepIndex = 0;
backdropService.open({disableEventsOnClick: true});
startStep();
}
function setView() {
if (scope.model.currentStep.view && scope.model.alias) {
//we do this to avoid a hidden dialog to start loading unconfigured views before the first activation
var configuredView = scope.model.currentStep.view;
if (scope.model.currentStep.view.indexOf(".html") === -1) {
var viewAlias = scope.model.currentStep.view.toLowerCase();
var tourAlias = scope.model.alias.toLowerCase();
configuredView = "views/common/tours/" + tourAlias + "/" + viewAlias + "/" + viewAlias + ".html";
}
if (configuredView !== scope.configuredView) {
scope.configuredView = configuredView;
}
} else {
scope.configuredView = null;
}
}
function nextStep() {
popover.hide();
pulseElement.hide();
$timeout.cancel(pulseTimer);
scope.model.currentStepIndex++;
// make sure we don't go too far
if(scope.model.currentStepIndex !== scope.model.steps.length) {
startStep();
// tour completed - final step
} else {
scope.loadingStep = true;
waitForPendingRerequests().then(function(){
scope.loadingStep = false;
// clear current step
scope.model.currentStep = {};
// set popover position to center
setPopoverPosition(null);
// remove backdrop hightlight and custom opacity
backdropService.setHighlight(null);
backdropService.setOpacity(null);
});
}
}
function startStep() {
scope.loadingStep = true;
backdropService.setOpacity(scope.model.steps[scope.model.currentStepIndex].backdropOpacity);
backdropService.setHighlight(null);
waitForPendingRerequests().then(function() {
scope.model.currentStep = scope.model.steps[scope.model.currentStepIndex];
setView();
// if highlight element is set - find it
findHighlightElement();
// if a custom event needs to be bound we do it now
if(scope.model.currentStep.event) {
bindEvent();
}
scope.loadingStep = false;
});
}
function findHighlightElement() {
scope.elementNotFound = false;
$timeout(function () {
// if an element isn't set - show the popover in the center
if(scope.model.currentStep && !scope.model.currentStep.element) {
setPopoverPosition(null);
return;
}
var element = angular.element(scope.model.currentStep.element);
// we couldn't find the element in the dom - abort and show error
if(element.length === 0) {
scope.elementNotFound = true;
setPopoverPosition(null);
return;
}
var scrollParent = element.scrollParent();
// Detect if scroll is needed
if (element[0].offsetTop > scrollParent[0].clientHeight) {
scrollParent.animate({
scrollTop: element[0].offsetTop
}, function () {
// Animation complete.
setPopoverPosition(element);
setPulsePosition();
backdropService.setHighlight(scope.model.currentStep.element, scope.model.currentStep.elementPreventClick);
});
} else {
setPopoverPosition(element);
setPulsePosition();
backdropService.setHighlight(scope.model.currentStep.element, scope.model.currentStep.elementPreventClick);
}
});
}
function setPopoverPosition(element) {
$timeout(function () {
var position = "center";
var margin = 20;
var css = {};
var popoverWidth = popover.outerWidth();
var popoverHeight = popover.outerHeight();
var popoverOffset = popover.offset();
var documentWidth = angular.element(document).width();
var documentHeight = angular.element(document).height();
if(element) {
var offset = element.offset();
var width = element.outerWidth();
var height = element.outerHeight();
// messure available space on each side of the target element
var space = {
"top": offset.top,
"right": documentWidth - (offset.left + width),
"bottom": documentHeight - (offset.top + height),
"left": offset.left
};
// get the posistion with most available space
position = findMax(space);
if (position === "top") {
if (offset.left < documentWidth / 2) {
css.top = offset.top - popoverHeight - margin;
css.left = offset.left;
} else {
css.top = offset.top - popoverHeight - margin;
css.left = offset.left - popoverWidth + width;
}
}
if (position === "right") {
if (offset.top < documentHeight / 2) {
css.top = offset.top;
css.left = offset.left + width + margin;
} else {
css.top = offset.top + height - popoverHeight;
css.left = offset.left + width + margin;
}
}
if (position === "bottom") {
if (offset.left < documentWidth / 2) {
css.top = offset.top + height + margin;
css.left = offset.left;
} else {
css.top = offset.top + height + margin;
css.left = offset.left - popoverWidth + width;
}
}
if (position === "left") {
if (offset.top < documentHeight / 2) {
css.top = offset.top;
css.left = offset.left - popoverWidth - margin;
} else {
css.top = offset.top + height - popoverHeight;
css.left = offset.left - popoverWidth - margin;
}
}
} else {
// if there is no dom element center the popover
css.top = "calc(50% - " + popoverHeight/2 + "px)";
css.left = "calc(50% - " + popoverWidth/2 + "px)";
}
popover.css(css).fadeIn("fast");
});
}
function setPulsePosition() {
if(scope.model.currentStep.event) {
pulseTimer = $timeout(function(){
var clickElementSelector = scope.model.currentStep.eventElement ? scope.model.currentStep.eventElement : scope.model.currentStep.element;
var clickElement = $(clickElementSelector);
var offset = clickElement.offset();
var width = clickElement.outerWidth();
var height = clickElement.outerHeight();
pulseElement.css({ "width": width, "height": height, "left": offset.left, "top": offset.top });
pulseElement.fadeIn();
}, 1000);
}
}
function waitForPendingRerequests() {
var deferred = $q.defer();
var timer = window.setInterval(function(){
// check for pending requests both in angular and on the document
if($http.pendingRequests.length === 0 && document.readyState === "complete") {
$timeout(function(){
deferred.resolve();
clearInterval(timer);
});
}
}, 50);
return deferred.promise;
}
function findMax(obj) {
var keys = Object.keys(obj);
var max = keys[0];
for (var i = 1, n = keys.length; i < n; ++i) {
var k = keys[i];
if (obj[k] > obj[max]) {
max = k;
}
}
return max;
}
function bindEvent() {
var bindToElement = scope.model.currentStep.element;
var eventName = scope.model.currentStep.event + ".step-" + scope.model.currentStepIndex;
var removeEventName = "remove.step-" + scope.model.currentStepIndex;
var handled = false;
if(scope.model.currentStep.eventElement) {
bindToElement = scope.model.currentStep.eventElement;
}
$(bindToElement).on(eventName, function(){
if(!handled) {
unbindEvent();
nextStep();
handled = true;
}
});
// Hack: we do this to handle cases where ng-if is used and removes the element we need to click.
// for some reason it seems the elements gets removed before the event is raised. This is a temp solution which assumes:
// "if you ask me to click on an element, and it suddenly gets removed from the dom, let's go on to the next step".
$(bindToElement).on(removeEventName, function () {
if(!handled) {
unbindEvent();
nextStep();
handled = true;
}
});
}
function unbindEvent() {
var eventName = scope.model.currentStep.event + ".step-" + scope.model.currentStepIndex;
var removeEventName = "remove.step-" + scope.model.currentStepIndex;
if(scope.model.currentStep.eventElement) {
angular.element(scope.model.currentStep.eventElement).off(eventName);
angular.element(scope.model.currentStep.eventElement).off(removeEventName);
} else {
angular.element(scope.model.currentStep.element).off(eventName);
angular.element(scope.model.currentStep.element).off(removeEventName);
}
}
function resize() {
findHighlightElement();
}
onInit();
$(window).on('resize.umbTour', resize);
scope.$on('$destroy', function () {
$(window).off('resize.umbTour');
unbindEvent();
$timeout.cancel(pulseTimer);
});
}
var directive = {
transclude: true,
restrict: 'E',
replace: true,
templateUrl: 'views/components/application/umb-tour.html',
link: link,
scope: {
model: "="
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbTour', TourDirective);
})();
@@ -0,0 +1,35 @@
(function() {
'use strict';
function TourStepDirective() {
function link(scope, element, attrs, ctrl) {
scope.close = function() {
if(scope.onClose) {
scope.onClose();
}
}
}
var directive = {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'views/components/application/umbtour/umb-tour-step.html',
scope: {
size: "@?",
onClose: "&?",
hideClose: "=?"
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbTourStep', TourStepDirective);
})();
@@ -0,0 +1,22 @@
(function() {
'use strict';
function TourStepContentDirective() {
var directive = {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'views/components/application/umbtour/umb-tour-step-content.html',
scope: {
content: "="
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbTourStepContent', TourStepContentDirective);
})();
@@ -0,0 +1,22 @@
(function() {
'use strict';
function TourStepCounterDirective() {
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/application/umbtour/umb-tour-step-counter.html',
scope: {
currentStep: "=",
totalSteps: "="
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbTourStepCounter', TourStepCounterDirective);
})();
@@ -0,0 +1,19 @@
(function() {
'use strict';
function TourStepFooterDirective() {
var directive = {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'views/components/application/umbtour/umb-tour-step-footer.html'
};
return directive;
}
angular.module('umbraco.directives').directive('umbTourStepFooter', TourStepFooterDirective);
})();
@@ -0,0 +1,22 @@
(function() {
'use strict';
function TourStepHeaderDirective() {
var directive = {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: 'views/components/application/umbtour/umb-tour-step-header.html',
scope: {
title: "="
}
};
return directive;
}
angular.module('umbraco.directives').directive('umbTourStepHeader', TourStepHeaderDirective);
})();
@@ -149,7 +149,8 @@ Use this directive to render an umbraco button. The directive can be used to gen
labelKey: "@?",
icon: "@?",
disabled: "=",
size: "@?"
size: "@?",
alias: "@?"
}
};
@@ -1,7 +1,9 @@
(function () {
'use strict';
function ContentEditController($rootScope, $scope, $routeParams, $q, $timeout, $window, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http) {
function ContentEditController($rootScope, $scope, $routeParams, $q, $timeout, $window, $location, appState, contentResource, entityResource, navigationService, notificationsService, angularHelper, serverValidationManager, contentEditingHelper, treeService, fileManager, formHelper, umbRequestHelper, keyboardService, umbModelMapper, editorState, $http, eventsService) {
var evts = [];
//setup scope vars
$scope.defaultButton = null;
@@ -15,21 +17,12 @@
$scope.page.listViewPath = null;
$scope.page.isNew = $scope.isNew ? true : false;
$scope.page.buttonGroupState = "init";
$scope.allowOpen = true;
function init(content) {
var buttons = contentEditingHelper.configureContentEditorButtons({
create: $scope.page.isNew,
content: content,
methods: {
saveAndPublish: $scope.saveAndPublish,
sendToPublish: $scope.sendToPublish,
save: $scope.save,
unPublish: $scope.unPublish
}
});
$scope.defaultButton = buttons.defaultButton;
$scope.subButtons = buttons.subButtons;
createButtons(content);
editorState.set($scope.content);
@@ -42,6 +35,35 @@
});
}
}
evts.push(eventsService.on("editors.content.changePublishDate", function (event, args) {
createButtons(args.node);
}));
evts.push(eventsService.on("editors.content.changeUnpublishDate", function (event, args) {
createButtons(args.node);
}));
// We don't get the info tab from the server from version 7.8 so we need to manually add it
contentEditingHelper.addInfoTab($scope.content.tabs);
}
function createButtons(content) {
var buttons = contentEditingHelper.configureContentEditorButtons({
create: $scope.page.isNew,
content: content,
methods: {
saveAndPublish: $scope.saveAndPublish,
sendToPublish: $scope.sendToPublish,
save: $scope.save,
unPublish: $scope.unPublish
}
});
$scope.defaultButton = buttons.defaultButton;
$scope.subButtons = buttons.subButtons;
}
/** Syncs the content item to it's tree node - this occurs on first load and after saving */
@@ -230,6 +252,13 @@
};
//ensure to unregister from all events!
$scope.$on('$destroy', function () {
for (var e in evts) {
eventsService.unsubscribe(evts[e]);
}
});
}
function createDirective() {
@@ -0,0 +1,174 @@
(function () {
'use strict';
function ContentNodeInfoDirective($timeout, $location, logResource, eventsService) {
function link(scope, element, attrs, ctrl) {
function onInit() {
scope.allowOpen = true;
scope.datePickerConfig = {
pickDate: true,
pickTime: true,
useSeconds: false,
format: "YYYY-MM-DD HH:mm",
icons: {
time: "icon-time",
date: "icon-calendar",
up: "icon-chevron-up",
down: "icon-chevron-down"
}
};
scope.auditTrailOptions = {
"id": scope.node.id
};
// get available templates
scope.availableTemplates = scope.node.allowedTemplates;
// get document type details
scope.documentType = scope.node.documentType;
// load audit trail when on the info tab
eventsService.on("app.tabChange", function (event, args) {
if (args.id === -1) {
loadAuditTrail();
}
});
}
scope.auditTrailPageChange = function (pageNumber) {
scope.auditTrailOptions.pageNumber = pageNumber;
loadAuditTrail();
};
scope.openDocumentType = function (documentType) {
var url = "/settings/documenttypes/edit/" + documentType.id;
$location.path(url);
};
scope.updateTemplate = function (templateAlias) {
// update template value
scope.node.template = templateAlias;
};
scope.datePickerChange = function (event, type) {
if (type === 'publish') {
setPublishDate(event.date.format("YYYY-MM-DD HH:mm"));
} else if (type === 'unpublish') {
setUnpublishDate(event.date.format("YYYY-MM-DD HH:mm"));
}
};
scope.clearPublishDate = function () {
clearPublishDate();
};
scope.clearUnpublishDate = function () {
clearUnpublishDate();
};
function loadAuditTrail() {
scope.loadingAuditTrail = true;
logResource.getPagedEntityLog(scope.auditTrailOptions)
.then(function (data) {
scope.auditTrail = data.items;
scope.auditTrailOptions.pageNumber = data.pageNumber;
scope.auditTrailOptions.pageSize = data.pageSize;
scope.auditTrailOptions.totalItems = data.totalItems;
scope.auditTrailOptions.totalPages = data.totalPages;
setAuditTrailLogTypeColor(scope.auditTrail);
scope.loadingAuditTrail = false;
});
}
function setAuditTrailLogTypeColor(auditTrail) {
angular.forEach(auditTrail, function (item) {
switch (item.logType) {
case "Publish":
item.logTypeColor = "success";
break;
case "UnPublish":
item.logTypeColor = "danger";
break;
default:
item.logTypeColor = "gray";
}
});
}
function setPublishDate(date) {
// update publish value
scope.node.releaseDate = date;
// emit event
var args = { node: scope.node, date: date };
eventsService.emit("editors.content.changePublishDate", args);
}
function clearPublishDate() {
// update publish value
scope.node.releaseDate = null;
// emit event
var args = { node: scope.node, date: null };
eventsService.emit("editors.content.changePublishDate", args);
}
function setUnpublishDate(date) {
// update publish value
scope.node.removeDate = date;
// emit event
var args = { node: scope.node, date: date };
eventsService.emit("editors.content.changeUnpublishDate", args);
}
function clearUnpublishDate() {
// update publish value
scope.node.removeDate = null;
// emit event
var args = { node: scope.node, date: null };
eventsService.emit("editors.content.changeUnpublishDate", args);
}
onInit();
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/content/umb-content-node-info.html',
scope: {
node: "="
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbContentNodeInfo', ContentNodeInfoDirective);
})();
@@ -156,7 +156,7 @@ angular.module('umbraco.directives')
if(els.indexOf(el) >= 0){return;}
// ignore clicks on new overlay
var parents = $(event.target).parents("a,button,.umb-overlay");
var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour");
if(parents.length > 0){
return;
}

Some files were not shown because too many files have changed in this diff Show More