XPath can unambiguously use $site/$parent (#14127)
* XPath can unambiguously use $site/$parent * add deprecation notices and obsolete methods * Update deprecation description text/instruction Co-authored-by: Mole <nikolajlauridsen@protonmail.ch> * Small spelling fix on deprecation description * keep depr. getByQuery and handle legacy usage --------- Co-authored-by: Kalle Macklin <karlmacklin@users.noreply.github.co> Co-authored-by: Mole <nikolajlauridsen@protonmail.ch>
This commit is contained in:
@@ -8,13 +8,23 @@ namespace Umbraco.Cms.Core.Xml;
|
||||
/// </summary>
|
||||
public class UmbracoXPathPathSyntaxParser
|
||||
{
|
||||
[Obsolete("This will be removed in Umbraco 13. Use ParseXPathQuery which accepts a parentId instead")]
|
||||
public static string ParseXPathQuery(
|
||||
string xpathExpression,
|
||||
int? nodeContextId,
|
||||
Func<int, IEnumerable<string>?> getPath,
|
||||
Func<int, bool> publishedContentExists) => ParseXPathQuery(xpathExpression, nodeContextId, null, getPath, publishedContentExists);
|
||||
|
||||
/// <summary>
|
||||
/// Parses custom umbraco xpath expression
|
||||
/// </summary>
|
||||
/// <param name="xpathExpression">The Xpath expression</param>
|
||||
/// <param name="nodeContextId">
|
||||
/// The current node id context of executing the query - null if there is no current node, in which case
|
||||
/// some of the parameters like $current, $parent, $site will be disabled
|
||||
/// The current node id context of executing the query - null if there is no current node.
|
||||
/// </param>
|
||||
/// <param name="parentId">
|
||||
/// The parent node id of the current node id context of executing the query. With this we can determine the
|
||||
/// $parent and $site parameters even if the current node is not yet published.
|
||||
/// </param>
|
||||
/// <param name="getPath">The callback to create the nodeId path, given a node Id</param>
|
||||
/// <param name="publishedContentExists">The callback to return whether a published node exists based on Id</param>
|
||||
@@ -22,6 +32,7 @@ public class UmbracoXPathPathSyntaxParser
|
||||
public static string ParseXPathQuery(
|
||||
string xpathExpression,
|
||||
int? nodeContextId,
|
||||
int? parentId,
|
||||
Func<int, IEnumerable<string>?> getPath,
|
||||
Func<int, bool> publishedContentExists)
|
||||
{
|
||||
@@ -84,19 +95,27 @@ public class UmbracoXPathPathSyntaxParser
|
||||
// parseable items:
|
||||
var vars = new Dictionary<string, Func<string, string>>();
|
||||
|
||||
// These parameters must have a node id context
|
||||
if (nodeContextId.HasValue)
|
||||
if (parentId.HasValue)
|
||||
{
|
||||
vars.Add("$parent", q =>
|
||||
{
|
||||
var path = getPath(parentId.Value)?.ToArray();
|
||||
var closestPublishedAncestorId = getClosestPublishedAncestor(path);
|
||||
return q.Replace("$parent", string.Format(rootXpath, closestPublishedAncestorId));
|
||||
});
|
||||
|
||||
vars.Add("$site", q =>
|
||||
{
|
||||
var closestPublishedAncestorId = getClosestPublishedAncestor(getPath(parentId.Value));
|
||||
return q.Replace(
|
||||
"$site",
|
||||
string.Format(rootXpath, closestPublishedAncestorId) + "/ancestor-or-self::*[@level = 1]");
|
||||
});
|
||||
}
|
||||
else if (nodeContextId.HasValue)
|
||||
{
|
||||
vars.Add("$current", q =>
|
||||
{
|
||||
var closestPublishedAncestorId = getClosestPublishedAncestor(getPath(nodeContextId.Value));
|
||||
return q.Replace("$current", string.Format(rootXpath, closestPublishedAncestorId));
|
||||
});
|
||||
|
||||
vars.Add("$parent", q =>
|
||||
{
|
||||
// remove the first item in the array if its the current node
|
||||
// this happens when current is published, but we are looking for its parent specifically
|
||||
var path = getPath(nodeContextId.Value)?.ToArray();
|
||||
if (path?[0] == nodeContextId.ToString())
|
||||
{
|
||||
@@ -116,6 +135,16 @@ public class UmbracoXPathPathSyntaxParser
|
||||
});
|
||||
}
|
||||
|
||||
// These parameters must have a node id context
|
||||
if (nodeContextId.HasValue)
|
||||
{
|
||||
vars.Add("$current", q =>
|
||||
{
|
||||
var closestPublishedAncestorId = getClosestPublishedAncestor(getPath(nodeContextId.Value));
|
||||
return q.Replace("$current", string.Format(rootXpath, closestPublishedAncestorId));
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: This used to just replace $root with string.Empty BUT, that would never work
|
||||
// the root is always "/root . Need to confirm with Per why this was string.Empty before!
|
||||
vars.Add("$root", q => q.Replace("$root", "/root"));
|
||||
|
||||
@@ -79,6 +79,7 @@ internal class NotFoundHandlerHelper
|
||||
var xpathResult = UmbracoXPathPathSyntaxParser.ParseXPathQuery(
|
||||
errorPage.ContentXPath!,
|
||||
domainContentId,
|
||||
null,
|
||||
nodeid =>
|
||||
{
|
||||
IEntitySlim? ent = entityService.Get(nodeid);
|
||||
|
||||
@@ -555,6 +555,15 @@ public class EntityController : UmbracoAuthorizedJsonController
|
||||
return Ok(returnUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an entity by a xpath query - OBSOLETE
|
||||
/// </summary>
|
||||
/// <param name="query"></param>
|
||||
/// <param name="nodeContextId"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("This will be removed in Umbraco 13. Use GetByXPath instead")]
|
||||
public ActionResult<EntityBasic?>? GetByQuery(string query, int nodeContextId, UmbracoEntityTypes type) => GetByXPath(query, nodeContextId, null, type);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an entity by a xpath query
|
||||
@@ -562,19 +571,16 @@ public class EntityController : UmbracoAuthorizedJsonController
|
||||
/// <param name="query"></param>
|
||||
/// <param name="nodeContextId"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="parentId"></param>
|
||||
/// <returns></returns>
|
||||
public ActionResult<EntityBasic?>? GetByQuery(string query, int nodeContextId, UmbracoEntityTypes type)
|
||||
public ActionResult<EntityBasic?>? GetByXPath(string query, int nodeContextId, int? parentId, UmbracoEntityTypes type)
|
||||
{
|
||||
// TODO: Rename this!!! It's misleading, it should be GetByXPath
|
||||
|
||||
|
||||
if (type != UmbracoEntityTypes.Document)
|
||||
{
|
||||
throw new ArgumentException("Get by query is only compatible with entities of type Document");
|
||||
}
|
||||
|
||||
|
||||
var q = ParseXPathQuery(query, nodeContextId);
|
||||
var q = ParseXPathQuery(query, nodeContextId, parentId);
|
||||
IPublishedContent? node = _publishedContentQuery.ContentSingleAtXPath(q);
|
||||
|
||||
if (node == null)
|
||||
@@ -586,10 +592,11 @@ public class EntityController : UmbracoAuthorizedJsonController
|
||||
}
|
||||
|
||||
// PP: Work in progress on the query parser
|
||||
private string ParseXPathQuery(string query, int id) =>
|
||||
private string ParseXPathQuery(string query, int id, int? parentId) =>
|
||||
UmbracoXPathPathSyntaxParser.ParseXPathQuery(
|
||||
query,
|
||||
id,
|
||||
parentId,
|
||||
nodeid =>
|
||||
{
|
||||
IEntitySlim? ent = _entityService.Get(nodeid);
|
||||
|
||||
@@ -318,9 +318,22 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
'Failed to retrieve entity data for ids ' + ids);
|
||||
},
|
||||
|
||||
/**
|
||||
* @deprecated use getByXPath instead.
|
||||
*/
|
||||
getByQuery: function (query, nodeContextId, type) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"entityApiBaseUrl",
|
||||
"GetByQuery",
|
||||
[{ query: query }, { nodeContextId: nodeContextId }, { type: type }])),
|
||||
'Failed to retrieve entity data for query ' + query);
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.entityResource#getByQuery
|
||||
* @name umbraco.resources.entityResource#getByXPath
|
||||
* @methodOf umbraco.resources.entityResource
|
||||
*
|
||||
* @description
|
||||
@@ -329,7 +342,7 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
* ##usage
|
||||
* <pre>
|
||||
* //get content by xpath
|
||||
* entityResource.getByQuery("$current", -1, "Document")
|
||||
* entityResource.getByXPath("$current", -1, -1, "Document")
|
||||
* .then(function(ent) {
|
||||
* var myDoc = ent;
|
||||
* alert('its here!');
|
||||
@@ -338,17 +351,18 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
*
|
||||
* @param {string} query xpath to use in query
|
||||
* @param {Int} nodeContextId id id to start from
|
||||
* @param {Int} parentId id id of the parent to the starting point
|
||||
* @param {string} type Object type name
|
||||
* @returns {Promise} resourcePromise object containing the entity.
|
||||
*
|
||||
*/
|
||||
getByQuery: function (query, nodeContextId, type) {
|
||||
getByXPath: function (query, nodeContextId, parentId, type) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"entityApiBaseUrl",
|
||||
"GetByQuery",
|
||||
[{ query: query }, { nodeContextId: nodeContextId }, { type: type }])),
|
||||
"GetByXPath",
|
||||
[{ query: query }, { nodeContextId: nodeContextId }, { parentId: parentId }, { type: type }])),
|
||||
'Failed to retrieve entity data for query ' + query);
|
||||
},
|
||||
|
||||
|
||||
@@ -245,9 +245,12 @@ function contentPickerController($scope, $q, $routeParams, $location, entityReso
|
||||
dialogOptions.startNodeId = -1;
|
||||
}
|
||||
else if ($scope.model.config.startNode.query) {
|
||||
//if we have a query for the startnode, we will use that.
|
||||
var rootId = editorState.current.id;
|
||||
entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) {
|
||||
entityResource.getByXPath(
|
||||
$scope.model.config.startNode.query,
|
||||
editorState.current.id,
|
||||
editorState.current.parentId,
|
||||
"Document"
|
||||
).then(function (ent) {
|
||||
dialogOptions.startNodeId = ($scope.model.config.idType === "udi" ? ent.udi : ent.id).toString();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user