Orchard Module only shows Media Library items to logged in users - orchardcms

I created a very simple module that displays images in a specific folder in the Media Library. It works great when I'm logged in to my Orchard site, but the images are not displayed to anonymous users. Here is a simplified version of my module code:
public ActionResult Image(int id, int? width, int? height) {
var items = _mediaLibraryService
.GetMediaContentItems("Portfolio", 0, Int32.MaxValue, null, null);
var mediaItem = items.Where(i => i.Id == id).SingleOrDefault();
if (mediaItem == null) {
return null;
}
string imageFileName = Server.MapPath(mediaItem.MediaUrl);
string contentType = "image/" + Path.GetExtension(imageFileName).Substring(1);
// code to resize image based on given parameters ...
return File(imageFileName, contentType);
}
The action accepts the MediaPart id and dimensions, finds the item by id, resizes the image, and returns it as a FileResult. This works fine when logged in, but anonymous users get nothing. Looking in the network tab in Chrome, it shows that the response length is 0.
Of course, I don't get this behavior when testing locally, only on my live site. Any ideas?

Related

Open GI on button click

I have created a button placed on PO line items grid and a new GI. I need to open these GI and automatically pass PO Order number as a parameter to GI.
I have written below code in button event handler. However, it is opening GI inside the inner frame (see screenshot) instead of in the main window.
public PXAction<POOrder> viewFullSODemandGI;
[PXButton()]
[PXUIField(DisplayName = "View Full SO Demand", MapEnableRights = PXCacheRights.Insert, MapViewRights = PXCacheRights.Insert)]
protected virtual IEnumerable ViewFullSODemandGI(PXAdapter adapter)
{
var poOrderNbr = string.Empty;
foreach (POOrder current in adapter.Get<POOrder>())
{
poOrderNbr = current.OrderNbr;
}
var sURL = PXUrl.ToAbsoluteUrl(HttpUtility.UrlPathEncode(string.Format("~/?CompanyID=Company&ScreenId=GI000092&POOrderNumber={0}", poOrderNbr.Trim())));
throw new PXRedirectToUrlException(sURL, PXBaseRedirectException.WindowMode.New, false, null);
}
Please suggest.
I guess the biggest difference between the 2 approaches (one suggested by #Brendan and the other one originally used by #Krunal) is how URL is composed:
#Brendan suggests a relative URL
#Krunal composed an absolute URL
I had exact same result as #Krunal with absolute URLs. However with the relative URL composed by either of code snippets below, the task was successfully achieved:
using GI name (Inquiry Title):
string inqName = "InvoicedItems";
var url = new StringBuilder(PXGenericInqGrph.INQUIRY_URL).Append("?name=").Append(inqName).ToString();
throw new PXRedirectToUrlException(url, PXBaseRedirectException.WindowMode.New, true, null);
using Generic Inquiry ID (Guid of a GI from database):
string inqID = "6b267dbb-0ff2-49b2-b005-355c544daba3";
var url = new StringBuilder(PXGenericInqGrph.INQUIRY_URL).Append("?id=").Append(inqID).ToString();
throw new PXRedirectToUrlException(url, PXBaseRedirectException.WindowMode.New, true, null);
It's also worth checking the PXRedirectToGIRequiredException:
using GI name (Inquiry Title) with a parameter (SalespersonID):
string inqName = "SalespersonSales&SalespersonID=SP0003";
throw new PXRedirectToGIRequiredException(inqName, PXBaseRedirectException.WindowMode.New, true);
using Generic Inquiry ID (Guid of a GI from database):
Guid inqID = Guid.Parse("6b267dbb-0ff2-49b2-b005-355c544daba3");
throw new PXRedirectToGIRequiredException(inqID, PXBaseRedirectException.WindowMode.New, true);
Both samples for the PXRedirectToGIRequiredException can absolutely assign values to GI parameters.
Change your window mode. I think you want to use InlineWindow. Also you can use the url just like this...
throw new PXRedirectToUrlException(
string.Format(#"~/GenericInquiry/GenericInquiry.aspx?ID=47842ccc-aa5d-4840-9d4a-7642cbf34cbe&POOrderNumber={0}", poOrderNbr.Trim()),
BaseRedirectException.WindowMode.InlineWindow,
string.Empty);
Tested and loads in the current window without the menu extras.
I typically call Generic inquiries using the GI's guid "ID" vs screen ID as the screen ID might be different between Acumatica instances. You can get the ID from the export of the GI as the row "DesignID" value in the XML. Replace the ID value i have in the example with your GI DesignID value.

How to set different page size for first page in Orchard CMS

We got the need to display a blog posts page that display X posts - first post is displayed as a header and the rest are in 2 columns. The page has a show more button at the bottom that fetches the next page posts using ajax and adding them at the bottom.
Is it possible to get X+1 items for the subsequent pages?
Any hint, even in code are welcome since we use a sourced version of orchard installation.
So before cluttering the comments above this is my proposed solution.
I think there was a slight misunderstanding about changing the controller action which I'd like to clarify (I hope I understood everything correctly now):
Orchard.Blogs | BlogController | Item Action
public ActionResult Item(int blogId, PagerParameters pagerParameters) {
// This is all original code
Pager pager = new Pager(_siteService.GetSiteSettings(), pagerParameters);
var blogPart = _blogService.Get(blogId, VersionOptions.Published).As<BlogPart>();
if (blogPart == null)
return HttpNotFound();
if (!_services.Authorizer.Authorize(Orchard.Core.Contents.Permissions.ViewContent, blogPart, T("Cannot view content"))) {
return new HttpUnauthorizedResult();
}
// This is the actual change:
// Use the pagerParameters provided, otherwise fall back to the blog settings
pager.PageSize = pagerParameters.PageSize.HasValue ? pager.PageSize : blogPart.PostsPerPage;
// This is all original code again
_feedManager.Register(blogPart, _services.ContentManager.GetItemMetadata(blogPart).DisplayText);
var blogPosts = _blogPostService.Get(blogPart, pager.GetStartIndex(), pager.PageSize) // Your new page size will be used
.Select(b => _services.ContentManager.BuildDisplay(b, "Summary"));
dynamic blog = _services.ContentManager.BuildDisplay(blogPart);
var list = Shape.List();
list.AddRange(blogPosts);
blog.Content.Add(Shape.Parts_Blogs_BlogPost_List(ContentItems: list), "5");
var totalItemCount = _blogPostService.PostCount(blogPart);
blog.Content.Add(Shape.Pager(pager).TotalItemCount(totalItemCount), "Content:after");
return new ShapeResult(this, blog);
}
So the change is very subtle, but this way I would configure the blogs default pageSize to 7 items and for every subsequent Ajax-Request I'd provide a "pageSize"-Parameter with the desired size.

Trying to hide Content Parts on Content Type

I am building Content Types and adding Content Parts specific to a Client and Attorney. All of these parts have fields and/or content pickers, etc.
I want to restrict the Client Role to see only Client Content Parts, while I just allow the Attorney Role to see any Content Parts, including it's own Attorney Content Part for a particular Content Type. Again, these are all on the same Content Type, so Content Permissions will not work (except on the Content Type in general).
I want to hide the Attorney Content Parts when a Client is logged on.
I have tried using this:
public override void Displaying(ShapeDisplayingContext context)
{
context.ShapeMetadata.OnDisplaying(displayedContext => {
var shape = context.Shape;
if (context.Shape.Part.Name == "Parts_AttorneyMatterPart")
{
var workContext = _workContextAccessor.GetContext();
var user = workContext.CurrentUser;
var roles = user.As<UserRolesPart>().Roles;
if (!roles.Contains("Spaces Attorney"))
{
shape = null;
}
}
});
}
Where I have a Content Part named "AttorneyMatterPart", and where the Attorney Role is "Spaces Attorney".
These Content Types and Parts were all created in the Orchard Admin. The only thing in my module is this class file.
But this won't hide the Content Part when the Client is logged in. I know that I have to work on the logic of what roles can see things (going to add || conditions for Admin, etc.). For now I am just testing this out.
Any help is appreciated.
EDIT (Bounty Added)
I am really stumped as to whether or not this is even possible. This Content Part is created through the Admin UI. Under shape tracing I can see under the "Content" zone Model > ContentItem > AttorneyMatterPart. I have tried ShapeTableBuilder and I have tried OnDisplaying and OnDisplayed from a ShapeDisplayingContext.
If someone could provide a working sample it would be much appreciated.
When a content part is created through the admin dashboard, there isn't really a shape to render it, only individual shapes for inner content fields...
So, try this
public override void Displaying(ShapeDisplayingContext context) {
context.ShapeMetadata.OnDisplaying(displayedContext => {
var shape = displayedContext.Shape;
if (shape.ContentPart != null
&& shape.ContentPart.PartDefinition.Name == "PartName") {
var workContext = _workContextAccessor.GetContext();
var user = workContext.CurrentUser;
if (user == null || !user.Has<UserRolesPart>()
|| !user.As<UserRolesPart>().Roles.Contains("RoleName")) {
displayedContext.ChildContent = new System.Web.HtmlString("");
}
}
});
}
See my answer on OrchardPros
http://orchardpros.net/tickets/6914
Best
Nulling the shape variable will just clear the local reference. Setting the following however should hide the shape:
displayedContext.ShapeMetadata.Position = "-";
Also FYI it's better not to check on roles the user have but rather create a custom permission, add that to the user role and then check for the permission through
IAuthorizationService.TryCheckAccess()

In Orchard, how to query based on current user's content picker field?

I have extended the built-in User ContentType with a Content Picker Field that can be used to select multiple Video ContentItems. This gives me a video multi-picker control on the Edit page of each User.
I love how Orchard CMS makes this so elegantly simple to setup.
Now that I can associate multiple Videos with a User, I'd like to create a Query that will display just the Videos that the currently logged in User has been granted access.
I was hoping to be able to setup a Query using the Projector module, in what I thought was the obvious way (see below), but this returns no results.
This is how I configured the second filter:
Clicked on the + Add a new Filter link on the Edit Query screen
Chose Videos:Ids from the User Content Fields section, like this:
Configured the new filter like this:
What am I doing wrong, or what is the simplest way of diagnosing this issue?
This is how the Content Picker field is defined:
I have spotted my error - it was due to me not having a proper understand of how the filters worked. The Videos:Ids filter in the User Content Fields section does not give access to the current user's list of videos, as I assumed. Instead, it is offering the field to be used in the filter, which would be useful if I were to write a query to produce a list of Users that had access to a specific Video.
It was wishful thinking that it worked the way I wanted, but it's obvious in retrospect how it actually works.
Update: in the hope it's useful for others, here's the custom filter I developed:
public interface IFilterProvider : IEventHandler
{
void Describe(dynamic describe);
}
public class CurrentUserVideosFilter : IFilterProvider
{
private readonly IWorkContextAccessor _workContextAccessor;
public CurrentUserVideosFilter(IWorkContextAccessor workContextAccessor)
{
_workContextAccessor = workContextAccessor;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public void Describe(dynamic describe)
{
describe.For("My Filter Category", T("My Filter Category"), T("My Filter Category"))
.Element("Current User's Videos", T("Current User's Videos"), T("Current User's Videos"),
(Action<dynamic>)ApplyFilter,
(Func<dynamic, LocalizedString>)DisplayFilter,
null
);
}
public void ApplyFilter(dynamic context)
{
var query = (IHqlQuery)context.Query;
context.Query = query.ForType("Video")
.Where(x => x.ContentPartRecord<IdentityPartRecord>(), x => x.InG("Id", GetVideoIdsForCurrentUser()));
}
private IList<int> GetVideoIdsForCurrentUser()
{
var currentUser = _workContextAccessor.GetContext().CurrentUser;
if (currentUser == null) return new int[0];
dynamic item = currentUser.ContentItem;
var videoContentItems = (IEnumerable<ContentItem>)item.User.Videos.ContentItems;
return videoContentItems.Select(i => i.Id).ToList();
}
public LocalizedString DisplayFilter(dynamic context)
{
return T("Videos that have been assigned to the currently logged in user");
}
}
I created this class in a new Orchard module, which contains all my customisations for the site I'm building. Once I installed the module, the filter was immediately available. I assume Orchard uses reflection to seek out all types that implement the IFilterProvider interface.
This is how the filter appears on the Add a Filter screen:
Clicking on the filter shows this screen:
Once the filter has been saved, the query works exactly how I'd like - it shows all videos that have been assigned to the currently logged in user.

Extension Library Name Picker gets local Names.nsf even though DB is on server

I have a Name Picker on an XPage with a dataProvider dominoNABNamePicker with the addressBookSel = all-public and people and groups = true. With the database on a Domino server using the Notes Client it displays my local Names.nsf. If I open the DB in a brouse it selects the correct names.nsf from the server.
Can't figure out if this is the result of settings in my client, the server or XPages. Does the same thing on two different PC's.
I would think that the all-public would force it to open only public NABs but it does not appear so.
I asked the same question myself.
The answer, in the control add
addressBookDb="SERVER!!names.nsf"
From here.
Can I have the extlib name picker running in xPINC lookup the directory on the server?
After a fair bit of frustration I have this working for the Notes Client and the Web Client. Perhaps this is obvious to most of you, but it sure wasn't to me.
First on the Name Picker I created a namePickerAggregator. Then I added a dominoNABNamePicker
in the addressBookDb I put the following SSJS:
var server:String = #Name("[CN]",session.getCurrentDatabase().getServer());
var allNABs:Array = session.getAddressBooks().iterator();
var pubNABs = new Array;
var privNABs = new Array;
while (allNABs.hasNext()) {
var db:NotesDatabase = allNABs.next();
if (db.isPublicAddressBook()){
pubNABs.push(db.getFileName())
} else {
privNABs.push(db.getFileName())
}
db.recycle()
}
if (pubNABs[0] == ""){
return privNames[0];
break;
} else {
return server + "!!" + pubNABs[0];
break
}
I then added a second dominoNABNamePicker with the same block of code except the return is
if (pubNABs[1] != "") {
return server + "!!" + pubNABs[1];
break;
} else {
return "";
}
This code works for both the Notes Client and the Web client so I'm now a happy camper, unless I find a gotcha somewhere.
Here is what I eventually did. I set a limit on the maximum number of address books (not great but it works) of 4 you can create as many as you want. So I created a couple of sessionScope variable that I created in the after Page Loads event on the XPage. I used this formula.
var allNABs:Array = session.getAddressBooks().iterator();
var pubNABs = new Array;
var privNABs = new Array;
while (allNABs.hasNext()) {
var db:NotesDatabase = allNABs.next();
if (db.isPublicAddressBook()){
pubNABs.push(db.getFilePath())
} else {
privNABs.push(db.getFilePath())
}
db.recycle()
}
sessionScope.put("ssPublicNABs", pubNABs);
sessionScope.put("ssPrivateNABs", privNABs);
because I use several different Name Pickers on the same page I did not want to repeat having to cycle through the Address books.
Then I created 4 NamePicker controls and added 1, 2 , 3 and 4 dominoNABNamePickers providers to each of the successive controls. Then set the rendered property based on the number of public Address books so they would not blow up on me. The db name property on each of the providers is :
var server:String = #Name("[CN]",session.getCurrentDatabase().getServer());
var pubNABs:Array = sessionScope.get("ssPublicNABs");
return server + "!!" + pubNABs[0];
where pubNABs[n] returns the correct filePath for the NAB. It works well in both Notes Client and the Web.
Then to make it not blow up on a local disconnected replica I created 4 more controls and did the same thing but used the privNABs with appropriate rendered properties so that there is no conflict. Seems like the long way around and I'm sure that there is an easier way, but it works.

Resources