private async Task <string> RelationshipTypeContentPicker(
            ContentPickerFieldSettings contentPickerFieldSettings,
            ISyncNameProvider syncNameProvider)
        {
            //todo: handle multiple types
            string pickedContentType = contentPickerFieldSettings.DisplayedContentTypes[0];

            string?relationshipType = null;

            if (contentPickerFieldSettings.Hint != null)
            {
                Match match = _relationshipTypeRegex.Match(contentPickerFieldSettings.Hint);
                if (match.Success)
                {
                    relationshipType = $"{match.Groups[1].Value}";
                }
            }

            if (relationshipType == null)
            {
                relationshipType = await syncNameProvider !.RelationshipTypeDefault(pickedContentType);
            }

            return(relationshipType);
        }
        public async Task AddRelationship(JObject contentItemField, IDescribeRelationshipsContext parentContext)
        {
            var describeContentItemHelper = _serviceProvider.GetRequiredService <IDescribeContentItemHelper>();

            ContentPickerFieldSettings contentPickerFieldSettings =
                parentContext.ContentPartFieldDefinition !.GetSettings <ContentPickerFieldSettings>();

            JArray?contentItemIdsJArray = (JArray?)contentItemField[ContentItemIdsKey];

            if (contentItemIdsJArray?.HasValues == true)
            {
                string relationshipType = await RelationshipTypeContentPicker(contentPickerFieldSettings, parentContext.SyncNameProvider);

                var sourceNodeLabels = await parentContext.SyncNameProvider.NodeLabels(parentContext.ContentItem.ContentType);

                string pickedContentType            = contentPickerFieldSettings.DisplayedContentTypes[0];
                IEnumerable <string> destNodeLabels = await parentContext.SyncNameProvider.NodeLabels(pickedContentType);

                parentContext.AvailableRelationships.Add(new ContentItemRelationship(sourceNodeLabels, relationshipType, destNodeLabels));

                //todo: do we need each child, or can we just have a hashset of types
                foreach (var nestedItem in contentItemIdsJArray)
                {
                    await describeContentItemHelper.BuildRelationships(nestedItem.Value <string>(), parentContext);
                }
            }
        }
        public ContentPickerFieldGraphSyncerAddSyncComponentsTestsBase()
        {
            Logger = A.Fake <ILogger <ContentPickerFieldGraphSyncer> >();
            ContentFieldGraphSyncer = new ContentPickerFieldGraphSyncer(
                A.Fake <IServiceProvider>());

            ContentPickerFieldSettings = A.Fake <ContentPickerFieldSettings>();

            A.CallTo(() => ContentPartFieldDefinition.GetSettings <ContentPickerFieldSettings>())
            .Returns(ContentPickerFieldSettings);
        }
        private async Task AddSyncComponents(IGraphMergeContext context, JArray?contentItemIdsJArray = null)
        {
            ContentPickerFieldSettings contentPickerFieldSettings =
                context.ContentPartFieldDefinition !.GetSettings <ContentPickerFieldSettings>();

            //todo: use pickedContentSyncNameProvider in RelationshipTypeContentPicker?
            string relationshipType = await RelationshipTypeContentPicker(contentPickerFieldSettings, context.SyncNameProvider);

            //todo: support multiple pickable content types
            string pickedContentType = contentPickerFieldSettings.DisplayedContentTypes[0];

            ISyncNameProvider pickedContentSyncNameProvider = _serviceProvider.GetSyncNameProvider(pickedContentType);

            IEnumerable <string> destNodeLabels = await pickedContentSyncNameProvider.NodeLabels();

            if (contentItemIdsJArray?.HasValues != true)
            {
                context.ReplaceRelationshipsCommand.RemoveAnyRelationshipsTo(
                    relationshipType,
                    destNodeLabels);
                return;
            }

            ContentItem[] foundDestinationContentItems = await GetContentItemsFromIds(
                contentItemIdsJArray,
                context.ContentManager,
                context.ContentItemVersion);

            if (context.ContentItemVersion.GraphReplicaSetName == GraphReplicaSetNames.Preview &&
                foundDestinationContentItems.Count() != contentItemIdsJArray.Count)
            {
                throw new GraphSyncException(
                          $"Missing picked content items. Looked for {string.Join(",", contentItemIdsJArray.Values<string?>())}. Found {string.Join(",", foundDestinationContentItems.Select(i => i.ContentItemId))}. Current merge node command: {context.MergeNodeCommand}.");
            }

            // if we're syncing to the published graph, some items may be draft only,
            // so it's valid to have less found content items than are picked
            //todo: we could also check when publishing and take into account how many we expect not to find as they are draft only

            IEnumerable <object> foundDestinationNodeIds =
                foundDestinationContentItems.Select(ci => GetNodeId(ci !, pickedContentSyncNameProvider, context.ContentItemVersion));

            long ordinal = 0;

            foreach (var item in foundDestinationNodeIds)
            {
                context.ReplaceRelationshipsCommand.AddRelationshipsTo(
                    relationshipType,
                    ContentPickerRelationshipProperties.Concat(new[] { new KeyValuePair <string, object>("Ordinal", ordinal++) }),
                    destNodeLabels,
                    pickedContentSyncNameProvider.IdPropertyName(),
                    item);
            }
        }
        public void GetNavigation(NavigationBuilder builder)
        {
            var workContext = _workContextAccessor.GetContext();
            var httpContext = workContext.HttpContext;

            if (httpContext == null)
            {
                return;
            }

            var queryString = workContext.HttpContext.Request.QueryString;

            string part  = queryString["part"];
            string field = queryString["field"];

            ContentPickerFieldSettings settings = null;

            // if the picker is loaded for a specific field, apply custom settings
            if (!String.IsNullOrEmpty(part) && !String.IsNullOrEmpty(field))
            {
                var definition = _contentDefinitionManager.GetPartDefinition(part).Fields.FirstOrDefault(x => x.Name == field);
                if (definition != null)
                {
                    settings = definition.Settings.GetModel <ContentPickerFieldSettings>();
                }
            }

            if (settings != null && !settings.ShowContentTab)
            {
                return;
            }

            builder.Add(T("Content Picker"),
                        menu => menu
                        .Add(T("Recent Content"), "5", item => item.Action("Index", "Admin", new { area = "Orchard.ContentPicker" }).LocalNav()));
        }
        public async Task <(bool validated, string failureReason)> ValidateSyncComponent(
            JObject contentItemField,
            IValidateAndRepairContext context)
        {
            ContentPickerFieldSettings contentPickerFieldSettings =
                context.ContentPartFieldDefinition !.GetSettings <ContentPickerFieldSettings>();

            string relationshipType = await RelationshipTypeContentPicker(contentPickerFieldSettings, context.SyncNameProvider);

            IRelationship[] actualRelationships = context.NodeWithRelationships.OutgoingRelationships
                                                  .Where(r => r.Type == relationshipType)
                                                  .ToArray();

            var contentItemIds = (JArray)contentItemField[ContentItemIdsKey] !;

            ContentItem[] destinationContentItems = await GetContentItemsFromIds(
                contentItemIds,
                context.ContentManager,
                context.ContentItemVersion);

            //todo: separate check for missing items, before check relationships
            //todo: move into helper?

            //todo: have to allow for published picker referencing draft item
            if (destinationContentItems.Count() != actualRelationships.Length)
            {
                return(false, $"expecting {destinationContentItems.Count()} relationships of type {relationshipType} in graph, but found {actualRelationships.Length}");
            }

            long ordinal = 0;

            foreach (ContentItem destinationContentItem in destinationContentItems)
            {
                //todo: should logically be called using destination ContentType, but it makes no difference atm
                object destinationId = context.SyncNameProvider.GetNodeIdPropertyValue(
                    destinationContentItem.Content.GraphSyncPart, context.ContentItemVersion);

                string destinationIdPropertyName =
                    context.SyncNameProvider.IdPropertyName(destinationContentItem.ContentType);

                var expectedRelationshipProperties =
                    ContentPickerRelationshipProperties.Concat(
                        new[] { new KeyValuePair <string, object>("Ordinal", ordinal++) });

                //todo: we might want to check that all the supplied relationship properties are there,
                // whilst not failing validation if other properties are present?
                (bool validated, string failureReason) = context.GraphValidationHelper.ValidateOutgoingRelationship(
                    context.NodeWithRelationships,
                    relationshipType,
                    destinationIdPropertyName,
                    destinationId,
                    expectedRelationshipProperties);

                if (!validated)
                {
                    return(false, failureReason);
                }

                // keep a count of how many relationships of a type we expect to be in the graph
                context.ExpectedRelationshipCounts.IncreaseCount(relationshipType);
            }

            return(true, "");
        }
        public ActionResult Index(ListContentsViewModel model, PagerParameters pagerParameters, string part, string field, string types)
        {
            var menuItems             = _navigationManager.BuildMenu("content-picker").ToList();
            var contentPickerMenuItem = menuItems.FirstOrDefault();

            if (contentPickerMenuItem == null)
            {
                return(HttpNotFound());
            }

            if (contentPickerMenuItem.Items.All(x => x.Text.TextHint != "Recent Content"))
            {
                // the default tab should not be displayed, redirect to the next one
                var root = menuItems.FirstOrDefault();
                if (root == null)
                {
                    return(HttpNotFound());
                }

                var firstChild = root.Items.First();
                if (firstChild == null)
                {
                    return(HttpNotFound());
                }

                var routeData   = new RouteValueDictionary(firstChild.RouteValues);
                var queryString = Request.QueryString;
                foreach (var key in queryString.AllKeys)
                {
                    if (!String.IsNullOrEmpty(key))
                    {
                        routeData[key] = queryString[key];
                    }
                }

                return(RedirectToRoute(routeData));
            }

            ContentPickerFieldSettings settings = null;

            // if the picker is loaded for a specific field, apply custom settings
            if (!String.IsNullOrEmpty(part) && !String.IsNullOrEmpty(field))
            {
                var definition = _contentDefinitionManager.GetPartDefinition(part).Fields.FirstOrDefault(x => x.Name == field);
                if (definition != null)
                {
                    settings = definition.Settings.GetModel <ContentPickerFieldSettings>();
                }
            }

            if (settings != null && !String.IsNullOrEmpty(settings.DisplayedContentTypes))
            {
                types = settings.DisplayedContentTypes;
            }

            IEnumerable <ContentTypeDefinition> contentTypes;

            if (!String.IsNullOrEmpty(types))
            {
                var rawTypes = types.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList();
                contentTypes = _contentDefinitionManager
                               .ListTypeDefinitions()
                               .Where(x => x.Parts.Any(p => rawTypes.Contains(p.PartDefinition.Name)) || rawTypes.Contains(x.Name))
                               .ToArray();
            }
            else
            {
                contentTypes = GetCreatableTypes(false).ToList();
            }

            var pager = new Pager(_siteService.GetSiteSettings(), pagerParameters);
            var query = Services.ContentManager.Query(VersionOptions.Latest, contentTypes.Select(ctd => ctd.Name).ToArray());

            if (!string.IsNullOrEmpty(model.Options.SelectedFilter))
            {
                var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(model.Options.SelectedFilter);
                if (contentTypeDefinition == null)
                {
                    return(HttpNotFound());
                }

                model.TypeDisplayName = !string.IsNullOrWhiteSpace(contentTypeDefinition.DisplayName)
                                            ? contentTypeDefinition.DisplayName
                                            : contentTypeDefinition.Name;
                query = query.ForType(model.Options.SelectedFilter);
            }

            switch (model.Options.OrderBy)
            {
            case ContentsOrder.Modified:
                query = query.OrderByDescending <CommonPartRecord>(cr => cr.ModifiedUtc);
                break;

            case ContentsOrder.Published:
                query = query.OrderByDescending <CommonPartRecord>(cr => cr.PublishedUtc);
                break;

            case ContentsOrder.Created:
                query = query.OrderByDescending <CommonPartRecord>(cr => cr.CreatedUtc);
                break;
            }

            model.Options.FilterOptions = contentTypes
                                          .Select(ctd => new KeyValuePair <string, string>(ctd.Name, ctd.DisplayName))
                                          .ToList().OrderBy(kvp => kvp.Value);

            var pagerShape         = Services.New.Pager(pager).TotalItemCount(query.Count());
            var pageOfContentItems = query.Slice(pager.GetStartIndex(), pager.PageSize).ToList();
            var list = Services.New.List();

            list.AddRange(pageOfContentItems.Select(ci => Services.ContentManager.BuildDisplay(ci, "SummaryAdmin")));

            foreach (IShape item in list.Items)
            {
                item.Metadata.Type = "ContentPicker";
            }

            var tab = Services.New.RecentContentTab()
                      .ContentItems(list)
                      .Pager(pagerShape)
                      .Options(model.Options)
                      .TypeDisplayName(model.TypeDisplayName ?? "");

            // retain the parameter in the pager links
            RouteData.Values["Options.SelectedFilter"] = model.Options.SelectedFilter;
            RouteData.Values["Options.OrderBy"]        = model.Options.OrderBy.ToString();
            RouteData.Values["Options.ContentsStatus"] = model.Options.ContentsStatus.ToString();

            return(new ShapeResult(this, Services.New.ContentPicker().Tab(tab)));
        }