function scoper_review_file_htaccess()
{
    $min_date = (int) scoper_get_site_option('file_htaccess_min_date');
    $last_regen = (int) scoper_get_option('file_htaccess_date');
    if (!$last_regen || $min_date > $last_regen) {
        scoper_flush_file_rules();
    }
}
function scoper_admin_section_restrictions($taxonomy)
{
    global $scoper, $scoper_admin;
    $tx = $scoper->taxonomies->get($taxonomy);
    if (empty($tx) || empty($tx->requires_term)) {
        wp_die(__('Invalid taxonomy', 'scoper'));
    }
    $is_administrator = is_administrator_rs($tx, 'user');
    if (!$scoper_admin->user_can_admin_terms($taxonomy)) {
        wp_die(__awp('Cheatin’ uh?'));
    }
    require_once dirname(__FILE__) . '/admin-bulk_rs.php';
    $role_assigner = init_role_assigner();
    $nonce_id = 'scoper-assign-roles';
    $role_codes = ScoperAdminBulk::get_role_codes();
    echo '<a name="scoper_top"></a>';
    // retrieve all terms to track hierarchical relationship, even though some may not be adminable by current user
    $val = ORDERBY_HIERARCHY_RS;
    $args = array('order_by' => $val);
    $all_terms = $scoper->get_terms($taxonomy, UNFILTERED_RS, COLS_ALL_RS, 0, $args);
    // =========================== Submission Handling =========================
    if (isset($_POST['rs_submit'])) {
        $err = ScoperAdminBulk::role_submission(TERM_SCOPE_RS, ROLE_RESTRICTION_RS, '', $taxonomy, $role_codes, '', $nonce_id);
        if (scoper_get_option('file_filtering')) {
            scoper_flush_file_rules();
        }
    } else {
        $err = 0;
    }
    // =========================== Prepare Data ===============================
    $tx_src = $scoper->data_sources->get($tx->source);
    if ($col_id = $tx_src->cols->id) {
        // determine which terms current user can admin
        if ($admin_terms = $scoper->get_terms($taxonomy, ADMIN_TERMS_FILTER_RS, COL_ID_RS)) {
            $admin_terms = array_fill_keys($admin_terms, true);
        }
    } else {
        $admin_terms = array();
    }
    // =========================== Display UI ===============================
    ?>


<div class="wrap agp-width97">
<?php 
    $tx_label = $tx->labels->singular_name;
    $src_label = $scoper->data_sources->member_property($tx->object_source, 'labels', 'singular_name');
    echo '<h2>' . sprintf(__('%s Restrictions', 'scoper'), $tx_label);
    echo '&nbsp;&nbsp;<span style="font-size: 0.6em; font-style: normal">(<a href="#scoper_notes">' . __('see notes', 'scoper') . '</a>)</span></h2>';
    if (scoper_get_option('display_hints')) {
        echo '<div class="rs-hint">';
        if ('category' == $taxonomy && scoper_get_otype_option('use_object_roles', 'post', 'post')) {
            printf(__('Reduce access by requiring some role(s) to be %1$s%2$s-assigned%3$s (or %4$s-assigned). Corresponding General Roles (whether assigned by WordPress or Role Scoper) are ignored.', 'scoper'), "<a href='admin.php?page=rs-{$taxonomy}-roles_t'>", $tx_label, '</a>', $src_label);
        } else {
            printf(__('Reduce access by requiring some role(s) to be %1$s%2$s-assigned%3$s. Corresponding General Role assignments are ignored.', 'scoper'), "<a href='admin.php?page=rs-{$taxonomy}-roles_t'>", $tx_label, '</a>');
        }
        echo '</div>';
    }
    if (!($role_defs_by_otype = $scoper->role_defs->get_for_taxonomy($tx->object_source, $taxonomy))) {
        echo '<br />' . sprintf(__('Role definition error (taxonomy: %s).', 'scoper'), $taxonomy);
        echo '</div>';
        return;
    }
    if (empty($admin_terms)) {
        echo '<br />' . sprintf(__('Either you do not have permission to administer any %s, or none exist.', 'scoper'), $tx->labels->name);
        echo '</div>';
        return;
    }
    ?>

<form action="" method="post" name="role_scope" id="role_assign">
<?php 
    wp_nonce_field($nonce_id);
    echo '<br /><div id="rs-term-scroll-links">';
    echo ScoperAdminBulkLib::taxonomy_scroll_links($tx, $all_terms, $admin_terms);
    echo '</div><hr />';
    // ============ Assignment Mode Selection Display ================
    // TODO: is Link Category label handled without workaround now?
    $tx_label = agp_strtolower($tx->labels->name);
    $tx_label_singular = agp_strtolower($tx->labels->singular_name);
    $parent_col = $tx_src->cols->parent;
    if (!$parent_col || !empty($tx->uses_standard_schema) && empty($tx->hierarchical)) {
        $assignment_modes = array(ASSIGN_FOR_ENTITY_RS => sprintf(__('for selected %s', 'scoper'), $tx_label));
    } else {
        $assignment_modes = array(ASSIGN_FOR_ENTITY_RS => sprintf(__('for selected %s', 'scoper'), $tx_label), ASSIGN_FOR_CHILDREN_RS => sprintf(__('for sub-%s of selected', 'scoper'), $tx_label), ASSIGN_FOR_BOTH_RS => sprintf(__('for selected and sub-%s', 'scoper'), $tx_label));
    }
    $max_scopes = array('term' => __('Restrict selected roles', 'scoper'), 'blog' => __('Unrestrict selected roles', 'scoper'));
    $args = array('max_scopes' => $max_scopes, 'scope' => TERM_SCOPE_RS);
    ScoperAdminBulk::display_inputs(ROLE_RESTRICTION_RS, $assignment_modes, $args);
    ScoperAdminBulk::item_tree_jslinks(ROLE_RESTRICTION_RS);
    // IE (6 at least) won't obey link color directive in a.classname CSS
    $ie_link_style = strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') !== false ? ' style="color:white;"' : '';
    $args = array('include_child_restrictions' => true, 'return_array' => true, 'role_type' => 'rs', 'force_refresh' => true);
    $strict_terms = $scoper->get_restrictions(TERM_SCOPE_RS, $taxonomy, $args);
    //strict_terms[taxonomy][role name][term_id] = array: terms which require Role Scoper assignment for specified role (user blog roles ignored, required caps may be supplied by scoper term role or object-specific assignment)
    // (for other terms, Role Scoper role assignment is optional (term role assignments will supplement blog caps)
    $editable_roles = array();
    foreach ($all_terms as $term) {
        $id = $term->{$col_id};
        foreach ($role_defs_by_otype as $object_type => $role_defs) {
            foreach (array_keys($role_defs) as $role_handle) {
                if ($role_assigner->user_has_role_in_term($role_handle, $taxonomy, $id, '', array('src_name' => $tx->object_source, 'object_type' => $object_type))) {
                    $editable_roles[$id][$role_handle] = true;
                }
            }
        }
    }
    $default_restrictions = $scoper->get_default_restrictions(TERM_SCOPE_RS);
    $default_strict_roles = !empty($default_restrictions[$taxonomy]) ? array_flip(array_keys($default_restrictions[$taxonomy])) : array();
    $table_captions = ScoperAdminUI::restriction_captions(TERM_SCOPE_RS, $tx, $tx_label_singular, $tx_label);
    $args = array('admin_items' => $admin_terms, 'editable_roles' => $editable_roles, 'default_strict_roles' => $default_strict_roles, 'ul_class' => 'rs-termlist', 'ie_link_style' => $ie_link_style, 'err' => $err, 'table_captions' => $table_captions);
    ScoperAdminBulk::item_tree(TERM_SCOPE_RS, ROLE_RESTRICTION_RS, $tx_src, $tx, $all_terms, '', $strict_terms, $role_defs_by_otype, $role_codes, $args);
    echo '<a href="#scoper_top">' . __('top', 'scoper') . '</a>';
    echo '<hr />';
    echo '<h4 style="margin-bottom:0.1em"><a name="scoper_notes"></a>' . __("Notes", 'scoper') . ':</h4><ul class="rs-notes">';
    $osrc = $scoper->data_sources->get($tx->object_source);
    if (empty($osrc->no_object_roles)) {
        echo '<li>';
        printf(__('Any %1$s Restriction causes the specified role to be granted only via %1$s Role assignment, regardless of these %2$s settings.', 'scoper'), $osrc->labels->singular_name, $tx->labels->singular_name);
        echo '</li></ul>';
    }
    ?>

</form>
</div>
<?php 
}
function scoper_expire_file_rules()
{
    if (IS_MU_RS) {
        scoper_update_option('file_htaccess_min_date', agp_time_gmt(), true);
    } else {
        if (did_action('scoper_init')) {
            scoper_flush_file_rules();
        } else {
            add_action('scoper_init', 'scoper_flush_file_rules');
        }
    }
}
function scoper_admin_object_restrictions($src_name, $object_type)
{
    global $scoper, $scoper_admin;
    if (!($src = $scoper->data_sources->get($src_name)) || !empty($src->no_object_roles) || !empty($src->taxonomy_only) || $src_name == 'group') {
        wp_die(__('Invalid data source', 'scoper'));
    }
    $is_administrator = is_administrator_rs($src, 'user');
    $role_bases = array();
    if (USER_ROLES_RS && ($is_administrator || $scoper_admin->user_can_admin_object($src_name, $object_type, 0, true))) {
        $role_bases[] = ROLE_BASIS_USER;
    }
    if (GROUP_ROLES_RS && ($is_administrator || $scoper_admin->user_can_admin_object($src_name, $object_type, 0, true) || current_user_can('manage_groups'))) {
        $role_bases[] = ROLE_BASIS_GROUPS;
    }
    if (empty($role_bases)) {
        wp_die(__awp('Cheatin&#8217; uh?'));
    }
    $otype = $scoper->data_sources->member_property($src_name, 'object_types', $object_type);
    require_once dirname(__FILE__) . '/admin-bulk_rs.php';
    require_once dirname(__FILE__) . '/admin_lib-bulk-parent_rs.php';
    $role_assigner = init_role_assigner();
    $nonce_id = 'scoper-assign-roles';
    $role_codes = ScoperAdminBulk::get_role_codes();
    echo '<a name="scoper_top"></a>';
    // ==== Process Submission =====
    $err = 0;
    if (isset($_POST['rs_submit'])) {
        $err = ScoperAdminBulk::role_submission(OBJECT_SCOPE_RS, ROLE_RESTRICTION_RS, '', $src_name, $role_codes, '', $nonce_id);
        if (scoper_get_option('file_filtering')) {
            scoper_flush_file_rules();
        }
    }
    ?>


<div class="wrap agp-width97">
<?php 
    $src_otype = isset($src->object_types) ? "{$src_name}:{$object_type}" : $src_name;
    $item_label_singular = $scoper_admin->interpret_src_otype($src_otype, 'singular_name');
    $item_label = $scoper_admin->interpret_src_otype($src_otype);
    echo '<h2>' . sprintf(__('%s Restrictions', 'scoper'), $item_label_singular) . '&nbsp;&nbsp;<span style="font-size: 0.6em; font-style: normal">(<a href="#scoper_notes">' . __('see notes', 'scoper') . '</a>)</span>' . '</h2>';
    if (scoper_get_option('display_hints')) {
        echo '<div class="rs-hint">';
        $link_open = "<a href='admin.php?page=rs-{$object_type}-roles'>";
        $uses_taxonomies = scoper_get_taxonomy_usage($src_name, $object_type);
        if ($uses_taxonomies && 1 == count($uses_taxonomies)) {
            $tx_display = $scoper->taxonomies->member_property(reset($uses_taxonomies), 'display_name');
            printf(__('Reduce access to a specific %1$s by requiring some role(s) to be %2$s%3$s-assigned%4$s. Corresponding WP-assigned Roles and RS-assigned General and %5$s Role assignments are ignored.', 'scoper'), $item_label_singular, $link_open, $item_label_singular, '</a>', $tx_display);
        } elseif (count($uses_taxonomies)) {
            printf(__('Reduce access to a specific %1$s by requiring some role(s) to be %2$s%3$s-assigned%4$s. Corresponding WP-assigned Roles and RS-assigned General and Term Role assignments are ignored.', 'scoper'), $item_label_singular, $link_open, $item_label_singular, '</a>');
        } else {
            printf(__('Reduce access to a specific %1$s by requiring some role(s) to be %2$s%3$s-assigned%4$s. Corresponding WP-assigned Roles and RS-assigned General Role assignments are ignored.', 'scoper'), $item_label_singular, $link_open, $item_label_singular, '</a>');
        }
        echo '</div>';
    }
    $ignore_hierarchy = !empty($otype->ignore_object_hierarchy);
    ?>


<form action="" method="post" name="role_assign" id="role_assign">
<?php 
    wp_nonce_field($nonce_id);
    // ============ Users / Groups and Assignment Mode Selection Display ================
    if (empty($src->cols->parent) || $ignore_hierarchy) {
        $assignment_modes = array(ASSIGN_FOR_ENTITY_RS => sprintf(__('for selected %s', 'scoper'), $item_label));
    } else {
        $assignment_modes = array(ASSIGN_FOR_ENTITY_RS => sprintf(__('for selected %s', 'scoper'), $item_label), ASSIGN_FOR_CHILDREN_RS => sprintf(__('for sub-%s of selected', 'scoper'), $item_label), ASSIGN_FOR_BOTH_RS => sprintf(__('for selected and sub-%s', 'scoper'), $item_label));
    }
    $max_scopes = array('object' => __('Restrict selected roles', 'scoper'), 'blog' => __('Unrestrict selected roles', 'scoper'));
    $args = array('max_scopes' => $max_scopes, 'scope' => OBJECT_SCOPE_RS);
    ScoperAdminBulk::display_inputs(ROLE_RESTRICTION_RS, $assignment_modes, $args);
    echo '<br />';
    $args = array('default_hide_empty' => !empty($otype->admin_default_hide_empty), 'hide_roles' => true, 'scope' => OBJECT_SCOPE_RS, 'src' => $src, 'otype' => $otype);
    ScoperAdminBulk::item_tree_jslinks(ROLE_RESTRICTION_RS, $args);
    // buffer prev/next caption for display with each obj type
    //$prevtext = _ x('prev', 'abbreviated link to previous item', 'scoper');
    //$nexttext = _ x('next', 'abbreviated link to next item', 'scoper');
    $prevtext = __('prev', 'scoper');
    $nexttext = __('next', 'scoper');
    $site_url = get_option('siteurl');
    $args = array('include_child_restrictions' => true, 'return_array' => true, 'role_type' => 'rs', 'force_refresh' => true);
    $strict_objects = $scoper->get_restrictions(OBJECT_SCOPE_RS, $src_name, $args);
    $object_names = array();
    $object_status = array();
    $listed_objects = array();
    $unlisted_objects = array();
    $col_id = $src->cols->id;
    $col_parent = isset($src->cols->parent) && !$ignore_hierarchy ? $src->cols->parent : '';
    $object_ids = array();
    if (isset($strict_objects['restrictions'])) {
        foreach (array_keys($strict_objects['restrictions']) as $role_handle) {
            $object_ids = $object_ids + array_keys($strict_objects['restrictions'][$role_handle]);
        }
    } elseif (isset($strict_objects['unrestrictions'])) {
        foreach (array_keys($strict_objects['unrestrictions']) as $role_handle) {
            $object_ids = $object_ids + array_keys($strict_objects['unrestrictions'][$role_handle]);
        }
    }
    $object_ids = array_flip(array_unique($object_ids));
    // Get the obj name, parent associated with each role (also sets $object_names, $unlisted objects)
    $listed_objects = ScoperAdminBulkParent::get_objects_info($object_ids, $object_names, $object_status, $unlisted_objects, $src, $otype, $ignore_hierarchy);
    if ($col_parent) {
        if ($listed_objects) {
            if ($unlisted_objects) {
                // query for any parent objects which don't have their own role assignments
                $listed_objects = ScoperAdminBulkParent::add_missing_parents($listed_objects, $unlisted_objects, $col_parent);
            }
            // convert keys from object ID to title+ID so we can alpha sort them
            $listed_objects_alpha = array();
            foreach (array_keys($listed_objects) as $id) {
                $listed_objects_alpha[$listed_objects[$id]->{$src->cols->name} . chr(11) . $id] = $listed_objects[$id];
            }
            uksort($listed_objects_alpha, "strnatcasecmp");
            $listed_objects = ScoperAdminBulkParent::order_by_hierarchy($listed_objects_alpha, $col_id, $col_parent);
        }
        // endif any listed objects
    } else {
        // endif doing object hierarchy
        if ($listed_objects) {
            // convert keys from object ID to title+ID so we can alpha sort them
            $listed_objects_alpha = array();
            foreach (array_keys($listed_objects) as $id) {
                $listed_objects_alpha[$listed_objects[$id]->{$src->cols->name} . chr(11) . $id] = $listed_objects[$id];
            }
            uksort($listed_objects_alpha, "strnatcasecmp");
            // convert to ordinal integer index
            $listed_objects = array_combine(array_keys(array_fill(0, count($listed_objects_alpha), true)), $listed_objects_alpha);
        }
    }
    if (!$is_administrator) {
        $cu_admin_results = ScoperAdminBulk::filter_objects_listing(ROLE_RESTRICTION_RS, $strict_objects, $src, $object_type);
    } else {
        $cu_admin_results = '';
    }
    // no need to filter admins
    // membuffer ids so user_can_admin_role() doesn't trigger a separate has_cap query for each one
    if ($cu_admin_results) {
        $scoper->listed_ids[$src_name] = $cu_admin_results;
    }
    global $scoper_admin;
    $role_display = array();
    $editable_roles = array();
    $role_defs_by_otype = array();
    $role_defs_by_otype[$object_type] = $scoper->role_defs->get_matching('rs', $src_name, $object_type);
    foreach (array_keys($role_defs_by_otype[$object_type]) as $role_handle) {
        $role_display[$role_handle] = $scoper->role_defs->get_abbrev($role_handle, OBJECT_UI_RS);
        if ($cu_admin_results && !is_user_administrator_rs()) {
            foreach (array_keys($cu_admin_results) as $object_id) {
                if ($scoper_admin->user_can_admin_role($role_handle, $object_id, $src_name, $object_type)) {
                    $editable_roles[$object_id][$role_handle] = true;
                }
            }
        }
    }
    $table_captions = ScoperAdminUI::restriction_captions(OBJECT_SCOPE_RS, '', $item_label_singular, $item_label);
    $args = array('admin_items' => $cu_admin_results, 'editable_roles' => $editable_roles, 'default_hide_empty' => !empty($otype->admin_default_hide_empty), 'ul_class' => 'rs-objlist', 'object_names' => $object_names, 'object_status' => $object_status, 'table_captions' => $table_captions, 'ie_link_style' => '', 'err' => $err);
    ScoperAdminBulk::item_tree(OBJECT_SCOPE_RS, ROLE_RESTRICTION_RS, $src, $otype, $listed_objects, '', $strict_objects, $role_defs_by_otype, $role_codes, $args);
    //ScoperAdminBulk::item_tree( OBJECT_SCOPE_RS, ROLE_ASSIGNMENT_RS, $src, $otype, $all_objects, $object_roles, $strict_objects, $role_defs_by_otype, $role_codes, $args);
    echo '<hr /><div style="background-color: white;"></div>';
    echo '<div class="rs-objlistkey">';
    $args = array('display_links' => true, 'display_restriction_key' => true);
    ScoperAdminUI::role_owners_key($otype, $args);
    echo '</div>';
    echo '</form><br /><h4 style="margin-bottom:0.1em"><a name="scoper_notes"></a>' . __("Notes", 'scoper') . ':</h4><ul class="rs-notes">';
    echo '<li>';
    printf(__('To edit all roles for any %1$s, click on the %1$s name.', 'scoper'), $otype->labels->singular_name);
    echo '</li>';
    echo '<li>';
    printf(__("To edit the %s via its default editor, click on the ID link.", 'scoper'), $otype->labels->singular_name);
    echo '</li>';
    if (!$is_administrator) {
        echo '<li>';
        printf(__('To enhance performance, the role editing checkboxes here may not include some roles which you can only edit due to your own %1$s-specific role. In such cases, click on the editing link to edit roles for the individual %1$s.', 'scoper'), $otype->labels->singular_name);
        echo '</li>';
    }
    echo '</ul>';
    echo '<a href="#scoper_top">' . __('top', 'scoper') . '</a>';
    ?>

</div>
<?php 
}
function scoper_mnt_save_object($src_name, $args, $object_id, $object = '')
{
    global $scoper, $scoper_admin;
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }
    // operations in this function only apply to main post save action, not revision save
    if ('post' == $src_name) {
        if (is_object($object) && !empty($object->post_type) && ('revision' == $object->post_type || 'auto-draft' == $object->post_status)) {
            return;
        }
    }
    static $saved_objects;
    if (!isset($saved_objects)) {
        $saved_objects = array();
    }
    if (isset($saved_objects[$src_name][$object_id])) {
        return;
    }
    $defaults = array('object_type' => '');
    $args = array_merge($defaults, (array) $args);
    extract($args);
    if ('post' == $src_name) {
        global $wpdb;
        $is_new_object = !get_post_meta($object_id, '_scoper_custom', true) && !$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->user2role2object_rs} WHERE scope = 'object' AND src_or_tx_name = 'post' AND obj_or_term_id = '{$object_id}'");
    } else {
        $is_new_object = true;
    }
    // for other data sources, we have to assume object is new unless it has a role or restriction stored already.
    if (empty($object_type)) {
        $object_type = cr_find_object_type($src_name, $object_id);
    }
    $saved_objects[$src_name][$object_id] = 1;
    // parent settings can affect the auto-assignment of propagating roles/restrictions
    $last_parent = 0;
    $set_parent = 0;
    if ($col_parent = $scoper->data_sources->member_property($src_name, 'cols', 'parent')) {
        if (in_array($GLOBALS['pagenow'], array('post.php', 'post-new.php', 'press-this.php'))) {
            if (isset($_POST[$col_parent])) {
                $set_parent = (int) $_POST[$col_parent];
            }
        } else {
            if (isset($object->{$col_parent})) {
                // this should also work for handling regular WP edit form, but leaving existing code above until further testing
                $set_parent = $object->{$col_parent};
            }
        }
    }
    // Determine whether this object is new (first time this RS filter has run for it, though the object may already be inserted into db)
    if ('post' == $src_name) {
        $post_type_obj = get_post_type_object($object_type);
        $last_parent = $object_id > 0 ? get_post_meta($object_id, '_scoper_last_parent', true) : '';
        if (is_numeric($last_parent)) {
            // not technically necessary, but an easy safeguard to avoid re-inheriting parent roles
            $is_new_object = false;
        }
        if (isset($set_parent) && $set_parent != $last_parent && ($set_parent || $last_parent)) {
            update_post_meta($object_id, '_scoper_last_parent', (int) $set_parent);
        }
    } else {
        // for other data sources, we have to assume object is new unless it has a role or restriction stored already.
        require_once dirname(__FILE__) . '/filters-admin-save-custom_rs.php';
        $is_new_object = ScoperCustomAdminFiltersSave::log_object_save($src_name, $object_id, $is_new_object, $col_parent, $set_parent);
    }
    // used here and in UI display to enumerate role definitions
    $role_defs = $scoper->role_defs->get_matching('rs', $src_name, $object_type);
    $role_handles = array_keys($role_defs);
    // Were roles / restrictions previously customized by direct edit?
    if ('post' == $src_name) {
        $roles_customized = $is_new_object ? false : get_post_meta($object_id, '_scoper_custom', true);
    } else {
        $roles_customized = false;
        if (!$is_new_object) {
            if ($custom_role_objects = (array) get_option("scoper_custom_{$src_name}")) {
                $roles_customized = isset($custom_role_objects[$object_id]);
            }
        }
    }
    $new_role_settings = false;
    $new_restriction_settings = false;
    $use_csv_entry = array(constant('ROLE_BASIS_USER') => scoper_get_option('user_role_assignment_csv'));
    // Were roles / restrictions custom-edited just now?
    if (!defined('XMLRPC_REQUEST')) {
        // Now determine if roles/restrictions have changed since the edit form load
        foreach ($role_defs as $role_handle => $role_def) {
            $role_code = 'r' . array_search($role_handle, $role_handles);
            // make sure the role assignment UI for this role was actually reviewed
            if (!isset($_POST["last_objscope_{$role_code}"])) {
                continue;
            }
            // did user change roles?
            if ($use_csv_entry[ROLE_BASIS_USER] && (!empty($_POST["{$role_code}u_csv"]) || !empty($_POST["p_{$role_code}u_csv"]))) {
                $new_role_settings = true;
            }
            // even if CSV entry is enabled, user removal is via checkbox
            $compare_vars = array("{$role_code}u" => "last_{$role_code}u", "{$role_code}g" => "last_{$role_code}g");
            if ($col_parent) {
                $compare_vars["p_{$role_code}u"] = "last_p_{$role_code}u";
                $compare_vars["p_{$role_code}g"] = "last_p_{$role_code}g";
            }
            foreach ($compare_vars as $var => $var_last) {
                $agents = isset($_POST[$var]) ? $_POST[$var] : array();
                $last_agents = !empty($_POST[$var_last]) ? explode("~", $_POST[$var_last]) : array();
                sort($agents);
                sort($last_agents);
                if ($last_agents != $agents) {
                    $new_role_settings = true;
                    break;
                }
            }
            // did user change restrictions?
            $compare_vars = array("objscope_{$role_code}" => "last_objscope_{$role_code}");
            if ($col_parent) {
                $compare_vars["objscope_children_{$role_code}"] = "last_objscope_children_{$role_code}";
            }
            foreach ($compare_vars as $var => $var_last) {
                $val = isset($_POST[$var]) ? $_POST[$var] : 0;
                $last_val = isset($_POST[$var_last]) ? $_POST[$var_last] : 0;
                if ($val != $last_val) {
                    $new_role_settings = true;
                    // NOTE: We won't re-inherit roles/restrictions following parent change if roles OR restrictions have been manually set
                    $new_restriction_settings = true;
                    // track manual restriction changes separately due to file filtering implications
                    break;
                }
            }
            if ($new_role_settings && $new_restriction_settings) {
                break;
            }
        }
        if ($new_role_settings && !$roles_customized) {
            $roles_customized = true;
            if ('post' == $src_name) {
                update_post_meta($object_id, '_scoper_custom', true);
            } else {
                $custom_role_objects[$object_id] = true;
                update_option("scoper_custom_{$src_name}", $custom_role_objects);
            }
        }
    }
    // endif user-modified roles/restrictions weren't already saved
    // apply default roles for new object
    if ($is_new_object && !$roles_customized) {
        // NOTE: this means we won't apply default roles if any roles have been manually assigned to the new object
        scoper_inherit_parent_roles($object_id, OBJECT_SCOPE_RS, $src_name, 0, $object_type);
    }
    // Inherit parent roles / restrictions, but only if a new parent is set and roles haven't been manually edited for this object
    if (isset($set_parent) && $set_parent != $last_parent && !$roles_customized) {
        // clear previously propagated role assignments
        if (!$is_new_object) {
            $args = array('inherited_only' => true, 'clear_propagated' => true);
            ScoperAdminLib::clear_restrictions(OBJECT_SCOPE_RS, $src_name, $object_id, $args);
            ScoperAdminLib::clear_roles(OBJECT_SCOPE_RS, $src_name, $object_id, $args);
        }
        // apply propagating roles, restrictions from selected parent
        if ($set_parent) {
            scoper_inherit_parent_roles($object_id, OBJECT_SCOPE_RS, $src_name, $set_parent, $object_type);
            scoper_inherit_parent_restrictions($object_id, OBJECT_SCOPE_RS, $src_name, $set_parent, $object_type);
        }
    }
    // endif new parent selection (or new object)
    // Roles/Restrictions were just edited manually, so store role settings (which may contain default roles even if no manual settings were made)
    if ($new_role_settings && !empty($_POST['rs_object_roles']) && (empty($_POST['action']) || 'autosave' != $_POST['action']) && !defined('XMLRPC_REQUEST')) {
        $role_assigner = init_role_assigner();
        $require_blogwide_editor = scoper_get_option('role_admin_blogwide_editor_only');
        if (('admin' != $require_blogwide_editor || is_user_administrator_rs()) && ('admin_content' != $require_blogwide_editor || is_content_administrator_rs())) {
            if ($object_type && $scoper_admin->user_can_admin_object($src_name, $object_type, $object_id)) {
                // store any object role (read/write/admin access group) selections
                $role_bases = array();
                if (GROUP_ROLES_RS) {
                    $role_bases[] = ROLE_BASIS_GROUPS;
                }
                if (USER_ROLES_RS) {
                    $role_bases[] = ROLE_BASIS_USER;
                }
                $set_roles = array_fill_keys($role_bases, array());
                $set_restrictions = array();
                $default_restrictions = $scoper->get_default_restrictions(OBJECT_SCOPE_RS);
                foreach ($role_defs as $role_handle => $role_def) {
                    if (!isset($role_def->valid_scopes[OBJECT_SCOPE_RS])) {
                        continue;
                    }
                    $role_code = 'r' . array_search($role_handle, $role_handles);
                    // make sure the role assignment UI for this role was actually reviewed
                    if (!isset($_POST["last_objscope_{$role_code}"])) {
                        continue;
                    }
                    foreach ($role_bases as $role_basis) {
                        $id_prefix = $role_code . substr($role_basis, 0, 1);
                        $for_entity_agent_ids = isset($_POST[$id_prefix]) ? $_POST[$id_prefix] : array();
                        $for_children_agent_ids = isset($_POST["p_{$id_prefix}"]) ? $_POST["p_{$id_prefix}"] : array();
                        // NOTE: restrict_roles, assign_roles functions validate current user roles before modifying assignments
                        // handle csv-entered agent names
                        if (!empty($use_csv_entry[$role_basis])) {
                            $csv_id = "{$id_prefix}_csv";
                            if ($csv_for_item = ScoperAdminLib::agent_ids_from_csv($csv_id, $role_basis)) {
                                $for_entity_agent_ids = array_merge($for_entity_agent_ids, $csv_for_item);
                            }
                            if ($csv_for_children = ScoperAdminLib::agent_ids_from_csv("p_{$csv_id}", $role_basis)) {
                                $for_children_agent_ids = array_merge($for_children_agent_ids, $csv_for_children);
                            }
                        }
                        $set_roles[$role_basis][$role_handle] = array();
                        if ($for_both_agent_ids = array_intersect($for_entity_agent_ids, $for_children_agent_ids)) {
                            $set_roles[$role_basis][$role_handle] = $set_roles[$role_basis][$role_handle] + array_fill_keys($for_both_agent_ids, ASSIGN_FOR_BOTH_RS);
                        }
                        if ($for_entity_agent_ids = array_diff($for_entity_agent_ids, $for_children_agent_ids)) {
                            $set_roles[$role_basis][$role_handle] = $set_roles[$role_basis][$role_handle] + array_fill_keys($for_entity_agent_ids, ASSIGN_FOR_ENTITY_RS);
                        }
                        if ($for_children_agent_ids = array_diff($for_children_agent_ids, $for_entity_agent_ids)) {
                            $set_roles[$role_basis][$role_handle] = $set_roles[$role_basis][$role_handle] + array_fill_keys($for_children_agent_ids, ASSIGN_FOR_CHILDREN_RS);
                        }
                    }
                    if (isset($default_restrictions[$src_name][$role_handle])) {
                        $max_scope = BLOG_SCOPE_RS;
                        $item_restrict = empty($_POST["objscope_{$role_code}"]);
                        $child_restrict = empty($_POST["objscope_children_{$role_code}"]);
                    } else {
                        $max_scope = OBJECT_SCOPE_RS;
                        $item_restrict = !empty($_POST["objscope_{$role_code}"]);
                        $child_restrict = !empty($_POST["objscope_children_{$role_code}"]);
                    }
                    $set_restrictions[$role_handle] = array('max_scope' => $max_scope, 'for_item' => $item_restrict, 'for_children' => $child_restrict);
                }
                $args = array('implicit_removal' => true, 'object_type' => $object_type);
                // don't record first-time storage of default roles as custom settings
                if (!$new_role_settings) {
                    $args['is_auto_insertion'] = true;
                }
                // Add or remove object role restrictions as needed (no DB update in nothing has changed)
                $role_assigner->restrict_roles(OBJECT_SCOPE_RS, $src_name, $object_id, $set_restrictions, $args);
                // Add or remove object role assignments as needed (no DB update if nothing has changed)
                foreach ($role_bases as $role_basis) {
                    $role_assigner->assign_roles(OBJECT_SCOPE_RS, $src_name, $object_id, $set_roles[$role_basis], $role_basis, $args);
                }
            }
            // endif object type is known and user can admin this object
        }
        // end if current user is an Administrator, or doesn't need to be
    }
    //endif roles were manually edited by user (and not autosave)
    // if post status has changed to or from private (or is a new private post), flush htaccess file rules for file attachment filtering
    if (scoper_get_option('file_filtering')) {
        /*
        if ( $new_restriction_settings ) {
        	$maybe_flush_file_rules = true;
        } else {
        	$maybe_flush_file_rules = false;
        		
        	global $scoper_admin_filters;
        	
        	if ( isset( $scoper_admin_filters->last_post_status[$object_id] ) ) {
        		$new_status = ( isset($_POST['post_status']) ) ? $_POST['post_status'] : ''; // assume for now that XML-RPC will not modify post status
        		
        		if ( $scoper_admin_filters->last_post_status[$object_id] != $new_status )
        			if ( ( 'private' == $new_status ) || ( 'private' == $scoper_admin_filters->last_post_status[$object_id] ) )
        				$maybe_flush_file_rules = true;
        		
        	} elseif ( isset($_POST['post_status']) && ( 'private' == $_POST['post_status'] ) )
        		$maybe_flush_file_rules = true;	
        }
        */
        //if ( $maybe_flush_file_rules ) {
        global $wpdb;
        if (scoper_get_var("SELECT ID FROM {$wpdb->posts} WHERE post_type = 'attachment' AND post_parent = '{$object_id}' LIMIT 1")) {
            // no need to flush file rules unless this post has at least one attachment
            scoper_flush_file_rules();
        }
        //}
    }
    if ('post' == $src_name && $post_type_obj->hierarchical) {
        $_post = get_post($object_id);
        if ('auto-draft' != $_post->post_status) {
            delete_option('scoper_page_ancestors');
            scoper_flush_cache_groups('get_pages');
        }
    }
    // need this to make metabox captions update in first refresh following edit & save
    if (is_admin() && isset($GLOBALS['scoper_admin_filters_item_ui'])) {
        $GLOBALS['scoper_admin_filters_item_ui']->act_tweak_metaboxes();
    }
    // possible TODO: remove other conditional calls since we're doing it here on every save
    scoper_flush_results_cache();
}
 function resync_file_rules()
 {
     // Don't allow this to execute too frequently, to prevent abuse or accidental recursion
     if (agp_time_gmt() - get_option('last_htaccess_resync_rs') > 30) {
         update_option('last_htaccess_resync_rs', agp_time_gmt());
         // Only the files / uploads .htaccess for current blog is regenerated
         scoper_flush_file_rules();
         usleep(10000);
         // Allow 10 milliseconds for server to regather itself following .htaccess update
     }
 }