<?php /** * Abstracts operations against Perforce labels. * * @copyright 2011 Perforce Software. All rights reserved. * @license Please see LICENSE.txt in top-level folder of this distribution. * @version <release>/<patch> */ namespace P4\Spec; use P4\Validate; use P4\Connection\ConnectionInterface; class Label extends PluralAbstract { const SPEC_TYPE = 'label'; const ID_FIELD = 'Label'; const FETCH_BY_NAME = 'name'; const FETCH_BY_OWNER = 'owner'; protected $fields = array( 'Update' => array( 'accessor' => 'getUpdateDateTime' ), 'Access' => array( 'accessor' => 'getAccessDateTime' ), 'Owner' => array( 'accessor' => 'getOwner', 'mutator' => 'setOwner' ), 'Description' => array( 'accessor' => 'getDescription', 'mutator' => 'setDescription' ), 'Options' => array( 'accessor' => 'getOptions', 'mutator' => 'setOptions' ), 'Revision' => array( 'accessor' => 'getRevision', 'mutator' => 'setRevision' ), 'View' => array( 'accessor' => 'getView', 'mutator' => 'setView' ) ); /** * Get all Labels 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. * FETCH_BY_NAME - set to label name pattern (e.g. 'labe*'). * FETCH_BY_OWNER - set to owner's username (e.g. 'jdoe'). * * @param ConnectionInterface $connection optional - a specific connection to use. * @return \P4\Model\Fielded\Iterator all records of this type. */ public static function fetchAll($options = array(), ConnectionInterface $connection = null) { // simply return parent - method exists to document options. return parent::fetchAll($options, $connection); } /** * Determine if the given label id exists. * * @param string $id the id to check for. * @param ConnectionInterface $connection optional - a specific connection to use. * @return bool true if the given id matches an existing label. */ public static function exists($id, ConnectionInterface $connection = null) { // check id for valid format if (!static::isValidId($id)) { return false; } $labels = static::fetchAll( array( static::FETCH_BY_NAME => $id, static::FETCH_MAXIMUM => 1 ), $connection ); return (bool) count($labels); } /** * Get the last update time for this label spec. * This value is read only, no setUpdateTime function is provided. * * If this is a brand new spec, null will be returned in lieu of a time. * * @return string|null Date/Time of last update, formatted "2009/11/23 12:57:06" or null */ public function getUpdateDateTime() { return $this->getRawValue('Update'); } /** * Get the last access time for this label spec. * This value is read only, no setAccessTime function is provided. * * If this is a brand new spec, null will be returned in lieu of a time. * * @return string|null Date/Time of last access, formatted "2009/11/23 12:57:06" or null */ public function getAccessDateTime() { return $this->getRawValue('Access'); } /** * Get the owner of this label. * * @return string|null User who owns this record. */ public function getOwner() { return $this->getRawValue('Owner'); } /** * Set the owner of this label to passed value. * * @param string|User|null $owner A string containing username * @return Label provides a fluent interface. * @throws \InvalidArgumentException Owner is incorrect type. */ public function setOwner($owner) { if ($owner instanceof User) { $owner = $owner->getId(); } if (!is_string($owner) && !is_null($owner)) { throw new \InvalidArgumentException('Owner must be a string, P4\Spec\User or null.'); } return $this->setRawValue('Owner', $owner); } /** * Get the description for this label. * * @return string|null description for this label. */ public function getDescription() { return $this->getRawValue('Description'); } /** * Set a description for this label. * * @param string|null $description description for this label. * @return Label provides a fluent interface. * @throws \InvalidArgumentException Description is incorrect type. */ public function setDescription($description) { if (!is_string($description) && !is_null($description)) { throw new \InvalidArgumentException('Description must be a string or null.'); } return $this->setRawValue('Description', $description); } /** * Get options for this label. * * @return string|null options which are set on this label ('locked' or 'unlocked'). */ public function getOptions() { return $this->getRawValue('Options'); } /** * Set the options for this label. See getOptions for expected values. * * @param string|null $options options to set on this label. * @return Label provides a fluent interface. * @throws \InvalidArgumentException Options are incorrect type. */ public function setOptions($options) { if (!is_string($options) && !is_null($options)) { throw new \InvalidArgumentException('Options must be a string or null.'); } return $this->setRawValue('Options', $options); } /** * Get the revision setting for this label. * * @return string|null Revision setting for this label. */ public function getRevision() { $revision = $this->getRawValue('Revision'); // strip quotes if needed if (is_string($revision) && substr($revision, 0, 1) === '"' && substr($revision, -1) === '"') { return substr($revision, 1, -1); } return $revision; } /** * Set the revision setting for this label. * * @param string|null $revision Revision setting for this label. * @return Label provides a fluent interface. * @throws \InvalidArgumentException revision is incorrect type. */ public function setRevision($revision) { if (!is_string($revision) && !is_null($revision)) { throw new \InvalidArgumentException('Revision must be a string or null.'); } // quote string values; leaves null values alone if (is_string($revision)) { $revision = '"' . $revision . '"'; } return $this->setRawValue('Revision', $revision); } /** * Get the view for this label. * View entries will be returned as an array of strings e.g.: * array ( * 0 => '//depot/example/with space/...', * 1 => '//depot/alternate/example/*' * ) * Labels view is fairly unique as each entry is only one depot path. * * @return array list view entries for this label, empty array if none. */ public function getView() { return $this->getRawValue('View') ?: array(); } /** * Set the view for this label. See getView for format details. * * @param array $view Array of view strings, empty array for none. * @return Label provides a fluent interface. * @throws \InvalidArgumentException View array, or a view entry, is incorrect type. */ public function setView($view) { if (!is_array($view)) { throw new \InvalidArgumentException('View must be passed as array.'); } foreach ($view as $entry) { if (!is_string($entry) || trim($entry) === "") { throw new \InvalidArgumentException( "Each view entry must be a non-empty string." ); } } return $this->setRawValue('View', $view); } /** * Add a view entry to this Label. * * @param string $path the depot path to add. * @return Label provides a fluent interface. */ public function addView($path) { $entries = $this->getView(); $entries[] = $path; return $this->setView($entries); } /** * Adds the specified filespecs to this label. The update is completed * synchronously, no need to call save. * * @param array $filespecs The filespecs to add to this label, can include rev-specs * @return Label provides a fluent interface. */ public function tag($filespecs) { if (!is_array($filespecs) || in_array(false, array_map('is_string', $filespecs))) { throw new \InvalidArgumentException( 'Tag requires an array of string values for input' ); } // there is a potential to exceed the arg-max limit; // run tag command as few times as possible $connection = $this->getConnection(); $batches = $connection->batchArgs($filespecs, array('-l', $this->getId())); foreach ($batches as $batch) { $connection->run('tag', $batch); } return $this; } /** * Check if the given id is in a valid format for this spec type. * * @param string $id the id to check * @return bool true if id is valid, false otherwise */ protected static function isValidId($id) { $validator = new Validate\SpecName; $validator->allowRelative(true); $validator->allowPercent(false); $validator->allowCommas(false); $validator->allowSlashes(true); return $validator->isValid($id); } /** * 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) { $flags = parent::getFetchAllFlags($options); if (isset($options[static::FETCH_BY_NAME])) { $name = $options[static::FETCH_BY_NAME]; if (!is_string($name) || trim($name) === '') { throw new \InvalidArgumentException( 'Filter by Name expects a non-empty string as input' ); } $flags[] = '-e'; $flags[] = $name; } if (isset($options[static::FETCH_BY_OWNER])) { $owner = $options[static::FETCH_BY_OWNER]; // We allow empty values as this returns labels with no owner if (!is_string($owner) || trim($owner) === '') { throw new \InvalidArgumentException( 'Filter by Owner expects a non-empty string as input' ); } $flags[] = '-u'; $flags[] = $owner; } return $flags; } }