예제 #1
0
        public IEnumerable <ViewDto> ViewUsage(int appId, Guid guid, Func <List <IView>, List <BlockConfiguration>, IEnumerable <ViewDto> > finalBuilder)
        {
            var wrapLog = Log.Call <IEnumerable <ViewDto> >($"{appId}, {guid}");
            var context = _ctxResolver.BlockOrApp(appId);

            // extra security to only allow zone change if host user
            var permCheck = ServiceProvider.Build <MultiPermissionsApp>().Init(context, context.AppState, Log);

            if (!permCheck.EnsureAll(GrantSets.ReadSomething, out var error))
            {
                throw HttpException.PermissionDenied(error);
            }

            var cms = _cmsRuntime.Init(context.AppState, context.UserMayEdit, Log);
            // treat view as a list - in case future code will want to analyze many views together
            var views = new List <IView> {
                cms.Views.Get(guid)
            };

            var blocks = cms.Blocks.AllWithView();

            Log.Add($"Found {blocks.Count} content blocks");

            var result = finalBuilder(views, blocks);

            return(wrapLog("ok", result));
        }
예제 #2
0
        public DialogContextStandalone DialogSettings(int appId)
        {
            // reset app-id if we get a info-token like -100
            if (appId < 0)
            {
                appId = Eav.Constants.AppIdEmpty;
            }

            var appContext = appId != Eav.Constants.AppIdEmpty ? _ctxResolver.App(appId) : null;
            var context    = appContext ?? _ctxResolver.Site();

            // if we have an appid (we don't have it in an install-new-apps-scenario) check permissions
            if (appContext != null)
            {
                var appAndPerms = ServiceProvider.Build <MultiPermissionsApp>().Init(appContext, appContext.AppState, Log);
                if (!appAndPerms.ZoneIsOfCurrentContextOrUserIsSuper(out var error))
                {
                    throw HttpException.PermissionDenied(error);
                }
            }

            var cb = _uiContextBuilder.SetZoneAndApp(context.Site.ZoneId, appContext?.AppState);

            return(new DialogContextStandalone
            {
                Context = cb.Get(Ctx.All),
            });
        }
예제 #3
0
        public Sxc.Adam.IFile SaveInAdam(string dontRelyOnParameterOrder = Eav.Constants.RandomProtectionParameter,
                                         Stream stream      = null,
                                         string fileName    = null,
                                         string contentType = null,
                                         Guid?guid          = null,
                                         string field       = null,
                                         string subFolder   = "")
        {
            Eav.Constants.ProtectAgainstMissingParameterNames(dontRelyOnParameterOrder, "SaveInAdam",
                                                              $"{nameof(stream)},{nameof(fileName)},{nameof(contentType)},{nameof(guid)},{nameof(field)},{nameof(subFolder)} (optional)");

            if (stream == null || fileName == null || contentType == null || guid == null || field == null)
            {
                throw new Exception();
            }

            var feats = new[] { FeatureIds.UseAdamInWebApi, FeatureIds.PublicUpload };

            if (!Eav.Configuration.Features.EnabledOrException(feats, "can't save in ADAM", out var exp))
            {
                throw exp;
            }

            var appId = DynCode?.Block?.AppId ?? DynCode?.App?.AppId ?? throw new Exception("Error, SaveInAdam needs an App-Context to work, but the App is not known.");

            return(ServiceProvider.Build <AdamTransUpload <int, int> >(typeof(AdamTransUpload <int, int>))
                   .Init(appId, contentType, guid.Value, field, false, Log)
                   .UploadOne(stream, fileName, subFolder, true));
        }
예제 #4
0
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            base.OnActionExecuting(context);

            var httpContext = context.HttpContext;

            ServiceProvider = httpContext.RequestServices;
            HttpRequest GetRequest() => httpContext.Request;

            _oqtState = new OqtState(GetRequest, ServiceProvider, Log);

            var getBlock = _oqtState.GetBlock(true);

            DynCode = ServiceProvider.Build <OqtaneDynamicCode>().Init(getBlock, Log);

            var stxResolver = ServiceProvider.Build <IContextResolver>(typeof(IContextResolver));

            stxResolver.AttachRealBlock(() => getBlock);
            stxResolver.AttachBlockContext(() => _oqtState.GetContext());

            if (context.HttpContext.Items.TryGetValue(CodeCompiler.SharedCodeRootPathKeyInCache, out var createInstancePath))
            {
                CreateInstancePath = createInstancePath as string;
            }
        }
예제 #5
0
파일: AppsManager.cs 프로젝트: valadas/2sxc
        internal void RemoveAppInSiteAndEav(int appId, bool fullDelete)
        {
            var zoneId = ZoneRuntime.ZoneId;

            // check portal assignment and that it's not the default app
            if (appId == ZoneRuntime.DefaultAppId)
            {
                throw new Exception("The default app of a zone cannot be removed.");
            }

            // todo: maybe verify the app is of this portal; I assume delete will fail anyhow otherwise

            // Prepare to Delete folder in dnn - this must be done, before deleting the app in the DB
            var sexyApp  = ServiceProvider.Build <App>().InitNoData(new AppIdentity(zoneId, appId), null);
            var folder   = sexyApp.Folder;
            var physPath = sexyApp.PhysicalPath;

            // now remove from DB. This sometimes fails, so we do this before trying to clean the files
            // as the db part should be in a transaction, and if it fails, everything should stay as is
            _zoneManagerLazy.Value.Init(zoneId, Log).DeleteApp(appId, fullDelete);

            // now really delete the files - if the DB didn't end up throwing an error
            // ...but only if it's a full-delete
            if (fullDelete && !string.IsNullOrEmpty(folder) && Directory.Exists(physPath))
            {
                Directory.Delete(physPath, true);
            }
        }
예제 #6
0
        protected void ThrowIfNotAllowedInApp(List <Grants> requiredGrants, IAppIdentity alternateApp = null)
        {
            var permCheck = ServiceProvider.Build <MultiPermissionsApp>().Init(ContextOfBlock, alternateApp ?? ContextOfBlock.AppState, Log);

            if (!permCheck.EnsureAll(requiredGrants, out var error))
            {
                throw HttpException.PermissionDenied(error);
            }
        }
예제 #7
0
        private IEnumerable <AdamItemDto> GetAdamListOfItems(int appId, BundleWithLinkField set, string key)
        {
            var adamListMaker = ServiceProvider.Build <IAdamTransGetItems>();

            adamListMaker.Init(appId, set.ContentTypeName, set.Guid, key, false, Log);
            var dic = adamListMaker.ItemsInField(string.Empty, false) as IEnumerable <AdamItemDto>;

            return(dic);
        }
예제 #8
0
        public string NewBlockAndRender(int parentId, string field, int sortOrder, string app = "", Guid?guid = null)
        {
            var entityId = NewBlock(parentId, field, sortOrder, app, guid);

            // now return a rendered instance
            var newContentBlock = ServiceProvider.Build <BlockFromEntity>().Init(Block, entityId, Log);

            return(newContentBlock.BlockBuilder.Render());
        }
예제 #9
0
        public IEnumerable <AppUiInfo> Apps(string apps = null)
        {
            // Note: we must get the zone-id from the tenant, since the app may not yet exist when inserted the first time
            var tenant = ContextOfBlock.Site;

            return(ServiceProvider.Build <CmsZones>().Init(tenant.ZoneId, Log)
                   .AppsRt
                   .GetSelectableApps(tenant, apps)
                   .ToList());
        }
예제 #10
0
        protected MultiPermissionsItems ThrowIfNotAllowedInItem(IEntity itm, List <Grants> requiredGrants, IAppIdentity alternateApp = null)
        {
            var permCheck = ServiceProvider.Build <MultiPermissionsItems>().Init(Context, alternateApp ?? Context.AppState, itm, Log);

            if (!permCheck.EnsureAll(requiredGrants, out var error))
            {
                throw HttpException.PermissionDenied(error);
            }
            return(permCheck);
        }
예제 #11
0
파일: AppsRuntime.cs 프로젝트: valadas/2sxc
        /// <summary>
        /// Returns all Apps for the current zone
        /// </summary>
        /// <returns></returns>
        public List <IApp> GetApps(ISite site, Func <Eav.Apps.App, IAppDataConfiguration> buildConfig)
        {
            var zId    = ZoneRuntime.ZoneId;
            var appIds = new ZoneRuntime().Init(zId, Log).Apps;

            return(appIds
                   .Select(a => ServiceProvider.Build <App>()
                           .PreInit(site)
                           .Init(new AppIdentity(zId, a.Key), buildConfig, Log) as IApp)
                   .OrderBy(a => a.Name)
                   .ToList());
        }
예제 #12
0
        public string ResolveHyperlink(int appId, string hyperlink, string contentType, Guid guid, string field)
        {
            try
            {
                var context = _ctxResolver.App(appId);
                // different security checks depending on the link-type
                var lookupPage = hyperlink.Trim().StartsWith("page", StringComparison.OrdinalIgnoreCase);

                // look it up first, because we need to know if the result is in ADAM or not (different security scenario)
                var conv     = ServiceProvider.Build <IValueConverter>();
                var resolved = conv.ToValue(hyperlink, guid);

                if (lookupPage)
                {
                    // page link - only resolve if the user has edit-permissions
                    // only people who have some full edit permissions may actually look up pages
                    var permCheckPage = ServiceProvider.Build <MultiPermissionsApp>().Init(context, context.AppState, Log);
                    return(permCheckPage.UserMayOnAll(GrantSets.WritePublished)
                                                ? resolved
                                                : hyperlink);
                }

                // for file, we need guid & field - otherwise return the original unmodified
                if (guid == default || string.IsNullOrEmpty(field) || string.IsNullOrEmpty(contentType))
                {
                    return(hyperlink);
                }

                var isOutsideOfAdam = !(resolved.IndexOf($"/{AdamConstants.AdamRootFolder}/", StringComparison.Ordinal) > 0);

                // file-check, more abilities to allow
                // this will already do a ensure-or-throw inside it if outside of adam
                var adamCheck = AdamState; // new AdamState<int, int>();
                adamCheck.Init(context, contentType, field, guid, isOutsideOfAdam, Log);
                if (!adamCheck.Security.SuperUserOrAccessingItemFolder(resolved, out var exp))
                {
                    throw exp;
                }
                if (!adamCheck.Security.UserIsPermittedOnField(GrantSets.ReadSomething, out exp))
                {
                    throw exp;
                }

                // if everything worked till now, it's ok to return the result
                return(resolved);
            }
            catch
            {
                return(hyperlink);
            }
        }
예제 #13
0
        public Dictionary <string, IEnumerable <Dictionary <string, object> > > Query(int?appId, string name, bool includeGuid, string stream)
        {
            var wrapLog = Log.Call($"'{name}', inclGuid: {includeGuid}, stream: {stream}");

            var appCtx = appId != null?_ctxResolver.BlockOrApp(appId.Value) : _ctxResolver.BlockRequired();

            // If no app available from context, check if an app-id was supplied in url
            // Note that it may only be an app from the current portal
            // and security checks will run internally
            var app = ServiceProvider.Build <Apps.App>().Init(ServiceProvider, appCtx.AppState.AppId, Log, appCtx.UserMayEdit);

            var result = BuildQueryAndRun(app, name, stream, includeGuid, appCtx, Log, appCtx.UserMayEdit);

            wrapLog(null);
            return(result);
        }
예제 #14
0
 private LinkInfoDto TryToConvertOrErrorText(int appId, string contentType, string field, string value, Guid entityGuid)
 {
     try
     {
         var hlnkBackend = ServiceProvider.Build <HyperlinkBackend <int, int> >().Init(Log);
         var result      = hlnkBackend.LookupHyperlink(appId, value, contentType, entityGuid, field);
         return(result);// _valueConverter.ToValue(value, entityGuid);
     }
     catch
     {
         return(new LinkInfoDto  {
             Value = "error"
         });
         //return "error";
     }
 }
예제 #15
0
        // New feature in 11.03 - Usage Statistics

        public dynamic Usage(int appId, Guid guid)
        {
            var context   = _ctxResolver.App(appId);
            var permCheck = ServiceProvider.Build <MultiPermissionsApp>().Init(context, context.AppState, Log);

            if (!permCheck.EnsureAll(GrantSets.ReadSomething, out var error))
            {
                throw HttpException.PermissionDenied(error);
            }

            var item          = context.AppState.List.One(guid);
            var relationships = item.Relationships.AllRelationships;

            // var result = relationships.Select(r => new EntityInRelationDto(r.))
            // todo: don't forget Metadata relationships
            return(null);
        }
예제 #16
0
        public IEnumerable <EntityForPickerDto> GetAvailableEntities(int appId, string[] items, string contentTypeName)
        {
            var context = _ctxResolver.App(appId);
            // do security check
            var permCheck = string.IsNullOrEmpty(contentTypeName)
                ? ServiceProvider.Build <MultiPermissionsApp>().Init(context, context.AppState, Log)
                : ServiceProvider.Build <MultiPermissionsTypes>().Init(context, context.AppState, contentTypeName, Log);

            if (!permCheck.EnsureAll(GrantSets.ReadSomething, out var error))
            {
                throw HttpException.PermissionDenied(error);
            }

            // maybe in the future, ATM not relevant
            var withDrafts = permCheck.EnsureAny(GrantSets.ReadDraft);

            return(_entityPickerApi.Init(Log).GetAvailableEntities(appId, items, contentTypeName, withDrafts));
        }
예제 #17
0
        public Dictionary <string, IEnumerable <Dictionary <string, object> > > PublicQuery(string appPath, string name, string stream)
        {
            var wrapLog = Log.Call($"path:{appPath}, name:{name}");

            if (string.IsNullOrEmpty(name))
            {
                throw HttpException.MissingParam(nameof(name));
            }

            var appCtx = _ctxResolver.AppOrBlock(appPath);

            var queryApp = ServiceProvider.Build <Apps.App>().Init(appCtx.AppState,
                                                                   ServiceProvider.Build <AppConfigDelegate>().Init(Log).Build(appCtx.UserMayEdit), Log);

            // now just run the default query check and serializer
            var result = BuildQueryAndRun(queryApp, name, stream, false, appCtx, Log, appCtx.UserMayEdit);

            wrapLog(null);
            return(result);
        }
예제 #18
0
        public List <AppDto> Apps()
        {
            var cms = _cmsZones.Init(_context.Site.ZoneId, Log);
            var configurationBuilder = ServiceProvider.Build <AppConfigDelegate>().Init(Log).Build(_context.UserMayEdit);
            var list = cms.AppsRt.GetApps(_context.Site, configurationBuilder);

            return(list.Select(a => new AppDto
            {
                Id = a.AppId,
                IsApp = a.AppGuid != Eav.Constants.DefaultAppName,
                Guid = a.AppGuid,
                Name = a.Name,
                Folder = a.Folder,
                AppRoot = a.Path,
                IsHidden = a.Hidden,
                ConfigurationId = a.Configuration?.Id,
                Items = a.Data.List.Count(),
                Thumbnail = a.Thumbnail,
                Version = a.VersionSafe()
            }).ToList());
        }
예제 #19
0
        private Dictionary <string, IEnumerable <AdamItemDto> > PrefetchAdam(int appId, EditDto editData)
        {
            // Step 1: try to find hyperlink fields
            var bundlesHavingLinks = BundleWithLinkFields(editData);

            var links = bundlesHavingLinks.SelectMany(set
                                                      => set.Hyperlink.Select(h
                                                                              =>
            {
                var adamListMaker = ServiceProvider.Build <IAdamTransGetItems>();
                adamListMaker.Init(appId, set.ContentTypeName, set.Guid, h.Key, false, Log);
                return(new
                {
                    set.Guid,
                    h.Key,
                    Dic = adamListMaker.ItemsInField(string.Empty) as IEnumerable <AdamItemDto>,
                });
            }))
                        //.Where(set => set != null)
                        // Step 2: Check which ones have a link reference
                        .ToDictionary(r => r.Key, r => r.Dic);

            return(links);
        }
예제 #20
0
        public Dictionary <Guid, int> Save(EditDto package, bool partOfPage)
        {
            Log.Add($"save started with a#{_appId}, i⋮{package.Items.Count}, partOfPage:{partOfPage}");

            var validator = new SaveDataValidator(package, Log);

            // perform some basic validation checks
            if (!validator.ContainsOnlyExpectedNodes(out var exp))
            {
                throw exp;
            }

            // todo: unsure about this - thought I should check contentblockappid in group-header, because this is where it should be saved!
            //var contextAppId = appId;
            //var targetAppId = package.Items.First().Header.Group.ContentBlockAppId;
            //if (targetAppId != 0)
            //{
            //    Log.Add($"detected content-block app to use: {targetAppId}; in context of app {contextAppId}");
            //    appId = targetAppId;
            //}

            var appMan  = _appManagerLazy.Value.Init(_appId, Log);
            var appRead = appMan.Read;
            var ser     = ServiceProvider.Build <JsonSerializer>().Init(appRead.AppState, Log);

            // Since we're importing directly into this app, we would prefer local content-types
            ser.PreferLocalAppTypes = true;
            validator.PrepareForEntityChecks(appRead);

            #region check if it's an update, and do more security checks then - shared with EntitiesController.Save
            // basic permission checks
            var permCheck = new Save.SaveSecurity(_context, Log)
                            .DoPreSaveSecurityCheck(_appId, package.Items);

            var foundItems = package.Items.Where(i => i.Entity.Id != 0 && i.Entity.Guid != Guid.Empty)
                             .Select(i => i.Entity.Guid != Guid.Empty
                        ? appRead.Entities.Get(i.Entity.Guid) // prefer guid access if available
                        : appRead.Entities.Get(i.Entity.Id)   // otherwise id
                                     );
            if (foundItems.Any(i => i != null) && !permCheck.EnsureAll(GrantSets.UpdateSomething, out var error))
            {
                throw HttpException.PermissionDenied(error);
            }
            #endregion


            var items = package.Items.Select(i =>
            {
                var ent = ser.Deserialize(i.Entity, false, false) as Entity;

                var index = package.Items.IndexOf(i); // index is helpful in case of errors
                if (!validator.EntityIsOk(index, ent, out exp))
                {
                    throw exp;
                }

                if (!validator.IfUpdateValidateAndCorrectIds(index, ent, out exp))
                {
                    throw exp;
                }

                ent.IsPublished        = package.IsPublished;
                ent.PlaceDraftInBranch = package.DraftShouldBranch;

                // new in 11.01
                if (i.Header.ListHas())
                {
                    // Check if Add was true, and fix if it had already been saved (EntityId != 0)
                    // the entityId is reset by the validator if it turns out to be an update
                    // todo: verify use - maybe it's to set before we save, as maybe afterwards it's always != 0?
                    var add      = i.Header.ListAdd();
                    i.Header.Add = add;
                    if (ent.EntityId > 0 && add)
                    {
                        i.Header.Add = false;
                    }
                }

                return(new BundleWithHeader <IEntity>
                {
                    Header = i.Header,
                    Entity = ent
                });
            })
                        .ToList();

            Log.Add("items to save generated, all data tests passed");

            return(_pagePublishing.SaveInPagePublishing(_ctxResolver.RealBlockOrNull(), _appId, items, partOfPage,
                                                        forceSaveAsDraft => DoSave(appMan, items, forceSaveAsDraft),
                                                        permCheck));
        }
예제 #21
0
        // 2021-04-13 2dm should be unused now
        //public string ResolveHyperlink(int appId, string hyperlink, string contentType, Guid guid, string field)
        //{
        //	try
        //	{
        //		var context = _ctxResolver.BlockOrApp(appId);
        //		// different security checks depending on the link-type
        //		var lookupPage = hyperlink.Trim().StartsWith(ValueConverterBase.PrefixPage, StringComparison.OrdinalIgnoreCase);

        //		// look it up first, because we need to know if the result is in ADAM or not (different security scenario)
        //		var conv = ServiceProvider.Build<IValueConverter>();
        //		var resolved = conv.ToValue(hyperlink, guid);

        //		if (lookupPage)
        //		{
        //			// page link - only resolve if the user has edit-permissions
        //			// only people who have some full edit permissions may actually look up pages
        //			var permCheckPage = ServiceProvider.Build<MultiPermissionsApp>().Init(context, context.AppState, Log);
        //			return permCheckPage.UserMayOnAll(GrantSets.WritePublished)
        //				? resolved
        //				: hyperlink;
        //		}

        //		// for file, we need guid & field - otherwise return the original unmodified
        //		if (guid == default || string.IsNullOrEmpty(field) || string.IsNullOrEmpty(contentType))
        //			return hyperlink;

        //		var isOutsideOfAdam = !(resolved.IndexOf($"/{AdamConstants.AdamRootFolder}/", StringComparison.Ordinal) > 0);

        //		// file-check, more abilities to allow
        //		// this will already do a ensure-or-throw inside it if outside of adam
        //              var adamCheck = AdamContext; // new AdamState<int, int>();
        //              adamCheck.Init(context, contentType, field, guid, isOutsideOfAdam, Log);
        //		if (!adamCheck.Security.SuperUserOrAccessingItemFolder(resolved, out var exp))
        //			throw exp;
        //		if (!adamCheck.Security.UserIsPermittedOnField(GrantSets.ReadSomething, out exp))
        //			throw exp;

        //		// if everything worked till now, it's ok to return the result
        //		return resolved;
        //	}
        //	catch
        //	{
        //		return hyperlink;
        //	}
        //}

        public LinkInfoDto LookupHyperlink(int appId, string hyperlink, string contentType, Guid guid, string field)
        {
            try
            {
                var context = _ctxResolver.BlockOrApp(appId);
                // different security checks depending on the link-type
                var lookupPage = hyperlink.Trim().StartsWith(ValueConverterBase.PrefixPage, StringComparison.OrdinalIgnoreCase);

                // look it up first, because we need to know if the result is in ADAM or not (different security scenario)
                var conv     = ServiceProvider.Build <IValueConverter>();
                var resolved = conv.ToValue(hyperlink, guid);

                if (lookupPage)
                {
                    // page link - only resolve if the user has edit-permissions
                    // only people who have some full edit permissions may actually look up pages
                    var permCheckPage = ServiceProvider.Build <MultiPermissionsApp>().Init(context, context.AppState, Log);
                    var userMay       = permCheckPage.UserMayOnAll(GrantSets.WritePublished);
                    return(new LinkInfoDto {
                        Value = userMay ? resolved : hyperlink
                    });
                }

                // for file, we need guid & field - otherwise return the original unmodified
                if (guid == default || string.IsNullOrEmpty(field) || string.IsNullOrEmpty(contentType))
                {
                    return new LinkInfoDto {
                               Value = hyperlink
                    }
                }
                ;

                var isOutsideOfAdam = !Sxc.Adam.Security.PathIsInItemAdam(guid, field, resolved);

                // file-check, more abilities to allow
                // this will already do a ensure-or-throw inside it if outside of adam
                var adamContext = AdamContext;
                adamContext.Init(context, contentType, field, guid, isOutsideOfAdam, Log);
                if (!adamContext.Security.SuperUserOrAccessingItemFolder(resolved, out var exp))
                {
                    throw exp;
                }
                if (!adamContext.Security.UserIsPermittedOnField(GrantSets.ReadSomething, out exp))
                {
                    throw exp;
                }

                // now try to find the item
                // we already know that the link was able to match, so we'll just use this to get the id
                var parts = new ValueConverterBase.LinkParts(hyperlink);
                // Note: kind of temporary solution, will fail if TFileId isn't int!
                var file     = ((IAdamFileSystem <int, int>)adamContext.AdamManager.AdamFs).GetFile(parts.Id);
                var dtoMaker = AdamContext.ServiceProvider.Build <AdamItemDtoMaker <TFolderId, TFileId> >().Init(AdamContext);
                // if everything worked till now, it's ok to return the result
                var adam = dtoMaker.Create(file as File <TFolderId, TFileId>);
                return(new LinkInfoDto {
                    Adam = adam, Value = adam.Url
                });
            }
            catch
            {
                return(new LinkInfoDto {
                    Value = hyperlink
                });
            }
        }
예제 #22
0
        public EditDto Load(int appId, List <ItemIdentifier> items)
        {
            // Security check
            var wrapLog = Log.Call($"load many a#{appId}, items⋮{items.Count}");

            var context = _ctxResolver.App(appId);

            var showDrafts = context.UserMayEdit;

            // do early permission check - but at this time it may be that we don't have the types yet
            // because they may be group/id combinations, without type information which we'll look up afterwards
            var appIdentity = State.Identity(null, appId);

            items = _contentGroupList.Init(appIdentity, Log, showDrafts).ConvertListIndexToId(items);
            TryToAutoFindMetadataSingleton(items, context.AppState);

            // now look up the types, and repeat security check with type-names
            // todo: 2020-03-20 new feat 11.01, may not check inner type permissions ATM
            var permCheck = ServiceProvider.Build <MultiPermissionsTypes>().Init(context, context.AppState, items, Log);

            if (!permCheck.EnsureAll(GrantSets.WriteSomething, out var error))
            {
                throw HttpException.PermissionDenied(error);
            }

            // load items - similar
            var result         = new EditDto();
            var entityApi      = _entityApi.Init(appId, permCheck.EnsureAny(GrantSets.ReadDraft), Log);
            var typeRead       = entityApi.AppRead.ContentTypes;
            var list           = entityApi.GetEntitiesForEditing(items);
            var jsonSerializer = ServiceProvider.Build <JsonSerializer>();

            result.Items = list.Select(e => new BundleWithHeader <JsonEntity>
            {
                Header = e.Header,
                Entity = GetSerializeAndMdAssignJsonEntity(appId, e, jsonSerializer, typeRead)
            }).ToList();

            // set published if some data already exists
            if (list.Any())
            {
                result.IsPublished = list.First().Entity?.IsPublished ?? true; // Entity could be null (new), then true
                // only set draft-should-branch if this draft already has a published item
                if (!result.IsPublished)
                {
                    result.DraftShouldBranch = list.First().Entity?.GetPublished() != null;
                }
            }

            // since we're retrieving data - make sure we're allowed to
            // this is to ensure that if public forms only have create permissions, they can't access existing data
            // important, this code is shared/duplicated in the EntitiesController.GetManyForEditing
            if (list.Any(set => set.Entity != null))
            {
                if (!permCheck.EnsureAll(GrantSets.ReadSomething, out error))
                {
                    throw HttpException.PermissionDenied(error);
                }
            }

            // load content-types
            var types = UsedTypes(list, typeRead);

            result.ContentTypes = types
                                  .Select(ct => JsonSerializer.ToJson(ct, true))
                                  .ToList();

            //// TEMP DEBUG
            //var inputTypes = types.SelectMany(t => t.Attributes.Select(a =>
            //{
            //    var Lookup = a.InputType();
            //    var mdAll = a.Metadata.Where(md => md.Type.Is(Constants.MetadataFieldTypeAll)).ToList();
            //    var MdAllCount = mdAll.Count;
            //    var MdAllAttribCount = mdAll.FirstOrDefault()?.Attributes.Count;
            //    var MdAllWithAttrib = mdAll.FirstOrDefault(md => md.Attributes.ContainsKey(Constants.MetadataFieldTypeAll));
            //    var MdAllAttrib = MdAllWithAttrib?.GetBestValue<string>(Constants.MetadataFieldTypeAll);
            //    var MdAllAttribZero = MdAllWithAttrib?.GetBestValue<string>(Constants.MetadataFieldTypeAll, new string[0]);
            //    var MdAllAttribEmpty = MdAllWithAttrib?.GetBestValue<string>(Constants.MetadataFieldTypeAll, new []{""});
            //    var MdAllAttribEn = MdAllWithAttrib?.GetBestValue<string>(Constants.MetadataFieldTypeAll, new[] { "en-us" });
            //    var MdAllAttribTr = MdAllWithAttrib?.GetBestValue<string>(Constants.MetadataFieldTypeAll, new[] { "tr-tr" });
            //    var MdAllType = a.Metadata.GetBestValue<string>(Constants.MetadataFieldAllInputType, Constants.MetadataFieldTypeAll);
            //    return new {Lookup, MdAllCount, MdAllType, MdAllAttribCount, MdAllWithAttrib?.EntityId, MdAllAttrib, MdAllAttribZero, MdAllAttribEmpty, MdAllAttribEn, MdAllAttribTr };
            //}));
            //var serializedDebug = JsonConvert.SerializeObject(inputTypes);
            //Log.Add("Test / debug: " + serializedDebug);

            // ensure that sub-properties of the content-types are included
            // this is for UI Formulas (children of @All) - WIP
            // and the warning/error Regex specials - WIP
            var entList = types.SelectMany(
                // in all Content-Type attributes like title, body etc.
                t => t.Attributes.SelectMany(
                    // check all metadata of these attributes - get possible sub-entities attached
                    a => a.Metadata.SelectMany(m => m.Children())
                    )
                );

            result.ContentTypeItems = entList.Select(e => jsonSerializer.ToJson(e, 0, Log)).ToList();

            // Fix not-supported input-type names; map to correct name
            result.ContentTypes
            .ForEach(jt => jt.Attributes
                     .ForEach(at => at.InputType = InputTypes.MapInputTypeV10(at.InputType)));

            // load input-field configurations
            result.InputTypes = GetNecessaryInputTypes(result.ContentTypes, typeRead);

            // also include UI features
            result.Features = FeaturesHelpers.FeatureListWithPermissionCheck(permCheck).ToList();

            // Attach context, but only the minimum needed for the UI
            result.Context = _contextBuilder.SetZoneAndApp(appIdentity.ZoneId, context.AppState)
                             .Get(Ctx.AppBasic | Ctx.Language | Ctx.Site | Ctx.System);

            try
            {
                result.Prefetch = TryToPrefectAdditionalData(appId, result);
            }
            catch (Exception) { /* ignore */ }

            // done - return
            wrapLog($"ready, sending items:{result.Items.Count}, " +
                    $"types:{result.ContentTypes.Count}, " +
                    $"inputs:{result.InputTypes.Count}, " +
                    $"feats:{result.Features.Count}");
            return(result);
        }