/ */ class P4_Group extends P4_Spec_PluralAbstract { const FETCH_BY_MEMBER = 'member'; const FETCH_INDIRECT = 'indirect'; const FETCH_BY_NAME = 'name'; protected static $_specType = 'group'; protected static $_idField = 'Group'; protected static $_accessors = array( 'MaxResults' => 'getMaxResults', 'MaxScanRows' => 'getMaxScanRows', 'MaxLockTime' => 'getMaxLockTime', 'Timeout' => 'getTimeout', 'Subgroups' => 'getSubgroups', 'Owners' => 'getOwners', 'Users' => 'getUsers', ); protected static $_mutators = array( 'MaxResults' => 'setMaxResults', 'MaxScanRows' => 'setMaxScanRows', 'MaxLockTime' => 'setMaxLockTime', 'Timeout' => 'setTimeout', 'Subgroups' => 'setSubgroups', 'Owners' => 'setOwners', 'Users' => 'setUsers', ); /** * Get all Groups from Perforce. Adds filtering options. * * @param array $options optional - array of options to augment fetch behavior. * supported options are: * FETCH_MAXIMUM - set to integer value to limit to the first * 'max' number of entries. * *Note: Limits imposed client side. * FETCH_BY_MEMBER - get groups containing passed group or * user (no wildcards). * FETCH_INDIRECT - used with FETCH_BY_MEMBER to also list * indirect matches. * FETCH_BY_NAME - get the named group. esstentially a 'fetch' * but performed differently (no wildcards). * *Note: not compatible with FETCH_BY_MEMBER * or FETCH_INDIRECT * * @param P4_Connection_Interface $connection optional - a specific connection to use. * @return P4_Model_Iterator all records of this type. */ public static function fetchAll($options = array(), P4_Connection_Interface $connection = null) { // the 'groups' command produces very unique output; we have taken over the parent // function to handle it here. // if no connection given, use default. $connection = $connection ?: static::getDefaultConnection(); // get command to use $command = static::_getFetchAllCommand(); // get fstat flags for given fetch options. $flags = static::_getFetchAllFlags($options); // fetch all specs. $result = $connection->run($command, $flags); // we manually implement FETCH_MAXIMUM for base class compatibility // pull out the passed value if present for later use. if (isset($options[self::FETCH_MAXIMUM])) { $max = (int) $options[self::FETCH_MAXIMUM]; } // 'groups' produces the below data-block for each owner/user/sub-group // array ( // 'user' => 'tester', // 'group' => 'test', // 'isSubGroup' => '0', // 'isOwner' => '0', // 'isUser' => '1', // 'maxResults' => '0', // 'maxScanRows' => '0', // 'maxLockTime' => '0', // 'timeout' => '43200', // 'isValidUser' => '1', // ) // convert result data to spec objects. $specs = new P4_Model_Iterator; $data = $result->getData(); // keep outer loop running so long as we have data (and haven't hit max results) while (current($data) !== false) { $element = current($data); $id = $element['group']; $values = array( 'Group' => $id, 'MaxResults' => $element['maxResults'], 'MaxScanRows' => $element['maxScanRows'], 'MaxLockTime' => $element['maxLockTime'], 'Timeout' => $element['timeout'], 'Subgroups' => array(), 'Owners' => array(), 'Users' => array() ); // loop-de-loop // collect all data blocks for a given group while ($element !== false && $element['group'] == $id) { // defer to lazy load if FETCH_BY_MEMBER option was used // as result data doesn't contain all the values if (isset($options[self::FETCH_BY_MEMBER])) { $values = array('Group' => $id); } else { if ($element['isSubGroup'] == 1) { $values['Subgroups'][] = $element['user']; } if ($element['isOwner'] == 1) { $values['Owners'][] = $element['user']; } if ($element['isUser'] == 1) { $values['Users'][] = $element['user']; } } $element = next($data); } // at this point we have all of the groups details // populate a spec and add it to the iterator $spec = new static($connection); $spec->_setValues($values) ->_deferPopulate(); $specs[] = $spec; // stop looping if we reach 'FETCH_MAXIMUM' if (isset($max) && count($specs) == $max) { break; } } return $specs; } /** * Save this spec to Perforce. Extend parent to throw if group is 'empty' * * @param bool $owner save the group as a group owner * @return P4_SpecAbstract provides a fluent interface * @throws P4_Spec_Exception if group is empty */ public function save($owner = false) { if ($this->isEmpty()) { throw new P4_Spec_Exception("Cannot save. Group is empty."); } // ensure all required fields have values. $this->_validateRequiredFields(); $flags = array('-i'); if ($owner) { $flags[] = '-a'; } $this->getConnection()->run( static::_getSpecType(), $flags, $this->_getValues() ); // should re-populate (server may change values). $this->_deferPopulate(true); return $this; } /** * Determine if the given group id exists. * * @param string $id the id to check for. * @param P4_Connection_Interface $connection optional - a specific connection to use. * @return bool true if the given id matches an existing group. */ public static function exists($id, P4_Connection_Interface $connection = null) { // check id for valid format if (!static::_isValidId($id)) { return false; } $groups = static::fetchAll( array( static::FETCH_BY_NAME => $id, static::FETCH_MAXIMUM => 1 ), $connection ); return (bool) count($groups); } /** * Determines if this group is 'empty'. * * A group is considered empty if no entries are present in: * -SubGroups * -Owners * -Users * * Values in Group (id), MaxResults, MaxScanRows, MaxLockTime do not * count towards 'emptiness'. * * @return bool True if group is empty, False otherwise */ public function isEmpty() { $entries = count($this->getValue('Subgroups')) + count($this->getValue('Owners')) + count($this->getValue('Users')); return !(bool) $entries; } /** * The maximum number of results that members of this group can access * from the server from a single command. The default value is null. * * Will be an integer >0, null (if 'unset') or the string 'unlimited' * * @return null|int|string Null if unset, integer >0 or 'unlimited' */ public function getMaxResults() { return $this->_getMaxValue('MaxResults'); } /** * Set the MaxResults for this group. See getMaxResults for more info. * * The string 'unset' may be passed in place of null for convienence. * * @param null|int|string $max null (or 'unset'), integer >0 or 'unlimited' * @return P4_Group provides fluent interface. */ public function setMaxResults($max) { return $this->_setMaxValue('MaxResults', $max); } /** * The maximum number of rows that members of this group can scan from * the server from a single command. The default value is null. * * Will be an integer >0, null (if 'unset') or the string 'unlimited' * * @return null|int|string Null if unset, integer >0 or 'unlimited' */ public function getMaxScanRows() { return $this->_getMaxValue('MaxScanRows'); } /** * Set the MaxScanRows for this group. See getMaxScanRows for more info. * * The string 'unset' may be passed in place of null for convienence. * * @param null|int|string $max null (or 'unset'), integer >0 or 'unlimited' * @return P4_Group provides fluent interface. */ public function setMaxScanRows($max) { return $this->_setMaxValue('MaxScanRows', $max); } /** * The maximum length of time (in milliseconds) that any one operation can * lock any database table when scanning data. The default value is null. * * Will be an integer >0, null (if 'unset') or the string 'unlimited' * * @return null|int|string Null if unset, integer >0 or 'unlimited' */ public function getMaxLockTime() { return $this->_getMaxValue('MaxLockTime'); } /** * Set the MaxLockTime for this group. See getMaxLockTime for more info. * * The string 'unset' may be passed in place of null for convienence. * * @param null|int|string $max null (or 'unset'), integer >0 or 'unlimited' * @return P4_Group provides fluent interface. */ public function setMaxLockTime($max) { return $this->_setMaxValue('MaxLockTime', $max); } /** * The duration (in seconds) of the validity of a session ticket created * by p4 login. The default value is 43200 seconds (12 hours). * For tickets that do not expire, will return 'unlimited'. * * Will be an integer >0, null (if 'unset') or the string 'unlimited' * * @return null|int|string Null if unset, integer >0 or 'unlimited' */ public function getTimeout() { return $this->_getMaxValue('Timeout'); } /** * Set the Timeout for this group. See getTimeout for more info. * * The string 'unset' may be passed in place of null for convienence. * * @param null|int|string $timeout null (or 'unset'), integer >0 or 'unlimited' * @return P4_Group provides fluent interface. */ public function setTimeout($timeout) { return $this->_setMaxValue('Timeout', $timeout); } /** * Returns the sub-groups for this group. * * @return array subgroups belonging to this group */ public function getSubgroups() { return $this->_getValue('Subgroups') ?: array(); } /** * Set the sub-groups for this group. * Expects an array containing group names or P4_Group objects. * * @param array $subgroups array of group names or P4_Group objects * @return P4_Group provides fluent interface. */ public function setSubgroups($subgroups) { if (!is_array($subgroups)) { throw new InvalidArgumentException( 'Subgroups must be specified as an array.' ); } foreach ($subgroups as &$group) { // normalize to strings if ($group instanceof P4_Group) { $group = $group->getId(); } if (!static::_isValidId($group)) { throw new InvalidArgumentException( 'Individual sub-groups must be a valid ID in either string or P4_Group format.' ); } } return $this->_setValue('Subgroups', $subgroups); } /** * Adds the passed group to the end of the current sub-groups. * * @param string|P4_Group $group new group to add * @return P4_Group provides fluent interface. */ public function addSubgroup($group) { $subgroups = $this->getSubgroups(); $subgroups[] = $group; return $this->setSubgroups($subgroups); } /** * Returns the owners for this group. * * @return array owners belonging to this group */ public function getOwners() { return $this->_getValue('Owners') ?: array(); } /** * Set the owners for this group. * Expects an array containing user names or P4_User objects. * * @param array $owners array of user names or P4_User objects * @return P4_Group provides fluent interface. */ public function setOwners($owners) { if (!is_array($owners)) { throw new InvalidArgumentException( 'Owners must be specified as an array.' ); } foreach ($owners as &$owner) { // normalize to strings if ($owner instanceof P4_User) { $owner = $owner->getId(); } if (!static::_isValidUserId($owner)) { throw new InvalidArgumentException( 'Individual owners must be a valid ID in either string or P4_User format.' ); } } return $this->_setValue('Owners', $owners); } /** * Adds the passed owner to the end of the current owners. * * @param string|P4_User $owner new owner to add * @return P4_Group provides fluent interface. */ public function addOwner($owner) { $owners = $this->getOwners(); $owners[] = $owner; return $this->setOwners($owners); } /** * Returns the users for this group. * * @return array users belonging to this group */ public function getUsers() { return $this->_getValue('Users') ?: array(); } /** * Set the users for this group. * Expects an array containing user names or P4_User objects. * * @param array $users array of user names or P4_User objects * @return P4_Group provides fluent interface. */ public function setUsers($users) { if (!is_array($users)) { throw new InvalidArgumentException( 'Users must be specified as an array.' ); } foreach ($users as &$user) { // normalize to strings if ($user instanceof P4_User) { $user = $user->getId(); } if (!static::_isValidUserId($user)) { throw new InvalidArgumentException( 'Individual users must be a valid ID in either string or P4_User format.' ); } } return $this->_setValue('Users', $users); } /** * Adds the passed user to the end of the current users. * * @param string|P4_User $user new user to add * @return P4_Group provides fluent interface. */ public function addUser($user) { $users = $this->getUsers(); $users[] = $user; return $this->setUsers($users); } /** * Get the value for a 'max' style field * (one of MaxResults, MaxScanRows, MaxLockTime and Timeout). * * @param string $field Name of the field to get the value from * @return null|int|string null (if 'unset'), integer >0 or 'unlimited' */ protected function _getMaxValue($field) { $max = $this->_getValue($field); // translate the string 'unset' to null if ($max === 'unset') { return null; } // integers come back from perforce as strings // casting to an int, then back to a string screens out non-digit // characters and allows for a 'pure digit' check. if ($max == (string)(int)$max) { return (int)$max; } return $max; } /** * Check if the given id is in a valid format for group specs. * * @param string $id the id to check * @return bool true if id is valid, false otherwise */ protected static function _isValidId($id) { $validator = new P4_Validate_GroupName; return $validator->isValid($id); } /** * Check if the given id is in a valid format for user specs. * * @param string $id the id to check * @return bool true if id is valid, false otherwise */ protected static function _isValidUserId($id) { $validator = new P4_Validate_UserName; return $validator->isValid($id); } /** * Set the value for a 'max' style field * (one of MaxResults, MaxScanRows, MaxLockTime and Timeout). * * Valid 'max' inputs are: * -null, gets converted to 'unset' * -the string 'unset' * -an integer greater than 0 * -the string 'unlimited' * * @param string $field Name of the field to set value on * @param null|int|string $max null (or 'unset'), integer >0 or 'unlimited' * @return P4_Group provides a fluent interface * @throws InvalidArgumentException If input is of incorrect type of format */ protected function _setMaxValue($field, $max) { // ensure input is in the ballpark if (!is_null($max) && !is_int($max) && !is_string($max)) { throw new InvalidArgumentException( "Type of input must be one of: null, int, string" ); } // convert null to 'unset' if ($max === null) { $max = 'unset'; } // verify string format input matches expected value if (is_string($max) && $max !== 'unlimited' && $max !== 'unset') { throw new InvalidArgumentException( "For string input, only the values 'unlimited' and 'unset' are valid." ); } // ensure integer input is greater than zero if (is_int($max) && $max <= 0) { throw new InvalidArgumentException( "For integer input, only values greater than zero are valid." ); } return $this->_setValue($field, $max); } /** * Produce set of flags for the spec list command, given fetch all options array. * Extends parent to add support for filter option. * * @param array $options array of options to augment fetch behavior. * see fetchAll for documented options. * @return array set of flags suitable for passing to spec list command. */ protected static function _getFetchAllFlags($options) { // clear FETCH_MAXIMUM if present as we handle it seperately unset($options[self::FETCH_MAXIMUM]); $flags = parent::_getFetchAllFlags($options); if (isset($options[static::FETCH_BY_NAME])) { $name = $options[static::FETCH_BY_NAME]; if (!static::_isValidId($name) && !static::_isValidUserId($name)) { throw new InvalidArgumentException( 'Filter by Name expects a valid group id.' ); } if (isset($options[static::FETCH_INDIRECT]) || isset($options[static::FETCH_BY_MEMBER]) ) { throw new InvalidArgumentException( 'Filter by Name is not compatible with Fetch by Member or Fetch Indirect.' ); } $flags[] = '-v'; $flags[] = $name; } if (isset($options[static::FETCH_INDIRECT], $options[static::FETCH_BY_MEMBER])) { $flags[] = '-i'; } if (isset($options[static::FETCH_BY_MEMBER])) { $member = $options[static::FETCH_BY_MEMBER]; if (!static::_isValidId($member) && !static::_isValidUserId($member)) { throw new InvalidArgumentException( 'Filter by Member expects a valid group or username.' ); } $flags[] = $member; } return $flags; } /** * This function is not utilized by P4_Group as our result format is incompatible. * Any attempt to call this function results in an exception. * * @param array $listEntry a single spec entry from spec list output. * @param array $flags the flags that were used for this 'fetchAll' run. * @param P4_Connection_Interface $connection a specific connection to use. * @return P4_Spec_PluralAbstract a (partially) populated instance of this spec class. * @throws BadFunctionCallException On any use of this function in this class. */ protected static function _fromSpecListEntry($listEntry, $flags, P4_Connection_Interface $connection) { throw new BadFunctionCallException( 'From Spec List Entry is not implemented in the P4_Group class.' ); } }