@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at upstream/main 376 lines 11 kB view raw
1<?php 2 3final class PhabricatorProjectSearchEngine 4 extends PhabricatorApplicationSearchEngine { 5 6 public function getResultTypeDescription() { 7 return pht('Projects'); 8 } 9 10 public function getApplicationClassName() { 11 return PhabricatorProjectApplication::class; 12 } 13 14 public function newQuery() { 15 return id(new PhabricatorProjectQuery()) 16 ->needImages(true) 17 ->needMembers(true) 18 ->needWatchers(true); 19 } 20 21 protected function buildCustomSearchFields() { 22 $subtype_map = id(new PhabricatorProject())->newEditEngineSubtypeMap(); 23 $hide_subtypes = ($subtype_map->getCount() == 1); 24 25 return array( 26 id(new PhabricatorSearchTextField()) 27 ->setLabel(pht('Name')) 28 ->setKey('name') 29 ->setDescription( 30 pht( 31 '(Deprecated.) Search for projects with a given name or '. 32 'hashtag using tokenizer/datasource query matching rules. This '. 33 'is deprecated in favor of the more powerful "query" '. 34 'constraint.')), 35 id(new PhabricatorSearchStringListField()) 36 ->setLabel(pht('Project Tags')) 37 ->setIsHidden(true) 38 ->setKey('slugs') 39 ->setDescription( 40 pht( 41 'Search for projects with particular hashtags.)')), 42 id(new PhabricatorUsersSearchField()) 43 ->setLabel(pht('Members')) 44 ->setKey('memberPHIDs') 45 ->setConduitKey('members') 46 ->setDescription(pht('Search for projects with particular members.')) 47 ->setAliases(array('member', 'members')), 48 id(new PhabricatorUsersSearchField()) 49 ->setLabel(pht('Watchers')) 50 ->setKey('watcherPHIDs') 51 ->setConduitKey('watchers') 52 ->setDescription(pht('Search for projects with particular watchers.')) 53 ->setAliases(array('watcher', 'watchers')), 54 id(new PhabricatorSearchSelectField()) 55 ->setLabel(pht('Status')) 56 ->setKey('status') 57 ->setOptions($this->getStatusOptions()) 58 ->setDefault('active'), 59 id(new PhabricatorSearchThreeStateField()) 60 ->setLabel(pht('Milestones')) 61 ->setKey('isMilestone') 62 ->setOptions( 63 pht('(Show All)'), 64 pht('Show Only Milestones'), 65 pht('Hide Milestones')) 66 ->setDescription( 67 pht( 68 'Pass true to find only milestones, or false to omit '. 69 'milestones.')), 70 id(new PhabricatorSearchThreeStateField()) 71 ->setLabel(pht('Root Projects')) 72 ->setKey('isRoot') 73 ->setOptions( 74 pht('(Show All)'), 75 pht('Show Only Root Projects'), 76 pht('Hide Root Projects')) 77 ->setDescription( 78 pht( 79 'Pass true to find only root projects, or false to omit '. 80 'root projects.')), 81 id(new PhabricatorSearchIntField()) 82 ->setLabel(pht('Minimum Depth')) 83 ->setKey('minDepth') 84 ->setIsHidden(true) 85 ->setDescription( 86 pht( 87 'Find projects with a given minimum depth. Root projects '. 88 'have depth 0, their immediate children have depth 1, and '. 89 'so on.')), 90 id(new PhabricatorSearchIntField()) 91 ->setLabel(pht('Maximum Depth')) 92 ->setKey('maxDepth') 93 ->setIsHidden(true) 94 ->setDescription( 95 pht( 96 'Find projects with a given maximum depth. Root projects '. 97 'have depth 0, their immediate children have depth 1, and '. 98 'so on.')), 99 id(new PhabricatorSearchDatasourceField()) 100 ->setLabel(pht('Subtypes')) 101 ->setKey('subtypes') 102 ->setAliases(array('subtype')) 103 ->setDescription( 104 pht('Search for projects with given subtypes.')) 105 ->setDatasource(new PhabricatorProjectSubtypeDatasource()) 106 ->setIsHidden($hide_subtypes), 107 id(new PhabricatorSearchCheckboxesField()) 108 ->setLabel(pht('Icons')) 109 ->setKey('icons') 110 ->setDescription(pht('Search for projects with particular icons.')) 111 ->setOptions($this->getIconOptions()), 112 id(new PhabricatorSearchCheckboxesField()) 113 ->setLabel(pht('Colors')) 114 ->setKey('colors') 115 ->setDescription(pht('Search for projects with particular colors.')) 116 ->setOptions($this->getColorOptions()), 117 id(new PhabricatorPHIDsSearchField()) 118 ->setLabel(pht('Parent Projects')) 119 ->setKey('parentPHIDs') 120 ->setConduitKey('parents') 121 ->setAliases(array('parent', 'parents', 'parentPHID')) 122 ->setDescription(pht('Find direct subprojects of specified parents.')), 123 id(new PhabricatorPHIDsSearchField()) 124 ->setLabel(pht('Ancestor Projects')) 125 ->setKey('ancestorPHIDs') 126 ->setConduitKey('ancestors') 127 ->setAliases(array('ancestor', 'ancestors', 'ancestorPHID')) 128 ->setDescription( 129 pht('Find all subprojects beneath specified ancestors.')), 130 ); 131 } 132 133 134 protected function buildQueryFromParameters(array $map) { 135 $query = $this->newQuery(); 136 137 if (strlen($map['name'])) { 138 $tokens = PhabricatorTypeaheadDatasource::tokenizeString($map['name']); 139 $query->withNameTokens($tokens); 140 } 141 142 if ($map['slugs']) { 143 $query->withSlugs($map['slugs']); 144 } 145 146 if ($map['memberPHIDs']) { 147 $query->withMemberPHIDs($map['memberPHIDs']); 148 } 149 150 if ($map['watcherPHIDs']) { 151 $query->withWatcherPHIDs($map['watcherPHIDs']); 152 } 153 154 if ($map['status']) { 155 $status = idx($this->getStatusValues(), $map['status']); 156 if ($status) { 157 $query->withStatus($status); 158 } 159 } 160 161 if ($map['icons']) { 162 $query->withIcons($map['icons']); 163 } 164 165 if ($map['colors']) { 166 $query->withColors($map['colors']); 167 } 168 169 if ($map['isMilestone'] !== null) { 170 $query->withIsMilestone($map['isMilestone']); 171 } 172 173 $min_depth = $map['minDepth']; 174 $max_depth = $map['maxDepth']; 175 176 if ($min_depth !== null || $max_depth !== null) { 177 if ($min_depth !== null && $max_depth !== null) { 178 if ($min_depth > $max_depth) { 179 throw new Exception( 180 pht( 181 'Search constraint "minDepth" must be no larger than '. 182 'search constraint "maxDepth".')); 183 } 184 } 185 } 186 187 if ($map['isRoot'] !== null) { 188 if ($map['isRoot']) { 189 if ($max_depth === null) { 190 $max_depth = 0; 191 } else { 192 $max_depth = min(0, $max_depth); 193 } 194 195 $query->withDepthBetween(null, 0); 196 } else { 197 if ($min_depth === null) { 198 $min_depth = 1; 199 } else { 200 $min_depth = max($min_depth, 1); 201 } 202 } 203 } 204 205 if ($min_depth !== null || $max_depth !== null) { 206 $query->withDepthBetween($min_depth, $max_depth); 207 } 208 209 if ($map['parentPHIDs']) { 210 $query->withParentProjectPHIDs($map['parentPHIDs']); 211 } 212 213 if ($map['ancestorPHIDs']) { 214 $query->withAncestorProjectPHIDs($map['ancestorPHIDs']); 215 } 216 217 if ($map['subtypes']) { 218 $query->withSubtypes($map['subtypes']); 219 } 220 221 return $query; 222 } 223 224 protected function getURI($path) { 225 return '/project/'.$path; 226 } 227 228 protected function getBuiltinQueryNames() { 229 $names = array(); 230 231 if ($this->requireViewer()->isLoggedIn()) { 232 $names['joined'] = pht('Joined'); 233 } 234 235 if ($this->requireViewer()->isLoggedIn()) { 236 $names['watching'] = pht('Watching'); 237 } 238 239 $names['active'] = pht('Active'); 240 $names['all'] = pht('All'); 241 242 return $names; 243 } 244 245 public function buildSavedQueryFromBuiltin($query_key) { 246 $query = $this->newSavedQuery(); 247 $query->setQueryKey($query_key); 248 249 $viewer_phid = $this->requireViewer()->getPHID(); 250 251 // By default, do not show milestones in the list view. 252 $query->setParameter('isMilestone', false); 253 254 $active = PhabricatorProjectStatus::STATUS_ACTIVE_KEY; 255 256 switch ($query_key) { 257 case 'all': 258 return $query; 259 case 'active': 260 return $query 261 ->setParameter('status', $active); 262 case 'joined': 263 return $query 264 ->setParameter('memberPHIDs', array($viewer_phid)) 265 ->setParameter('status', $active); 266 case 'watching': 267 return $query 268 ->setParameter('watcherPHIDs', array($viewer_phid)) 269 ->setParameter('status', $active); 270 } 271 272 return parent::buildSavedQueryFromBuiltin($query_key); 273 } 274 275 private function getStatusOptions() { 276 $active = PhabricatorProjectStatus::STATUS_ACTIVE_KEY; 277 $archived = PhabricatorProjectStatus::STATUS_ARCHIVED_KEY; 278 279 return array( 280 $active => pht('Show Only Active Projects'), 281 $archived => pht('Show Only Archived Projects'), 282 'all' => pht('Show All Projects'), 283 ); 284 } 285 286 private function getStatusValues() { 287 $active = PhabricatorProjectStatus::STATUS_ACTIVE_KEY; 288 $archived = PhabricatorProjectStatus::STATUS_ARCHIVED_KEY; 289 290 return array( 291 $active => PhabricatorProjectQuery::STATUS_ACTIVE, 292 $archived => PhabricatorProjectQuery::STATUS_ARCHIVED, 293 'all' => PhabricatorProjectQuery::STATUS_ANY, 294 ); 295 } 296 297 private function getIconOptions() { 298 $options = array(); 299 300 $set = new PhabricatorProjectIconSet(); 301 foreach ($set->getIcons() as $icon) { 302 if ($icon->getIsDisabled()) { 303 continue; 304 } 305 306 $options[$icon->getKey()] = array( 307 id(new PHUIIconView()) 308 ->setIcon($icon->getIcon()), 309 ' ', 310 $icon->getLabel(), 311 ); 312 } 313 314 return $options; 315 } 316 317 private function getColorOptions() { 318 $options = array(); 319 320 foreach (PhabricatorProjectIconSet::getColorMap() as $color => $name) { 321 $options[$color] = array( 322 id(new PHUITagView()) 323 ->setType(PHUITagView::TYPE_SHADE) 324 ->setColor($color) 325 ->setName($name), 326 ); 327 } 328 329 return $options; 330 } 331 332 /** 333 * @param array<PhabricatorProject> $projects 334 * @param PhabricatorSavedQuery $query 335 * @param array<PhabricatorObjectHandle> $handles 336 */ 337 protected function renderResultList( 338 array $projects, 339 PhabricatorSavedQuery $query, 340 array $handles) { 341 assert_instances_of($projects, PhabricatorProject::class); 342 $viewer = $this->requireViewer(); 343 344 $list = id(new PhabricatorProjectListView()) 345 ->setViewer($viewer) 346 ->setProjects($projects) 347 ->setShowWatching(true) 348 ->setShowMember(true) 349 ->renderList(); 350 351 return id(new PhabricatorApplicationSearchResultView()) 352 ->setObjectList($list) 353 ->setNoDataString(pht('No projects found.')); 354 } 355 356 protected function getNewUserBody() { 357 $create_button = id(new PHUIButtonView()) 358 ->setTag('a') 359 ->setText(pht('Create a Project')) 360 ->setHref('/project/edit/') 361 ->setColor(PHUIButtonView::GREEN); 362 363 $icon = $this->getApplication()->getIcon(); 364 $app_name = $this->getApplication()->getName(); 365 $view = id(new PHUIBigInfoView()) 366 ->setIcon($icon) 367 ->setTitle(pht('Welcome to %s', $app_name)) 368 ->setDescription( 369 pht('Projects are labels are tags. You can use them for a codebase, '. 370 'a team, a sprint, or anything you need to group or categorize.')) 371 ->addAction($create_button); 372 373 return $view; 374 } 375 376}