PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/src/applications/phid/query/PhabricatorObjectListQuery.php

http://github.com/facebook/phabricator
PHP | 266 lines | 193 code | 46 blank | 27 comment | 22 complexity | 0a04d1c37537ec10fedff0e4f7ef38f8 MD5 | raw file
Possible License(s): JSON, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause, LGPL-2.0, MIT, LGPL-2.1, LGPL-3.0
  1. <?php
  2. final class PhabricatorObjectListQuery extends Phobject {
  3. private $viewer;
  4. private $objectList;
  5. private $allowedTypes = array();
  6. private $allowPartialResults;
  7. private $suffixes = array();
  8. public function setAllowPartialResults($allow_partial_results) {
  9. $this->allowPartialResults = $allow_partial_results;
  10. return $this;
  11. }
  12. public function getAllowPartialResults() {
  13. return $this->allowPartialResults;
  14. }
  15. public function setSuffixes(array $suffixes) {
  16. $this->suffixes = $suffixes;
  17. return $this;
  18. }
  19. public function getSuffixes() {
  20. return $this->suffixes;
  21. }
  22. public function setAllowedTypes(array $allowed_types) {
  23. $this->allowedTypes = $allowed_types;
  24. return $this;
  25. }
  26. public function getAllowedTypes() {
  27. return $this->allowedTypes;
  28. }
  29. public function setViewer(PhabricatorUser $viewer) {
  30. $this->viewer = $viewer;
  31. return $this;
  32. }
  33. public function getViewer() {
  34. return $this->viewer;
  35. }
  36. public function setObjectList($object_list) {
  37. $this->objectList = $object_list;
  38. return $this;
  39. }
  40. public function getObjectList() {
  41. return $this->objectList;
  42. }
  43. public function execute() {
  44. $names = $this->getObjectList();
  45. // First, normalize any internal whitespace so we don't get weird results
  46. // if linebreaks hit in weird spots.
  47. $names = preg_replace('/\s+/', ' ', $names);
  48. // Split the list on commas.
  49. $names = explode(',', $names);
  50. // Trim and remove empty tokens.
  51. foreach ($names as $key => $name) {
  52. $name = trim($name);
  53. if (!strlen($name)) {
  54. unset($names[$key]);
  55. continue;
  56. }
  57. $names[$key] = $name;
  58. }
  59. // Remove duplicates.
  60. $names = array_unique($names);
  61. $name_map = array();
  62. foreach ($names as $name) {
  63. $parts = explode(' ', $name);
  64. // If this looks like a monogram, ignore anything after the first token.
  65. // This allows us to parse "O123 Package Name" as though it was "O123",
  66. // which we can look up.
  67. if (preg_match('/^[A-Z]\d+\z/', $parts[0])) {
  68. $name_map[$parts[0]] = $name;
  69. } else {
  70. // For anything else, split it on spaces and use each token as a
  71. // value. This means "alincoln htaft", separated with a space instead
  72. // of with a comma, is two different users.
  73. foreach ($parts as $part) {
  74. $name_map[$part] = $part;
  75. }
  76. }
  77. }
  78. // If we're parsing with suffixes, strip them off any tokens and keep
  79. // track of them for later.
  80. $suffixes = $this->getSuffixes();
  81. if ($suffixes) {
  82. $suffixes = array_fuse($suffixes);
  83. $suffix_map = array();
  84. $stripped_map = array();
  85. foreach ($name_map as $key => $name) {
  86. $found_suffixes = array();
  87. do {
  88. $has_any_suffix = false;
  89. foreach ($suffixes as $suffix) {
  90. if (!$this->hasSuffix($name, $suffix)) {
  91. continue;
  92. }
  93. $key = $this->stripSuffix($key, $suffix);
  94. $name = $this->stripSuffix($name, $suffix);
  95. $found_suffixes[] = $suffix;
  96. $has_any_suffix = true;
  97. break;
  98. }
  99. } while ($has_any_suffix);
  100. $stripped_map[$key] = $name;
  101. $suffix_map[$key] = array_fuse($found_suffixes);
  102. }
  103. $name_map = $stripped_map;
  104. }
  105. $objects = $this->loadObjects(array_keys($name_map));
  106. $types = array();
  107. foreach ($objects as $name => $object) {
  108. $types[phid_get_type($object->getPHID())][] = $name;
  109. }
  110. $invalid = array();
  111. if ($this->getAllowedTypes()) {
  112. $allowed = array_fuse($this->getAllowedTypes());
  113. foreach ($types as $type => $names_of_type) {
  114. if (empty($allowed[$type])) {
  115. $invalid[] = $names_of_type;
  116. }
  117. }
  118. }
  119. $invalid = array_mergev($invalid);
  120. $missing = array();
  121. foreach ($name_map as $key => $name) {
  122. if (empty($objects[$key])) {
  123. $missing[$key] = $name;
  124. }
  125. }
  126. $result = array_unique(mpull($objects, 'getPHID'));
  127. // For values which are plain PHIDs of allowed types, let them through
  128. // unchecked. This can happen occur if subscribers or reviewers which the
  129. // revision author does not have permission to see are added by Herald
  130. // rules. Any actual edits will be checked later: users are not allowed
  131. // to add new reviewers they can't see, but they can touch a field which
  132. // contains them.
  133. foreach ($missing as $key => $value) {
  134. if (isset($allowed[phid_get_type($value)])) {
  135. unset($missing[$key]);
  136. $result[$key] = $value;
  137. }
  138. }
  139. // NOTE: We could couple this less tightly with Differential, but it is
  140. // currently the only thing that uses it, and we'd have to add a lot of
  141. // extra API to loosen this. It's not clear that this will be useful
  142. // elsewhere any time soon, so let's cross that bridge when we come to it.
  143. if (!$this->getAllowPartialResults()) {
  144. if ($invalid && $missing) {
  145. throw new DifferentialFieldParseException(
  146. pht(
  147. 'The objects you have listed include objects of the wrong '.
  148. 'type (%s) and objects which do not exist (%s).',
  149. implode(', ', $invalid),
  150. implode(', ', $missing)));
  151. } else if ($invalid) {
  152. throw new DifferentialFieldParseException(
  153. pht(
  154. 'The objects you have listed include objects of the wrong '.
  155. 'type (%s).',
  156. implode(', ', $invalid)));
  157. } else if ($missing) {
  158. throw new DifferentialFieldParseException(
  159. pht(
  160. 'The objects you have listed include objects which do not '.
  161. 'exist (%s).',
  162. implode(', ', $missing)));
  163. }
  164. }
  165. if ($suffixes) {
  166. foreach ($result as $key => $phid) {
  167. $result[$key] = array(
  168. 'phid' => $phid,
  169. 'suffixes' => idx($suffix_map, $key, array()),
  170. );
  171. }
  172. }
  173. return array_values($result);
  174. }
  175. private function loadObjects($names) {
  176. // First, try to load visible objects using monograms. This covers most
  177. // object types, but does not cover users or user email addresses.
  178. $query = id(new PhabricatorObjectQuery())
  179. ->setViewer($this->getViewer())
  180. ->withNames($names);
  181. $query->execute();
  182. $objects = $query->getNamedResults();
  183. $results = array();
  184. foreach ($names as $key => $name) {
  185. if (isset($objects[$name])) {
  186. $results[$name] = $objects[$name];
  187. unset($names[$key]);
  188. }
  189. }
  190. if ($names) {
  191. // We still have some symbols we haven't been able to resolve, so try to
  192. // load users. Try by username first...
  193. $users = id(new PhabricatorPeopleQuery())
  194. ->setViewer($this->getViewer())
  195. ->withUsernames($names)
  196. ->execute();
  197. $user_map = array();
  198. foreach ($users as $user) {
  199. $user_map[phutil_utf8_strtolower($user->getUsername())] = $user;
  200. }
  201. foreach ($names as $key => $name) {
  202. $normal_name = phutil_utf8_strtolower($name);
  203. if (isset($user_map[$normal_name])) {
  204. $results[$name] = $user_map[$normal_name];
  205. unset($names[$key]);
  206. }
  207. }
  208. }
  209. return $results;
  210. }
  211. private function hasSuffix($key, $suffix) {
  212. return (substr($key, -strlen($suffix)) === $suffix);
  213. }
  214. private function stripSuffix($key, $suffix) {
  215. if ($this->hasSuffix($key, $suffix)) {
  216. return substr($key, 0, -strlen($suffix));
  217. }
  218. return $key;
  219. }
  220. }