| I have often been asked about changing the Alerts that are sent from SharePoint when a list item is updated. By far the most frequent is the ability to add a comment or instructions in the email message that is sent. There are many different ways that this request can be accomplished, but I’m including a solution that I consider to be light-weight and easy to understand and customize. Scenario If a user wants to advise another, relying on the alert system is not sufficient. The user may not have subscribed to the list. Even if the user is subscribed, the frequency of their subscription may be daily or weekly. Those frequencies may be acceptable, but there is no way to determine the setting, thus making it difficult to set expectations for a response. The solution I’ve written is designed to send emails to a specific site user with a user-entered message. To avoid confusion with the built-in functionality, I named it “Advise One Another (A1A)” and I’m sure that those of you who know me will know what inspired the name. Solution The core of the solution is an ItemUpdating event receiver and columns added to the list. The receiver will inspect the values of the fields and send an email if appropriate. The receiver will alter the fields values before that are saved, resetting them to blank. This will keep the contents of the message private. List columns At first glance, it would seem that a content type is appropriate. We are defining columns and behavior for a list, which is what content types are for. However, this solution is not designed to be part of an information management hierarchy. Making the list item use a content type for sending emails will obfuscate the true meaning of the item/document. To avoid this issue, the solution will add the necessary columns directly to the list. This solution has the benefit of making these columns available to all items, regardless of the content type. The columns added to the list: | Title | Data Type | | Send Email | Yes/No | | Mail To | User | | CC | User | | Message | Note | I am not putting the fields into the Site Column gallery. I am concerned that end users who are not aware of these technical details will add the columns and expect the email functionality to work. But, as a bonus, I have code that provides some good examples for the SPList object in the server API. Adding the columns Now that we know the necessary columns, we need to get them added to the list. The obvious place for this is a Feature Activated receiver. But to which list should we add the columns? For now, we are going to add the fields to any list based on the Tasks template. Figure 1 shows a sample feature event receiver to discover lists based on the Tasks template and add the columns to those lists. Figure 1 - public override void FeatureActivated(SPFeatureReceiverProperties properties)
- {
- logger = SharePointServiceLocator.GetCurrent().GetInstance<ILogger>();
- configManager =
- SharePointServiceLocator.GetCurrent().GetInstance<IConfigManager>();
-
- try
- {
- logger.TraceToDeveloper(
- "enter BindToTasksEventReceiver::FeatureActivated", logCategory);
- SPWeb web = properties.Feature.Parent as SPWeb;
-
- configManager.SetWeb(web);
- bag = configManager.GetPropertyBag(ConfigLevel.CurrentSPWeb);
-
- // ensure that task lists have the required fields
- foreach (SPList list in web.Lists)
- {
- if (list.BaseTemplate == SPListTemplateType.Tasks)
- {
- logger.TraceToDeveloper(
- String.Format("found list {0}", list.Title), logCategory);
- EnsureColumnnsPresent(list);
- }
- }
- }
- catch (Exception ex)
- {
- logger.TraceToDeveloper(
- ex, "An error occurred", 0,
- SandboxTraceSeverity.Unexpected, logCategory);
- }
- finally
- {
- logger.TraceToDeveloper(
- "exit BindToTasksEventReceiver::FeatureActivated", logCategory);
- }
- }
The identification of the appropriate lists is performed via the BaseTemplate property of the list. The SPListTemplateType enumeration contains entries for the out of the box templates – if you are targeting a custom list template you will need to specify its template id. The EnsureColumsPresent () method does the actual work of adding the columns to the list. There is one major design concern here. Users can create columns thru the browser, and it is possible that they will use the field name that we want. (In fact, if a power user tried to solve this problem before engaging a developer, it is likely that the columns will exist.) Typically, you would specify an Internal or Static name for the column in the XML of the field definition. However, we are not using XML. To add a column to a list you must use the Add() method of the SPFieldCollection class. None of the overrides of the Add method provide for the internal name. The internal name is generated based on the display name, and is returned by the method. So, we need to store the internal name of the fields somewhere. The Event Receiver will need the field name to pull the values. The obvious place to store this is in the property bag of the SPWeb object. I choose; however, to use the Application Settings Manager functionality of the SharePoint Guidance from Microsoft’s patterns and practices group. The guidance also includes helper classes for logging to SharePoint’s unified logging system (ULS). The EnsureColumnsPresent method is shown in Figure 2. Figure 2 - private void EnsureColumnnsPresent(SPList list)
- {
- string key = default(string);
- string fldInternalName = default(string);
-
- foreach (FieldCreationInfo fldInfo in Constants.RequiredFields)
- {
- key = list.ID.ToString() + ":" + fldInfo.ConfigurationKey;
- if (!configManager.ContainsKeyInPropertyBag(key, bag))
- {
- fldInternalName = FieldCreationHelper.CreateField(list, fldInfo);
- logger.TraceToDeveloper(
- String.Format("added field {0}:{1}",
- fldInfo.DisplayName, fldInternalName),
- logCategory);
- configManager.SetInPropertyBag(key, fldInternalName, bag);
- }
- else
- {
- fldInternalName = configManager.GetFromPropertyBag<string>(key, bag);
- logger.TraceToDeveloper(
- String.Format("field exists {0}:{1}",
- fldInfo.DisplayName, fldInternalName),
- logCategory);
- }
- }
- }
Once the feature is activated, the internal names of the fields are stored in configuration, with the list Id prepended to the configuration key. Removing the columns When writing any SharePoint feature, we should handle the case when the feature is deactivated. The general rule that the SharePoint product follows is to remove components, but not content. (Data loss is the biggest no-no.) Our feature has no content, but we should remove the columns from any list to which we added them. Figure 3 shows the FeatureDeactivating code to do just that. Figure 3 - public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
- {
- logger = SharePointServiceLocator.GetCurrent().GetInstance<ILogger>();
-
- try
- {
- logger.TraceToDeveloper(
- "enter BindToTasksEventReceiver::FeatureDeactivating", logCategory);
- SPWeb web = properties.Feature.Parent as SPWeb;
-
- // ensure that task lists have the required fields
- foreach (SPList list in web.Lists)
- {
- if (list.BaseTemplate == SPListTemplateType.Tasks)
- {
- logger.TraceToDeveloper
- (String.Format("found list {0}", list.Title), logCategory);
-
- FieldInternalNames fldNames =
- FieldCreationHelper.GetFieldInternalNamesFromConfiguration(list);
- logger.TraceToDeveloper(
- RemoveColumn(list, fldNames.SendEmailFieldname), logCategory);
- logger.TraceToDeveloper(
- RemoveColumn(list, fldNames.MailToFieldname), logCategory);
- logger.TraceToDeveloper(
- RemoveColumn(list, fldNames.CCFieldname), logCategory);
- logger.TraceToDeveloper(
- RemoveColumn(list, fldNames.MessageFieldname), logCategory);
- FieldCreationHelper.RemoveFieldInternalNamesFromConfiguration(list);
- }
- }
- }
- catch (Exception ex)
- {
- logger.TraceToDeveloper(ex, "An error occurred", 0,
- SandboxTraceSeverity.Unexpected, logCategory);
- }
- finally
- {
- logger.TraceToDeveloper(
- "exit BindToTasksEventReceiver::FeatureDeactivating", logCategory);
- }
- }
-
- private string RemoveColumn(SPList list, string fieldName)
- {
- string results = string.Empty;
- try
- {
- list.Fields.Delete(fieldName);
- results = String.Format("field '{0}' deleted", fieldName);
- }
- catch (ArgumentException)
- {
- results = String.Format("field '{0}' not found", fieldName);
- }
- catch (Exception)
- {
- results = String.Format("field '{0}' cannot be deleted.");
- }
- return results;
- }
Field Creation Helpers The EnsureColumnsPresent method relies on a helper library named FieldCreationHelpers. The first object in this library is FieldCreationInfo. This is a data-only class used to define the required properties of the created field. The CreateField method will actually create the field, correctly attaching any lookup fields to the appropriate list. (The A1A solution does not use lookup fields.) Lastly the library contains GetFieldInternalNamesFromConfiguration. This method encapsulates the call to p&p configuration, returning the existing column names our event receiver will need. Event Receiver Since we are limiting our functionality to a single list type, we can use the Receivers element to attach our event receiver. Our scenario requires that the mail fields should not be saved, so our receiver hooks into the ItemUpdating event. The sequence number is very low, since we want our receiver to run as soon as possible. Any receiver running before ours will have access to the mail fields. The Elements.xml that defines our receiver is listed in Figure 4. Figure 4 - <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
- <Receivers ListTemplateId="107">
- <Receiver>
- <Name>TaskItemSendEmailReceiverItemUpdating</Name>
- <Type>ItemUpdating</Type>
- <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
- <Class>
- Schaeflein.Community.AdviseOneAnother.ItemReceivers.TaskItemSendEmailReceiver
- </Class>
- <SequenceNumber>10000</SequenceNumber>
- </Receiver>
- </Receivers>
- </Elements>
Again, our example attaches to all task lists, so the ListTemplateId attribute is set to the appropriate ListTemplateId. The code for the receiver is pretty straightforward. It extracts the values from the fields, and if necessary sends the message using the SPUtility.SendEmail function. It then sets the AfterProperties to blanks, so that the values are not persisted to the list. One other interesting section of code is the building of the list item display url: string listItemUrl = String.Format("{0}/{1}?ID={2}", properties.WebUrl, properties.List.Forms[PAGETYPE.PAGE_DISPLAYFORM].Url, properties.ListItem.ID);
Using the Forms property of the list will ensure that the default display form is used – even if the forms have been modified via SharePoint Designer.
Next Steps
There are several enhancements to the solution that I can imagine.
I may tackle these in the future, but for now they are left up to you!
The complete solution is available on CodePlex. |
| A while back on the Twitter, I posted a question that started a few ripples. Has SharePoint Saturday jumped the shark? (Millions of parrotheads will tell you that fins to the left are never a bad thing!)
I did not originate the question, but I do admit to giving it a very public position. But I think the time is right for such a discussion. The SharePoint world is growing, there will be another "official" SharePoint conference from Microsoft for the second year in a row, and the SPS movement has matured.
On the "Yes" side of the answer are two complaints I’ve heard often:
- It is a clique and it is the same speakers over and over. Those speakers just want to visit places for free.
- It is all about vendors
I would like to respond with a few questions of my own:
- Do you want to speak? Have you done so before? If a dozen people give up their weekend to come listen to you, will they feel it worthwhile?
- Did you know that local user groups are desperate for speakers, and are great for learning how to prepare and give a presentation?
- Can you afford the travel costs to SPS events? Did you know that you can register in the INETA Regional Speaker program and get reimbursed for travel to user groups?
- Do you work at a company that can provide meeting rooms for free? Do you work at a company that can feed 100 people for free? Do you work at a company that does not need customers and profits?
- Do you want to only take from the community and not give back?
For the record, I think SharePoint Saturday is a great idea. I believe the regional nature of the events should be highlighted, and speakers be from those regions. I spoke at many of the early SPS events, but now only speak occasionally. That does not mean SPS is bad, it is just not conducive to *my* thoughts about community and *my* schedule.
No, SPS has not jumped the shark.
And since I mentioned schedules, my speaking schedule is now posted on this site. (That’s right, I am a capitalist just like every other vendor in the SharePoint ecosystem.) |
| During the holiday season, most technical people get pressed into service as the IT Department for the family. There always is a PC around that is slow or infected with viruses. Scott Hanselman has discussed this topic frequently, with a summary post being my favorite. My Dad’s PC got infected this holiday shopping season. Of course, we can’t pinpoint the exact website, but he thought he was on Amazon. Of course, that is *very* unlikely, but task number one was getting the machine usable. He had Norton on the machine (which had expired), but launching the program would not work. It would immediately abort, most likely because the virus was looking for that executable. Same thing with “the internet protector program I got from AOL” and Microsoft Security Essentials (MSE). So, I booted the PC into safe mode. The incessant pop-ups stopped, but the Security Essentials program would not run either. Same with Norton.  After some Bing-ing, I found the Microsoft Safety Scanner program. A 72KB executable that runs in safe mode and is refreshed periodically to combat the latest issues. Success!!!! The program expires 10 days after download, so is not a replacement for MSE (or any other AV program). But it discovered and removed viruses on the computer, allowing me to boot normally and remove the failing AV/protector programs. And put MSE on and configure its updates. |
| Today’s task required me to check if a column exists on a list, and if not then add it from the site column gallery. Since I knew that SP2010 added a method TryGetList(), I thought I would look around for a similar method for fields. Sure enough, I found TryGetFieldByStaticName() on the SPFieldCollection class. This is exactly what I was looking for. Why it uses the StaticName property instead of the DisplayName (like the Item method does) or the Internal Name (like CAML does) is totally beyond me. But at least I can check for a field without anticipating an exception. After hours of digging and bothering people on IM and the twitter, I discovered that TryGetFieldByStaticName() works as you would expect when the SPFieldCollection is based on an SPList object. However, the exact same method based on an SPWeb object (like the site columns gallery) always returns null! Here is a snippet that illustrates the problem: TryGetFieldByStaticName sample - private void AddColumnIfNotPresentOnList(SPList list, string columnName)
- {
- SPField listField = null;
- SPField siteField = null;
-
- listField = list.Fields.TryGetFieldByStaticName(columnName);
- if (listField == null)
- {
- // *** this line always returns null ***
- siteField = list.ParentWeb.Site.RootWeb.Fields.TryGetFieldByStaticName(columnName);
-
- // had to use the following instead...
- try
- {
- siteField = list.ParentWeb.Site.RootWeb.Fields[columnName];
- }
- catch (Exception)
- {
- // yes, I shudder when writing this code...
- }
- if (siteField != null)
- {
- list.Fields.Add(siteField);
- }
- }
- }
** NOTE ** The above is an illustration, not working code. |
| I had an interesting problem to solve today. Given a set of Terms entered in the TaxonomyWebTaggingControl, I had to determine which already existed in a collection. The IEnumerable.Contains() method can do this, but by default this will compare the object references. That is not quite what we want. The interesting part is that the Contains method has an override that accepts an System.Collections.IEqualityComparer. This gives us the ability to define what "equal" means. For a Term in the Metadata server TermStore, this would typically be the ID. But, what if you are copying values between services (say, from production to QA)? Then perhaps equality is simply the name of the term. To help with these scenarios, I wrote an equality Comparer that can determine if two terms are equal. TermEqualityComparer class - using System;
-
- public class TermEqualityComparer : IEqualityComparer<Term>
- {
- public TermEqualityComparer() { }
-
- private CompareProperty _propToCompare = CompareProperty.Id;
- public TermEqualityComparer(CompareProperty propertyToCompare)
- {
- _propToCompare = propertyToCompare;
- }
-
- // Terms are equal if their names and ids are equal.
- public bool Equals(Term x, Term y)
- {
- //Check whether the compared objects reference the same data.
- if (Object.ReferenceEquals(x, y)) return true;
-
- //Check whether any of the compared objects is null.
- if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
- return false;
-
- //Check whether the terms' properties are equal.
- bool results = false;
- switch (_propToCompare)
- {
- case CompareProperty.Name:
- results = x.Name == y.Name;
- break;
-
- case CompareProperty.Id:
- results = x.Id == y.Id;
- break;
-
- case CompareProperty.NameAndId:
- results = x.Id == y.Id && x.Name == y.Name;
- break;
- }
- return results;
- }
-
- // If Equals() returns true for a pair of objects
- // then GetHashCode() must return the same value for these objects.
- public int GetHashCode(Term term)
- {
- //Check whether the object is null
- if (Object.ReferenceEquals(term, null))
- {
- return 0;
- }
- else
- {
- int results = default(int);
- switch (_propToCompare)
- {
- case CompareProperty.Name:
- results = term.Name.GetHashCode();
- break;
- case CompareProperty.Id:
- results = term.Id.ToString().GetHashCode();
- break;
- case CompareProperty.NameAndId:
- break;
- }
- return term.Name.GetHashCode();
- }
- }
-
- public enum CompareProperty
- {
- Name,
- Id,
- NameAndId
- }
- }
By default, the class will compare only on ID. To change this behavior, use the overloaded constructor passing the appropriate enumeration value: TermEqualityComparer example - TermEqualityComparer teq = new TermEqualityComparer(CompareProperty.Name);
- if (termCollection.Contains(term, teq))
- {
- // collection contains term...
- }
|
| I came across a cool tool for use in helping set up the claims rules in ADFS: Claims Grabber Looks like a great addition to the toolset for troubleshooting/installing SharePoint+Claims |
| I know that a lot has been written about the setup of a development environment for SharePoint 2010. I want to put my thoughts down in a single location – because I know that I will find it helpful in the future. These items are not in any particular order. - Install SQL Server before going any further. Even if you install the Express edition, that is better than just using the built in database. Why? Because it gives you the flexibility to swap out the database edition/instance/server in the future. (a SQL Server alias can be helpful in this case.)
- Follow the best practices for accounts. How will you know if your code fails with a permission issue if everything runs as domain administrator?
- Install using a script. Yes, virtual machine snapshots can save time. But if you have a script, you can be sure every developer has the same environment. Even better – run the script that your ITPro used on production!
- Look at the service applications. Do you really need the Excel, Word and Visio services running? I didn’t think so.
- Install ULS viewer. Then, make sure that your code writes diagnostic, warning and error information to those logs. The SharePoint Guidance from patterns & practices has code to make this brain-dead simple.
- Install extensions for Visual Studio from the gallery. There are lots to choose from – I install the Productivity Power Tools, SharePoint Power tools and CKSDEV
|
| In my TechEd NA session on claims, I demonstrated a web part that simply rendered all the claims of the current user. I intended to replicate the code in a WCF service. However, I have crossed that off my list. Dominick Baier of DevelopMentor has posted a service to echo the claims as part of a larger series on ADFS2, WIF and WCF. Recommended. |
| Have you ever noticed the security paradox in computers? Most people will agree that managing Active Directory or LDAP directories is an administrative task. Not necessarily the password resets and account create (those tend to get pushed to the help desk), but the installation, configuration and general maintenance of these vital data stores is performed by the top-of-the-heap admin. (Rightly so!) However, if the authentication provider is not one of these, say an ASP.NET Membership provider (FBA), then it usually falls to the developer to configure the solution. It seems short sighted to me that the person charged with monitoring the system would wash their hands of the technology used to secure it! But, I’m not here today to change this activity. No, I am writing about a wonderful little utility that developers and admins alike can use to get up to speed on the new claims-based identity world! First, a little background. To setup claims authentication with SharePoint, you need to follow the directions on TechNet: Configure authentication using a SAML security token (SharePoint Server 2010). The big stumbling point for those just getting started – where do I find a Security Token Service Web application? The answer is either ADFS2 or the WIF SDK. Well, now you have a third, and much easier option - SelfSTS. SelfSTS is a security token service that is designed to issue tokens without a lot of configuration. For a full explanation, refer to Vittorio’s blog post. (If you are not subscribed to Vittorio’s blog, then you are not serious about claims auth!) To get SelfSTS working with SharePoint, there are a few little tweaks required outside of the steps laid out by TechNet and Vittorio. The steps below are copied and adjusted from the TechNet directions. - Extract SelfSTS and run the executable from the bin\Release directory.
- Save the X509 Certificate that SelfSTS is using
- In the browser, navigate to the federation metadata page. (Be sure to start SelfSTS first. Otherwise, you will get a DNS error.) Hint: You can copy the url of the page from the SelfSTS UI. Click the "C" next to the link.
- In the metadata page, look for a tag named X509Certificate. Copy the inner text from any of these tags and paste it into Notepad.
- Save the file in Notepad. I put mine in the SelfSTS bin\Release folder with the name SelfSTS.cer
- In the SharePoint Management Console (PowerShell), read the certificate into a variable.
$cert = New-Object
System.Security.Cryptography.X509Certificates.X509Certificate2("path to cert file")
- Map the claim that SelfSTS provides to one that is understood by SharePoint
$map1 = New-SPClaimTypeMapping "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" `
-IncomingClaimTypeDisplayName "EmailAddress" -SameAsIncoming
- The Realm setting is used by the STS to identify the source of the authentication request. In ADFS2, there is a user interface for mapping this identifier with the url of the application. However, SelfSTS does not have such a UI. Instead, it will automatically redirect to the value provided as the realm. So, this must be the url of the SharePoint claim service.
$realm = "http://[WebApplicationUrl]/_trust/"
- You will need to tell SharePoint the url of the identity provider. This is the endpoint in SelfSTS. (I’ve listed the default value below.)
$signinurl = "http://localhost:8000/STS/Issue/"
- Now, the trusted login provider can be created as shown in the TechNet article.
New-SPTrustedIdentityTokenIssuer -Name
"SelfSTS" -Description "SelfSTS sample" -Realm $realm -ImportTrustCertificate `
$cert -ClaimsMappings $map1 -SignInUrl $signinurl -IdentifierClaim $map1.InputClaimType
Upon completion of these steps, the SelfSTS provider will be available for selection in the Trusted Identity Provider section when creating a new Web Application or modifying the Authentication Providers of an existing claims-based web application.
Before attempting to login to a site collection using SelfSTS, there are a few other items to address. The token signing certificate used by SelfSTS must be trusted by SharePoint. This is accomplished by the New-SPTrustedRootAuthority cmdlet in PowerShell, or via the Manage Trust link in the Security section of Central Administration. Also, be sure to grant permissions to the account provided by SelfSTS (or the all users account).
Again, the key differences when using SelfSTS is the Realm property, and that https is not required. (You are not leaving the box, so no need to incur that overhead.) Lastly, I want to point out that THIS IS FOR DEVELOPMENT PURPOSES ONLY. You should never use SelfSTS in production.
Update: Works fine under Windows 7.
|
| Introduction
The out-of-the-box Image Web Part can be used to display an image on a site. The web part has a property that specifies the web address of the image. For most site members, this is problematic – how do they get an image on the web site? And how do they determine the web address? For power users, these steps are understood, but are time consuming to complete.
The Image Upload Web Part will allow the site member to browse their local computer for the image. Once the image is selected, the web part will automatically upload the image to a location specified by an administrator and set the web address.
The result is a solution that works for both groups. Site members can display pictures from their computer and administrators can provide storage for those pictures with changing the permissions of their site and with minimal training.
More information available in the download or at the project's page on CodePlex.
ImageUploadv3.zip (.16 KB) |
Compliance Details javascript:commonShowModalDialog('{SiteUrl}/_layouts/itemexpiration.aspx?ID={ItemId}&List={ListId}', 'center:1;dialogHeight:500px;dialogWidth:500px;resizable:yes;status:no;location:no;menubar:no;help:no', function GotoPageAfterClose(pageid){if(pageid == 'hold') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/hold.aspx?ID={ItemId}&List={ListId}'); return false;} if(pageid == 'audit') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/Reporting.aspx?Category=Auditing&backtype=item&ID={ItemId}&List={ListId}'); return false;} if(pageid == 'config') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/expirationconfig.aspx?ID={ItemId}&List={ListId}'); return false;}}, null); return false; 0x0 0x1 ContentType 0x01 898 |
|
|
|
|
|
Compliance Details javascript:commonShowModalDialog('{SiteUrl}/_layouts/itemexpiration.aspx?ID={ItemId}&List={ListId}', 'center:1;dialogHeight:500px;dialogWidth:500px;resizable:yes;status:no;location:no;menubar:no;help:no', function GotoPageAfterClose(pageid){if(pageid == 'hold') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/hold.aspx?ID={ItemId}&List={ListId}'); return false;} if(pageid == 'audit') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/Reporting.aspx?Category=Auditing&backtype=item&ID={ItemId}&List={ListId}'); return false;} if(pageid == 'config') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/expirationconfig.aspx?ID={ItemId}&List={ListId}'); return false;}}, null); return false; 0x0 0x1 ContentType 0x01 898 |
|
|
Compliance Details javascript:commonShowModalDialog('{SiteUrl}/_layouts/itemexpiration.aspx?ID={ItemId}&List={ListId}', 'center:1;dialogHeight:500px;dialogWidth:500px;resizable:yes;status:no;location:no;menubar:no;help:no', function GotoPageAfterClose(pageid){if(pageid == 'hold') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/hold.aspx?ID={ItemId}&List={ListId}'); return false;} if(pageid == 'audit') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/Reporting.aspx?Category=Auditing&backtype=item&ID={ItemId}&List={ListId}'); return false;} if(pageid == 'config') {STSNavigate(unescape(decodeURI('{SiteUrl}'))+'/_layouts/expirationconfig.aspx?ID={ItemId}&List={ListId}'); return false;}}, null); return false; 0x0 0x1 ContentType 0x01 898 |
|
|