Jake Vanderwerf
2026-02-04 2127b1bdd73ecd2423e443992da4b442f5a3c1a3
inc/managers/RoleManager.php
@@ -438,4 +438,280 @@
         $this->registerRole($slug, $config);
      }
   }
   /******************************************************************
    * OWNABLE and MANAGABLE terms (ie: tattoo shops)
   ******************************************************************/
   /**
    * Grant ownership of a content taxonomy term
    * Owners have full control over the term and its members
    *
    * @param int $userID User ID
    * @param int $termID Term ID
    * @param string $taxonomy Taxonomy slug (without BASE)
    * @return bool Success
    */
   public function grantOwnership(int $userID, int $termID, string $taxonomy): bool
   {
      if (!get_userdata($userID) || !term_exists($termID)){
         return false;
      }
      $taxonomy = jvbNoBase($taxonomy);
      // Verify this is an ownable content taxonomy
      if (!Features::forTaxonomy($taxonomy)->has('is_content') ||
         !Features::forTaxonomy($taxonomy)->has('is_ownable')) {
         return false;
      }
      $user = get_userdata($userID);
      if (!$user) {
         return false;
      }
      // Grant both ownership and management
      $user->add_cap(BASE . 'can_own_' . $termID);
      $user->add_cap(BASE . 'can_manage_' . $termID);
      do_action(BASE . 'granted_ownership', $userID, $termID, $taxonomy);
      return true;
   }
   /**
    * Revoke ownership of a content taxonomy term
    *
    * @param int $userID User ID
    * @param int $termID Term ID
    * @param string $taxonomy Taxonomy slug (without BASE)
    * @return bool Success
    */
   public function revokeOwnership(int $userID, int $termID, string $taxonomy): bool
   {
      if (!get_userdata($userID) || !term_exists($termID)){
         return false;
      }
      $taxonomy = jvbNoBase($taxonomy);
      $user = get_userdata($userID);
      if (!$user) {
         return false;
      }
      $user->remove_cap(BASE . 'can_own_' . $termID);
      do_action(BASE . 'revoked_ownership', $userID, $termID, $taxonomy);
      return true;
   }
   /**
    * Grant management capabilities for a content taxonomy term
    * Managers can approve members and edit content but don't own the term
    *
    * @param int $userID User ID
    * @param int $termID Term ID
    * @param string $taxonomy Taxonomy slug (without BASE)
    * @return bool Success
    */
   public function grantManagement(int $userID, int $termID, string $taxonomy): bool
   {
      if (!get_userdata($userID) || !term_exists($termID)){
         return false;
      }
      $taxonomy = jvbNoBase($taxonomy);
      // Verify this is an ownable content taxonomy
      if (!Features::forTaxonomy($taxonomy)->has('is_content') ||
         !Features::forTaxonomy($taxonomy)->has('is_ownable')) {
         return false;
      }
      $user = get_userdata($userID);
      if (!$user) {
         return false;
      }
      $user->add_cap(BASE . 'can_manage_' . $termID);
      do_action(BASE . 'granted_management', $userID, $termID, $taxonomy);
      return true;
   }
   /**
    * Revoke management capabilities for a content taxonomy term
    *
    * @param int $userID User ID
    * @param int $termID Term ID
    * @param string $taxonomy Taxonomy slug (without BASE)
    * @return bool Success
    */
   public function revokeManagement(int $userID, int $termID, string $taxonomy): bool
   {
      if (!get_userdata($userID) || !term_exists($termID)){
         return false;
      }
      $taxonomy = jvbNoBase($taxonomy);
      $user = get_userdata($userID);
      if (!$user) {
         return false;
      }
      $user->remove_cap(BASE . 'can_manage_' . $termID);
      do_action(BASE . 'revoked_management', $userID, $termID, $taxonomy);
      return true;
   }
   /**
    * Check if user owns a term
    *
    * @param int $userID User ID
    * @param int $termID Term ID
    * @return bool
    */
   public function isOwner(int $userID, int $termID): bool
   {
      return user_can($userID, BASE . 'can_own_' . $termID);
   }
   /**
    * Check if user can manage a term (owner or manager)
    *
    * @param int $userID User ID
    * @param int $termID Term ID
    * @return bool
    */
   public function isManager(int $userID, int $termID): bool
   {
      return user_can($userID, BASE . 'can_manage_' . $termID) ||
         user_can($userID, BASE . 'can_own_' . $termID);
   }
   /**
    * Get all terms a user owns
    *
    * @param int $userID User ID
    * @param string|null $taxonomy Optional: filter by taxonomy
    * @return array Array of term IDs
    */
   public function getOwnedTerms(int $userID, ?string $taxonomy = null): array
   {
      $user = get_userdata($userID);
      if (!$user) {
         return [];
      }
      $owned = [];
      foreach ($user->allcaps as $cap => $value) {
         if ($value && strpos($cap, BASE . 'can_own_') === 0) {
            $termID = (int) str_replace(BASE . 'can_own_', '', $cap);
            if ($termID) {
               $owned[] = $termID;
            }
         }
      }
      // Filter by taxonomy if specified
      if ($taxonomy && !empty($owned)) {
         $taxonomy = jvbCheckBase($taxonomy);
         $filtered = [];
         foreach ($owned as $termID) {
            $term = get_term($termID);
            if ($term && !is_wp_error($term) && $term->taxonomy === $taxonomy) {
               $filtered[] = $termID;
            }
         }
         return $filtered;
      }
      return $owned;
   }
   /**
    * Get all terms a user can manage (owns or manages)
    *
    * @param int $userID User ID
    * @param string|null $taxonomy Optional: filter by taxonomy
    * @return array Array of term IDs
    */
   public function getManagedTerms(int $userID, ?string $taxonomy = null): array
   {
      $user = get_userdata($userID);
      if (!$user) {
         return [];
      }
      $managed = [];
      foreach ($user->allcaps as $cap => $value) {
         if ($value && (strpos($cap, BASE . 'can_manage_') === 0 ||
               strpos($cap, BASE . 'can_own_') === 0)) {
            $termID = (int) str_replace([BASE . 'can_manage_', BASE . 'can_own_'], '', $cap);
            if ($termID && !in_array($termID, $managed)) {
               $managed[] = $termID;
            }
         }
      }
      // Filter by taxonomy if specified
      if ($taxonomy && !empty($managed)) {
         $taxonomy = jvbCheckBase($taxonomy);
         $filtered = [];
         foreach ($managed as $termID) {
            $term = get_term($termID);
            if ($term && !is_wp_error($term) && $term->taxonomy === $taxonomy) {
               $filtered[] = $termID;
            }
         }
         return $filtered;
      }
      return $managed;
   }
   /**
    * Get all ownable taxonomies
    *
    * @return array Array of taxonomy slugs
    */
   public function getOwnableTaxonomies(): array
   {
      static $ownable = null;
      if ($ownable === null) {
         $ownable = [];
         foreach (JVB_TAXONOMY as $taxonomy => $config) {
            if (Features::forTaxonomy($taxonomy)->has('is_content') &&
               Features::forTaxonomy($taxonomy)->has('is_ownable')) {
               $ownable[] = $taxonomy;
            }
         }
      }
      return $ownable;
   }
   /**
    * Get all invitable taxonomies
    *
    * @return array Array of taxonomy slugs
    */
   public function getInvitableTaxonomies(): array
   {
      static $invitable = null;
      if ($invitable === null) {
         $invitable = [];
         foreach (JVB_TAXONOMY as $taxonomy => $config) {
            if (Features::forTaxonomy($taxonomy)->has('invitable')) {
               $invitable[] = $taxonomy;
            }
         }
      }
      return $invitable;
   }
}