<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[34091] trunk: Fail gracefully when checking mapped cap against unregistered post type.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="https://core.trac.wordpress.org/changeset/34091">34091</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"https://core.trac.wordpress.org/changeset/34091","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>boonebgorges</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2015-09-12 21:26:57 +0000 (Sat, 12 Sep 2015)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Fail gracefully when checking mapped cap against unregistered post type.

Post type objects are reponsible for mapping their capabilities to core caps.
As a result, when the post type is no longer registered, the caps are no
longer mapped. This causes problems when a post is left in the database after
the post type is no longer present, and WP does an 'edit_post' or other cap
check against it: a PHP notice is thrown, and the cap check always fails.

As a more graceful fallback, we map all post-type-dependent caps onto
'edit_others_posts', which allows highly privileged users to be able to
access orphaned content (such as comments belonging to disabled post types),
while minimizing the possibility of unintended privilege escalation.

We also add a `_doing_it_wrong()` notice, so that developers and site
administrators are aware that the cap mapping is failing in the absence of
the registered post type.

Props mitchoyoshitaka, DrewAPicture, imath, codeelite, boonebgorges, nofearinc, SergeyBiryukov, jorbin, dlh.
Fixes <a href="https://core.trac.wordpress.org/ticket/16956">#16956</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunksrcwpincludescapabilitiesfunctionsphp">trunk/src/wp-includes/capabilities-functions.php</a></li>
<li><a href="#trunktestsphpunittestsusercapabilitiesphp">trunk/tests/phpunit/tests/user/capabilities.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunksrcwpincludescapabilitiesfunctionsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/src/wp-includes/capabilities-functions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/src/wp-includes/capabilities-functions.php  2015-09-12 21:05:14 UTC (rev 34090)
+++ trunk/src/wp-includes/capabilities-functions.php    2015-09-12 21:26:57 UTC (rev 34091)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -53,6 +53,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $post_type = get_post_type_object( $post->post_type );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( ! $post_type ) {
+                       /* translators: 1: post type, 2: capability name */
+                       _doing_it_wrong( __FUNCTION__, sprintf( __( 'The post type %1$s is not registered, so it may not be reliable to check the capability "%2$s" against a post of that type.' ), $post->post_type, $cap ), '4.4.0' );
+                       $caps[] = 'edit_others_posts';
+                       break;
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! $post_type->map_meta_cap ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $caps[] = $post_type->cap->$cap;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -101,6 +107,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $post_type = get_post_type_object( $post->post_type );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( ! $post_type ) {
+                       /* translators: 1: post type, 2: capability name */
+                       _doing_it_wrong( __FUNCTION__, sprintf( __( 'The post type %1$s is not registered, so it may not be reliable to check the capability "%2$s" against a post of that type.' ), $post->post_type, $cap ), '4.4.0' );
+                       $caps[] = 'edit_others_posts';
+                       break;
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! $post_type->map_meta_cap ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $caps[] = $post_type->cap->$cap;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -143,6 +155,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $post_type = get_post_type_object( $post->post_type );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( ! $post_type ) {
+                       /* translators: 1: post type, 2: capability name */
+                       _doing_it_wrong( __FUNCTION__, sprintf( __( 'The post type %1$s is not registered, so it may not be reliable to check the capability "%2$s" against a post of that type.' ), $post->post_type, $cap ), '4.4.0' );
+                       $caps[] = 'edit_others_posts';
+                       break;
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! $post_type->map_meta_cap ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        $caps[] = $post_type->cap->$cap;
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -169,6 +187,12 @@
</span><span class="cx" style="display: block; padding: 0 10px">        case 'publish_post':
</span><span class="cx" style="display: block; padding: 0 10px">                $post = get_post( $args[0] );
</span><span class="cx" style="display: block; padding: 0 10px">                $post_type = get_post_type_object( $post->post_type );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                if ( ! $post_type ) {
+                       /* translators: 1: post type, 2: capability name */
+                       _doing_it_wrong( __FUNCTION__, sprintf( __( 'The post type %1$s is not registered, so it may not be reliable to check the capability "%2$s" against a post of that type.' ), $post->post_type, $cap ), '4.4.0' );
+                       $caps[] = 'edit_others_posts';
+                       break;
+               }
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $caps[] = $post_type->cap->publish_posts;
</span><span class="cx" style="display: block; padding: 0 10px">                break;
</span></span></pre></div>
<a id="trunktestsphpunittestsusercapabilitiesphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: trunk/tests/phpunit/tests/user/capabilities.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- trunk/tests/phpunit/tests/user/capabilities.php   2015-09-12 21:05:14 UTC (rev 34090)
+++ trunk/tests/phpunit/tests/user/capabilities.php     2015-09-12 21:26:57 UTC (rev 34091)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -994,4 +994,27 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                $this->assertFalse( current_user_can( 'edit_user', $super_admin->ID ) );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+       /**
+        * @ticket 16956
+        */
+       function test_require_edit_others_posts_if_post_type_doesnt_exist() {
+               register_post_type( 'existed' );
+               $post_id = $this->factory->post->create( array( 'post_type' => 'existed' ) );
+               _unregister_post_type( 'existed' );
+
+               $subscriber_id = $this->factory->user->create( array( 'role' => 'subscriber' ) );
+               $editor_id = $this->factory->user->create( array( 'role' => 'editor' ) );
+
+               $this->setExpectedIncorrectUsage( 'map_meta_cap' );
+               foreach ( array( 'delete_post', 'edit_post', 'read_post', 'publish_post' ) as $cap ) {
+                       wp_set_current_user( $subscriber_id );
+                       $this->assertSame( array( 'edit_others_posts' ), map_meta_cap( $cap, $subscriber_id, $post_id ) );
+                       $this->assertFalse( current_user_can( $cap, $post_id ) );
+
+                       wp_set_current_user( $editor_id );
+                       $this->assertSame( array( 'edit_others_posts' ), map_meta_cap( $cap, $editor_id, $post_id ) );
+                       $this->assertTrue( current_user_can( $cap, $post_id ) );
+               }
+       }
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span></span></pre>
</div>
</div>

</body>
</html>