WP REST API V2-按完整插件检索子页面(URL/路径)

时间:2017-03-28 作者:Snaver

我很难确定这是否是本机支持的,或者它是否不是实现此功能的最佳位置-在我的应用程序(Laravel)或API(WordPress)的同一面。

通过slug检索单个页面(/关于我们)很容易:

/wp-json/wp/v2/pages?slug=about-us
但检索子/子页面时会出现问题。考虑一下/关于我们/子页面-这在WordPress中工作得很好,但通过RESTAPI检索页面似乎不可能?

/wp-json/wp/v2/pages?slug=about-us/child-page
/wp-json/wp/v2/pages?slug=%2Fabout-us%2Fchild-page%2F
没有结果。

我可以搜索单独的页面并获得结果,但是如果另一个页面共享该段代码,则可能会发生冲突。

/wp-json/wp/v2/pages?slug=child-page
get_page_by_path() 它允许您通过路径搜索页面,这正是我所追求的-我可以使用自定义REST API端点来实现这一点(https://www.coditty.com/code/wordpress-rest-api-how-to-get-content-by-slug) 但返回的结果不标准,与WP-REST等效值不可比(见下文)

{
    "id": 22,
    "date": "2017-03-28T13:15:53",
    "date_gmt": "2017-03-28T12:15:53",
    "guid": {
      "rendered": "http://127.0.0.1:8000/?page_id=22"
    },
    "modified": "2017-03-28T13:15:53",
    "modified_gmt": "2017-03-28T12:15:53",
    "slug": "test-sub-page",
    "status": "publish",
    "type": "page",
    "link": "http://127.0.0.1:8000/test/test-sub-page/",
    "title": {
      "rendered": "Test Sub page"
    },
    "content": {
      "rendered": "...",
      "protected": false
    },
    "excerpt": {
      "rendered": "....",
      "protected": false
    },
    "author": 1,
    "featured_media": 0,
    "parent": 7,
    "menu_order": 0,
    "comment_status": "closed",
    "ping_status": "closed",
    "template": "",
    "meta": [],
    "_links": {
      "self": [
        {
          "href": "http://127.0.0.1:8000/wp-json/wp/v2/pages/22"
        }
      ],
      "collection": [
        {
          "href": "http://127.0.0.1:8000/wp-json/wp/v2/pages"
        }
      ],
      "about": [
        {
          "href": "http://127.0.0.1:8000/wp-json/wp/v2/types/page"
        }
      ],
      "author": [
        {
          "embeddable": true,
          "href": "http://127.0.0.1:8000/wp-json/wp/v2/users/1"
        }
      ],
      "replies": [
        {
          "embeddable": true,
          "href": "http://127.0.0.1:8000/wp-json/wp/v2/comments?post=22"
        }
      ],
      "version-history": [
        {
          "href": "http://127.0.0.1:8000/wp-json/wp/v2/pages/22/revisions"
        }
      ],
      "up": [
        {
          "embeddable": true,
          "href": "http://127.0.0.1:8000/wp-json/wp/v2/pages/7"
        }
      ],
      "wp:attachment": [
        {
          "href": "http://127.0.0.1:8000/wp-json/wp/v2/media?parent=22"
        }
      ],
      "curies": [
        {
          "name": "wp",
          "href": "https://api.w.org/{rel}",
          "templated": true
        }
      ]
    }
}
VS公司

{
  "ID": 22,
  "post_author": "1",
  "post_date": "2017-03-28 13:15:53",
  "post_date_gmt": "2017-03-28 12:15:53",
  "post_content": "...",
  "post_title": "Test Sub page",
  "post_excerpt": "",
  "post_status": "publish",
  "comment_status": "closed",
  "ping_status": "closed",
  "post_password": "",
  "post_name": "test-sub-page",
  "to_ping": "",
  "pinged": "",
  "post_modified": "2017-03-28 13:15:53",
  "post_modified_gmt": "2017-03-28 12:15:53",
  "post_content_filtered": "",
  "post_parent": 7,
  "guid": "http://127.0.0.1:8000/?page_id=22",
  "menu_order": 0,
  "post_type": "page",
  "post_mime_type": "",
  "comment_count": "0",
  "filter": "raw"
}
或者,我可以从我的应用程序中轮询每个段/页面的API,以验证每个页面是否存在,并以这种方式建立数据。。。

2 个回复
SO网友:bosco

遗憾的是,本机并不支持此功能。具体而言,问题在于大多数帖子类型包括page 使用底座WP_REST_Posts_Controller 其中映射了slug 参数设置为post_name__in WP_Query 参数,这不利于解析层次结构段塞。这个pagename 然而,查询变量确实如此,但每个查询只有一个,这可能就是为什么它还没有用于层次结构帖子类型的REST请求。

有许多解决方案和解决方法。请注意,下面的代码尚未经过彻底测试,JavaScript尤其忽略了重要的身份验证和错误处理实践——它仅用于说明目的。

发出多个请求您的客户端只需遍历每个路径部分并使用_fields 参数,通过仅请求祖先帖子的ID,然后使用该帖子ID作为parent 后续请求的参数:

async function wpse261645_fetchPage( path ) {
  const parts = path.split( \'/\' );
  const uri = \'/wp-json/wp/v2/pages\';
  let parent_id;

  for( let i = 0; i < parts.length; i++ ) {
    const params = new URLSearchParams( { slug: parts[i] } );

    if( i < parts.length - 1 )
      params.append( \'_fields\', \'id\' );

    if( parent_id )
      params.append( \'parent\', parent_id );
    
    const res = await fetch(
      `${uri}?${params}`,
      {
        method: \'GET\',
        headers: { \'Content-Type\': \'application/json\' }
      }
    ).then( res => res.json() );

    if( i === parts.length - 1 )
      return res;

    parent_id = res[0].id;
  }
}

修改slug REST参数/QV映射这可能是最有吸引力的解决方案,因为它可以直接有效地解决原始问题,而不需要客户端进行特殊处理。但由于涉及到的运动部件的数量以及我自己对REST API的不熟悉,这有点复杂和实验性——我完全愿意接受建议和改进!

WP_REST_Posts_Controller 执行查询以检索与请求相对应的项,它通过以下方式运行查询参数和请求对象the rest_{$this->post_type}_query filter. 我们可以利用此过滤器有选择地重新映射slug 根据所选立柱类型或控制器的需要。

还需要调整slug 相关控制器的参数模式、解析和清理例程/s或%2F缓动值或跳闸验证错误。我不认为这会产生任何兼容性问题,因为模式和参数注册中唯一涉及的部分是交换它们的清理回调-更改应该对客户端和发现几乎不可见,并且与原始功能完全向后兼容(除非有人一直依赖REST API进行转换/s插入-s) -但我还没有彻底测试过。

解析、架构和清理调整(以保持/s)

function wpse261645_sanitize_nested_slug( $slug ) {
  // Exploding slugs, as one does.
  $slug_parts = array_map( \'sanitize_title\', explode( \'/\', $slug ) );

  return implode( \'/\', $slug_parts );
}

function wpse261645_parse_nested_slug_list( $slugs ) {
  $slugs = wp_parse_list( $slugs );

  return array_unique( array_map( \'wpse261645_sanitize_nested_slug\', $slugs ) );
}

function wpse261645_nested_slug_schema( $schema ) {
  $schema[\'slug\'][\'arg_options\'][\'sanitize_callback\'] = \'wpse261645_sanitize_nested_slug\';

  return $schema;
}
add_filter( \'rest_page_item_schema\', \'wpse261645_nested_slug_schema\' );

function wpse261645_nested_slug_collection_params( $params ) {
  $params[\'slug\'][\'sanitize_callback\'] = \'wpse261645_parse_nested_slug_list\';

  return $params;
}
add_filter( \'rest_page_collection_params\', \'wpse261645_nested_slug_collection_params\' );

重新映射slug REST参数查询变量/slug 参数,我们可以将值映射到各种不同的查询中:

function wpse261645_remap_slug_param_qv( $args, $request ) {
  $slugs = $request->get_param( \'slug\' );

  // If the `slug` param was not even set, skip further processing.
  if( empty( $slugs ) )
    return $args;

  // Pull out hierarchical slugs into their own list.
  $nested_slugs = [];
  foreach( $slugs as $index => $slug ) {
    if( strpos( $slug, \'/\' ) !== false ) {
      $nested_slugs[] = $slug;
      unset( $slugs[ $index ] );
    }
  }

  if( count( $slugs ) ) {
    $args[\'post_name__in\'] = $slugs;

    if( count( $nested_slugs ) ) {
      $args[\'wpse261645_compound_query\'] = true;
      $args[\'post__in\'] = array_map( \'url_to_postid\', $nested_slugs );

      add_filter( \'posts_where\', \'wpse261645_compound_query_where\', 10, 2 );
    }
  }
  else {
    unset( $args[\'post_name__in\'] );

    if( count( $nested_slugs ) === 1 )
      $args[\'pagename\'] = $nested_slugs[0];
    elseif( count( $nested_slugs > 1 ) )
      $args[\'post__in\'] = array_map( \'url_to_postid\', $nested_slugs );
  }

  return $args;
}
add_filter( \'rest_page_query\', \'wpse261645_remap_slug_param_qv\', 10, 2 );

function wpse261645_compound_query_where( $where, $query ) {
  global $wpdb;

  if( ! isset( $query->query[\'wpse261645_compound_query\'] ) )
    return $where;

  return preg_replace(
    "/ AND ({$wpdb->posts}.post_name IN \\([^)]*\\)) AND ({$wpdb->posts}.ID IN \\([^)]*\\))/",
    \' AND ($1 OR $2)\',
    $where
  );
}
上述逻辑根据slug:

任意数量的扁平段塞映射到post_name__in QV符合标准pagename QV,出租WP_Query 本机处理路径分辨率url_to_postid() 并映射到post__in QV。查找会增加额外的开销post__in 和post_name__in QV和WHERE子句分别对这些条件进行了修改,而不是对其进行了修订。分层段塞将解析为ID,从而增加额外的开销总之,最有效的查询是传递slug 作为单个层次段塞,或列表中任意数量的扁平段塞。分层或混合段塞列表将导致额外的开销。

作为一个额外的好处,此实现还通过包含/. 例如。?slug=foobar 将返回所有带有slug的帖子foobar 按照惯例,但是?slug=/foobar 将返回带弹头的立柱foobar 它没有父对象。

对Web路径使用HEAD请求

This is a horrible dirty hack 这依赖于RESTAPI之外的约定,这些约定可能会产生大量开销some sites may be prone to disabling 要启动-I strongly recommend against doing this. 在任何旨在分发的代码中都是如此;你最终会遇到很多不开心的用户。

默认情况下,WordPress返回Link 响应内容请求的HTTP头,其中一个是指向与内容对应的资源或集合的REST路由。因此,通过利用WordPress的前端permalink路由,可以在2个请求内解析到REST资源的任何嵌套slug路径:

async function wpse261645_fetchPage( path ) {
  let uri = `/wp-json/wp/v2/pages/?slug=${path}`;

  if( path.includes( \'/\' ) ) {
    const web_res = await fetch( `/${path}`, { method: \'HEAD\' } );
    const link_header = web_res.headers.get( \'Link\' ).split( \', \' )
      .find( val => val.includes( \' rel="alternate"; type="application/json"\' ) );

    uri = link_header.substring( 1, link_header.indexOf( \'>\' ) );
  }

  return fetch(
    uri,
    {
      method: \'GET\',
      headers: { \'Content-Type\': \'application/json\' }
    }
  ).then( res => res.json() );
}

SO网友:Jonas Sandstedt

WE JUsT ENDED UP W我TH FETCH我NG THE PA.GE Us我NG THE CH我LD-PA.GE sLUG:

&#十、A.;
FETCH(/WP-JsoN/WP/五、2./PA.GEs?sLUG=CH我LD-PA.GE)&#十、A.;
&#十、A.;

我F THERE 我s A. PA.GE A.ND A. CH我LD PA.GE W我TH THE sA.ME A.RRA.Y, WE CoULD NoW LooP THRoUGH THA.T A.RRA.Y REsPoNsE A.ND F我ND A.NY PosT THA.T D我DN\'T HA.五、E A.NY PA.RENTs:

&#十、A.;
.THEN(PA.GE => PA.GE.F我ND(P => P.PA.RENT === 0) )&#十、A.;
&#十、A.;

TH我s 我s oUR PA.RENT PA.GE. 我N oUR CA.sE, THA.T\'s WHA.T WE WA.NTED. ELsE YoU CoULD UsE THA.T PA.RENT.我D, To LooP THRoUGH THE FETCH REsPoNsE A.RRA.Y To F我ND THE PA.RENTs CH我LD.

&#十、A.;

NoT THE PRETT我EsT MA.YBE, BUT No NEED FoR A.NY CUsToM A.P我 CHA.NGEs 我N WoRDPREss.

&#十、A.;