[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/search/engine/ -> PhabricatorApplicationSearchEngine.php (source)

   1  <?php
   2  
   3  /**
   4   * Represents an abstract search engine for an application. It supports
   5   * creating and storing saved queries.
   6   *
   7   * @task construct  Constructing Engines
   8   * @task app        Applications
   9   * @task builtin    Builtin Queries
  10   * @task uri        Query URIs
  11   * @task dates      Date Filters
  12   * @task read       Reading Utilities
  13   * @task exec       Paging and Executing Queries
  14   * @task render     Rendering Results
  15   */
  16  abstract class PhabricatorApplicationSearchEngine {
  17  
  18    private $application;
  19    private $viewer;
  20    private $errors = array();
  21    private $customFields = false;
  22    private $request;
  23    private $context;
  24  
  25    const CONTEXT_LIST  = 'list';
  26    const CONTEXT_PANEL = 'panel';
  27  
  28    public function setViewer(PhabricatorUser $viewer) {
  29      $this->viewer = $viewer;
  30      return $this;
  31    }
  32  
  33    protected function requireViewer() {
  34      if (!$this->viewer) {
  35        throw new Exception('Call setViewer() before using an engine!');
  36      }
  37      return $this->viewer;
  38    }
  39  
  40    public function setContext($context) {
  41      $this->context = $context;
  42      return $this;
  43    }
  44  
  45    public function isPanelContext() {
  46      return ($this->context == self::CONTEXT_PANEL);
  47    }
  48  
  49    public function saveQuery(PhabricatorSavedQuery $query) {
  50      $query->setEngineClassName(get_class($this));
  51  
  52      $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
  53      try {
  54        $query->save();
  55      } catch (AphrontDuplicateKeyQueryException $ex) {
  56        // Ignore, this is just a repeated search.
  57      }
  58      unset($unguarded);
  59    }
  60  
  61    /**
  62     * Create a saved query object from the request.
  63     *
  64     * @param AphrontRequest The search request.
  65     * @return PhabricatorSavedQuery
  66     */
  67    abstract public function buildSavedQueryFromRequest(
  68      AphrontRequest $request);
  69  
  70    /**
  71     * Executes the saved query.
  72     *
  73     * @param PhabricatorSavedQuery The saved query to operate on.
  74     * @return The result of the query.
  75     */
  76    abstract public function buildQueryFromSavedQuery(
  77      PhabricatorSavedQuery $saved);
  78  
  79    /**
  80     * Builds the search form using the request.
  81     *
  82     * @param AphrontFormView       Form to populate.
  83     * @param PhabricatorSavedQuery The query from which to build the form.
  84     * @return void
  85     */
  86    abstract public function buildSearchForm(
  87      AphrontFormView $form,
  88      PhabricatorSavedQuery $query);
  89  
  90    public function getErrors() {
  91      return $this->errors;
  92    }
  93  
  94    public function addError($error) {
  95      $this->errors[] = $error;
  96      return $this;
  97    }
  98  
  99    /**
 100     * Return an application URI corresponding to the results page of a query.
 101     * Normally, this is something like `/application/query/QUERYKEY/`.
 102     *
 103     * @param   string  The query key to build a URI for.
 104     * @return  string  URI where the query can be executed.
 105     * @task uri
 106     */
 107    public function getQueryResultsPageURI($query_key) {
 108      return $this->getURI('query/'.$query_key.'/');
 109    }
 110  
 111  
 112    /**
 113     * Return an application URI for query management. This is used when, e.g.,
 114     * a query deletion operation is cancelled.
 115     *
 116     * @return  string  URI where queries can be managed.
 117     * @task uri
 118     */
 119    public function getQueryManagementURI() {
 120      return $this->getURI('query/edit/');
 121    }
 122  
 123  
 124    /**
 125     * Return the URI to a path within the application. Used to construct default
 126     * URIs for management and results.
 127     *
 128     * @return string URI to path.
 129     * @task uri
 130     */
 131    abstract protected function getURI($path);
 132  
 133  
 134    /**
 135     * Return a human readable description of the type of objects this query
 136     * searches for.
 137     *
 138     * For example, "Tasks" or "Commits".
 139     *
 140     * @return string Human-readable description of what this engine is used to
 141     *   find.
 142     */
 143    abstract public function getResultTypeDescription();
 144  
 145  
 146    public function newSavedQuery() {
 147      return id(new PhabricatorSavedQuery())
 148        ->setEngineClassName(get_class($this));
 149    }
 150  
 151    public function addNavigationItems(PHUIListView $menu) {
 152      $viewer = $this->requireViewer();
 153  
 154      $menu->newLabel(pht('Queries'));
 155  
 156      $named_queries = $this->loadEnabledNamedQueries();
 157  
 158      foreach ($named_queries as $query) {
 159        $key = $query->getQueryKey();
 160        $uri = $this->getQueryResultsPageURI($key);
 161        $menu->newLink($query->getQueryName(), $uri, 'query/'.$key);
 162      }
 163  
 164      if ($viewer->isLoggedIn()) {
 165        $manage_uri = $this->getQueryManagementURI();
 166        $menu->newLink(pht('Edit Queries...'), $manage_uri, 'query/edit');
 167      }
 168  
 169      $menu->newLabel(pht('Search'));
 170      $advanced_uri = $this->getQueryResultsPageURI('advanced');
 171      $menu->newLink(pht('Advanced Search'), $advanced_uri, 'query/advanced');
 172  
 173      return $this;
 174    }
 175  
 176    public function loadAllNamedQueries() {
 177      $viewer = $this->requireViewer();
 178  
 179      $named_queries = id(new PhabricatorNamedQueryQuery())
 180        ->setViewer($viewer)
 181        ->withUserPHIDs(array($viewer->getPHID()))
 182        ->withEngineClassNames(array(get_class($this)))
 183        ->execute();
 184      $named_queries = mpull($named_queries, null, 'getQueryKey');
 185  
 186      $builtin = $this->getBuiltinQueries($viewer);
 187      $builtin = mpull($builtin, null, 'getQueryKey');
 188  
 189      foreach ($named_queries as $key => $named_query) {
 190        if ($named_query->getIsBuiltin()) {
 191          if (isset($builtin[$key])) {
 192            $named_queries[$key]->setQueryName($builtin[$key]->getQueryName());
 193            unset($builtin[$key]);
 194          } else {
 195            unset($named_queries[$key]);
 196          }
 197        }
 198  
 199        unset($builtin[$key]);
 200      }
 201  
 202      $named_queries = msort($named_queries, 'getSortKey');
 203  
 204      return $named_queries + $builtin;
 205    }
 206  
 207    public function loadEnabledNamedQueries() {
 208      $named_queries = $this->loadAllNamedQueries();
 209      foreach ($named_queries as $key => $named_query) {
 210        if ($named_query->getIsBuiltin() && $named_query->getIsDisabled()) {
 211          unset($named_queries[$key]);
 212        }
 213      }
 214      return $named_queries;
 215    }
 216  
 217  
 218  /* -(  Applications  )------------------------------------------------------- */
 219  
 220  
 221    protected function getApplicationURI($path = '') {
 222      return $this->getApplication()->getApplicationURI($path);
 223    }
 224  
 225    protected function getApplication() {
 226      if (!$this->application) {
 227        $class = $this->getApplicationClassName();
 228  
 229        $this->application = id(new PhabricatorApplicationQuery())
 230          ->setViewer($this->requireViewer())
 231          ->withClasses(array($class))
 232          ->withInstalled(true)
 233          ->executeOne();
 234  
 235        if (!$this->application) {
 236          throw new Exception(
 237            pht(
 238              'Application "%s" is not installed!',
 239              $class));
 240        }
 241      }
 242  
 243      return $this->application;
 244    }
 245  
 246    protected function getApplicationClassName() {
 247      throw new PhutilMethodNotImplementedException();
 248    }
 249  
 250  
 251  /* -(  Constructing Engines  )----------------------------------------------- */
 252  
 253  
 254    /**
 255     * Load all available application search engines.
 256     *
 257     * @return list<PhabricatorApplicationSearchEngine> All available engines.
 258     * @task construct
 259     */
 260    public static function getAllEngines() {
 261      $engines = id(new PhutilSymbolLoader())
 262        ->setAncestorClass(__CLASS__)
 263        ->loadObjects();
 264  
 265      return $engines;
 266    }
 267  
 268  
 269    /**
 270     * Get an engine by class name, if it exists.
 271     *
 272     * @return PhabricatorApplicationSearchEngine|null Engine, or null if it does
 273     *   not exist.
 274     * @task construct
 275     */
 276    public static function getEngineByClassName($class_name) {
 277      return idx(self::getAllEngines(), $class_name);
 278    }
 279  
 280  
 281  /* -(  Builtin Queries  )---------------------------------------------------- */
 282  
 283  
 284    /**
 285     * @task builtin
 286     */
 287    public function getBuiltinQueries() {
 288      $names = $this->getBuiltinQueryNames();
 289  
 290      $queries = array();
 291      $sequence = 0;
 292      foreach ($names as $key => $name) {
 293        $queries[$key] = id(new PhabricatorNamedQuery())
 294          ->setUserPHID($this->requireViewer()->getPHID())
 295          ->setEngineClassName(get_class($this))
 296          ->setQueryName($name)
 297          ->setQueryKey($key)
 298          ->setSequence((1 << 24) + $sequence++)
 299          ->setIsBuiltin(true);
 300      }
 301  
 302      return $queries;
 303    }
 304  
 305  
 306    /**
 307     * @task builtin
 308     */
 309    public function getBuiltinQuery($query_key) {
 310      if (!$this->isBuiltinQuery($query_key)) {
 311        throw new Exception("'{$query_key}' is not a builtin!");
 312      }
 313      return idx($this->getBuiltinQueries(), $query_key);
 314    }
 315  
 316  
 317    /**
 318     * @task builtin
 319     */
 320    protected function getBuiltinQueryNames() {
 321      return array();
 322    }
 323  
 324  
 325    /**
 326     * @task builtin
 327     */
 328    public function isBuiltinQuery($query_key) {
 329      $builtins = $this->getBuiltinQueries();
 330      return isset($builtins[$query_key]);
 331    }
 332  
 333  
 334    /**
 335     * @task builtin
 336     */
 337    public function buildSavedQueryFromBuiltin($query_key) {
 338      throw new Exception("Builtin '{$query_key}' is not supported!");
 339    }
 340  
 341  
 342  /* -(  Reading Utilities )--------------------------------------------------- */
 343  
 344  
 345    /**
 346     * Read a list of user PHIDs from a request in a flexible way. This method
 347     * supports either of these forms:
 348     *
 349     *   users[]=alincoln&users[]=htaft
 350     *   users=alincoln,htaft
 351     *
 352     * Additionally, users can be specified either by PHID or by name.
 353     *
 354     * The main goal of this flexibility is to allow external programs to generate
 355     * links to pages (like "alincoln's open revisions") without needing to make
 356     * API calls.
 357     *
 358     * @param AphrontRequest  Request to read user PHIDs from.
 359     * @param string          Key to read in the request.
 360     * @param list<const>     Other permitted PHID types.
 361     * @return list<phid>     List of user PHIDs.
 362     *
 363     * @task read
 364     */
 365    protected function readUsersFromRequest(
 366      AphrontRequest $request,
 367      $key,
 368      array $allow_types = array()) {
 369  
 370      $list = $this->readListFromRequest($request, $key);
 371  
 372      $phids = array();
 373      $names = array();
 374      $allow_types = array_fuse($allow_types);
 375      $user_type = PhabricatorPHIDConstants::PHID_TYPE_USER;
 376      foreach ($list as $item) {
 377        $type = phid_get_type($item);
 378        if ($type == $user_type) {
 379          $phids[] = $item;
 380        } else if (isset($allow_types[$type])) {
 381          $phids[] = $item;
 382        } else {
 383          $names[] = $item;
 384        }
 385      }
 386  
 387      if ($names) {
 388        $users = id(new PhabricatorPeopleQuery())
 389          ->setViewer($this->requireViewer())
 390          ->withUsernames($names)
 391          ->execute();
 392        foreach ($users as $user) {
 393          $phids[] = $user->getPHID();
 394        }
 395        $phids = array_unique($phids);
 396      }
 397  
 398      return $phids;
 399    }
 400  
 401  
 402    /**
 403     * Read a list of generic PHIDs from a request in a flexible way. Like
 404     * @{method:readUsersFromRequest}, this method supports either array or
 405     * comma-delimited forms. Objects can be specified either by PHID or by
 406     * object name.
 407     *
 408     * @param AphrontRequest  Request to read PHIDs from.
 409     * @param string          Key to read in the request.
 410     * @param list<const>     Optional, list of permitted PHID types.
 411     * @return list<phid>     List of object PHIDs.
 412     *
 413     * @task read
 414     */
 415    protected function readPHIDsFromRequest(
 416      AphrontRequest $request,
 417      $key,
 418      array $allow_types = array()) {
 419  
 420      $list = $this->readListFromRequest($request, $key);
 421  
 422      $objects = id(new PhabricatorObjectQuery())
 423        ->setViewer($this->requireViewer())
 424        ->withNames($list)
 425        ->execute();
 426      $list = mpull($objects, 'getPHID');
 427  
 428      if (!$list) {
 429        return array();
 430      }
 431  
 432      // If only certain PHID types are allowed, filter out all the others.
 433      if ($allow_types) {
 434        $allow_types = array_fuse($allow_types);
 435        foreach ($list as $key => $phid) {
 436          if (empty($allow_types[phid_get_type($phid)])) {
 437            unset($list[$key]);
 438          }
 439        }
 440      }
 441  
 442      return $list;
 443    }
 444  
 445  
 446    /**
 447     * Read a list of items from the request, in either array format or string
 448     * format:
 449     *
 450     *   list[]=item1&list[]=item2
 451     *   list=item1,item2
 452     *
 453     * This provides flexibility when constructing URIs, especially from external
 454     * sources.
 455     *
 456     * @param AphrontRequest  Request to read strings from.
 457     * @param string          Key to read in the request.
 458     * @return list<string>   List of values.
 459     */
 460    protected function readListFromRequest(
 461      AphrontRequest $request,
 462      $key) {
 463      $list = $request->getArr($key, null);
 464      if ($list === null) {
 465        $list = $request->getStrList($key);
 466      }
 467  
 468      if (!$list) {
 469        return array();
 470      }
 471  
 472      return $list;
 473    }
 474  
 475    protected function readDateFromRequest(
 476      AphrontRequest $request,
 477      $key) {
 478  
 479      return id(new AphrontFormDateControl())
 480        ->setUser($this->requireViewer())
 481        ->setName($key)
 482        ->setAllowNull(true)
 483        ->readValueFromRequest($request);
 484    }
 485  
 486    protected function readBoolFromRequest(
 487      AphrontRequest $request,
 488      $key) {
 489      if (!strlen($request->getStr($key))) {
 490        return null;
 491      }
 492      return $request->getBool($key);
 493    }
 494  
 495  
 496    protected function getBoolFromQuery(PhabricatorSavedQuery $query, $key) {
 497      $value = $query->getParameter($key);
 498      if ($value === null) {
 499        return $value;
 500      }
 501      return $value ? 'true' : 'false';
 502    }
 503  
 504  
 505  /* -(  Dates  )-------------------------------------------------------------- */
 506  
 507  
 508    /**
 509     * @task dates
 510     */
 511    protected function parseDateTime($date_time) {
 512      if (!strlen($date_time)) {
 513        return null;
 514      }
 515  
 516      return PhabricatorTime::parseLocalTime($date_time, $this->requireViewer());
 517    }
 518  
 519  
 520    /**
 521     * @task dates
 522     */
 523    protected function buildDateRange(
 524      AphrontFormView $form,
 525      PhabricatorSavedQuery $saved_query,
 526      $start_key,
 527      $start_name,
 528      $end_key,
 529      $end_name) {
 530  
 531      $start_str = $saved_query->getParameter($start_key);
 532      $start = null;
 533      if (strlen($start_str)) {
 534        $start = $this->parseDateTime($start_str);
 535        if (!$start) {
 536          $this->addError(
 537            pht(
 538              '"%s" date can not be parsed.',
 539              $start_name));
 540        }
 541      }
 542  
 543  
 544      $end_str = $saved_query->getParameter($end_key);
 545      $end = null;
 546      if (strlen($end_str)) {
 547        $end = $this->parseDateTime($end_str);
 548        if (!$end) {
 549          $this->addError(
 550            pht(
 551              '"%s" date can not be parsed.',
 552              $end_name));
 553        }
 554      }
 555  
 556      if ($start && $end && ($start >= $end)) {
 557        $this->addError(
 558          pht(
 559            '"%s" must be a date before "%s".',
 560            $start_name,
 561            $end_name));
 562      }
 563  
 564      $form
 565        ->appendChild(
 566          id(new PHUIFormFreeformDateControl())
 567            ->setName($start_key)
 568            ->setLabel($start_name)
 569            ->setValue($start_str))
 570        ->appendChild(
 571          id(new AphrontFormTextControl())
 572            ->setName($end_key)
 573            ->setLabel($end_name)
 574            ->setValue($end_str));
 575    }
 576  
 577  
 578  /* -(  Paging and Executing Queries  )--------------------------------------- */
 579  
 580  
 581    public function getPageSize(PhabricatorSavedQuery $saved) {
 582      return $saved->getParameter('limit', 100);
 583    }
 584  
 585  
 586    public function shouldUseOffsetPaging() {
 587      return false;
 588    }
 589  
 590  
 591    public function newPagerForSavedQuery(PhabricatorSavedQuery $saved) {
 592      if ($this->shouldUseOffsetPaging()) {
 593        $pager = new AphrontPagerView();
 594      } else {
 595        $pager = new AphrontCursorPagerView();
 596      }
 597  
 598      $page_size = $this->getPageSize($saved);
 599      if (is_finite($page_size)) {
 600        $pager->setPageSize($page_size);
 601      } else {
 602        // Consider an INF pagesize to mean a large finite pagesize.
 603  
 604        // TODO: It would be nice to handle this more gracefully, but math
 605        // with INF seems to vary across PHP versions, systems, and runtimes.
 606        $pager->setPageSize(0xFFFF);
 607      }
 608  
 609      return $pager;
 610    }
 611  
 612  
 613    public function executeQuery(
 614      PhabricatorPolicyAwareQuery $query,
 615      AphrontView $pager) {
 616  
 617      $query->setViewer($this->requireViewer());
 618  
 619      if ($this->shouldUseOffsetPaging()) {
 620        $objects = $query->executeWithOffsetPager($pager);
 621      } else {
 622        $objects = $query->executeWithCursorPager($pager);
 623      }
 624  
 625      return $objects;
 626    }
 627  
 628  
 629  /* -(  Rendering  )---------------------------------------------------------- */
 630  
 631  
 632    public function setRequest(AphrontRequest $request) {
 633      $this->request = $request;
 634      return $this;
 635    }
 636  
 637    public function getRequest() {
 638      return $this->request;
 639    }
 640  
 641    public function renderResults(
 642      array $objects,
 643      PhabricatorSavedQuery $query) {
 644  
 645      $phids = $this->getRequiredHandlePHIDsForResultList($objects, $query);
 646  
 647      if ($phids) {
 648        $handles = id(new PhabricatorHandleQuery())
 649          ->setViewer($this->requireViewer())
 650          ->witHPHIDs($phids)
 651          ->execute();
 652      } else {
 653        $handles = array();
 654      }
 655  
 656      return $this->renderResultList($objects, $query, $handles);
 657    }
 658  
 659    protected function getRequiredHandlePHIDsForResultList(
 660      array $objects,
 661      PhabricatorSavedQuery $query) {
 662      return array();
 663    }
 664  
 665    protected function renderResultList(
 666      array $objects,
 667      PhabricatorSavedQuery $query,
 668      array $handles) {
 669      throw new Exception(pht('Not supported here yet!'));
 670    }
 671  
 672  
 673  /* -(  Application Search  )------------------------------------------------- */
 674  
 675  
 676    /**
 677     * Retrieve an object to use to define custom fields for this search.
 678     *
 679     * To integrate with custom fields, subclasses should override this method
 680     * and return an instance of the application object which implements
 681     * @{interface:PhabricatorCustomFieldInterface}.
 682     *
 683     * @return PhabricatorCustomFieldInterface|null Object with custom fields.
 684     * @task appsearch
 685     */
 686    public function getCustomFieldObject() {
 687      return null;
 688    }
 689  
 690  
 691    /**
 692     * Get the custom fields for this search.
 693     *
 694     * @return PhabricatorCustomFieldList|null Custom fields, if this search
 695     *   supports custom fields.
 696     * @task appsearch
 697     */
 698    public function getCustomFieldList() {
 699      if ($this->customFields === false) {
 700        $object = $this->getCustomFieldObject();
 701        if ($object) {
 702          $fields = PhabricatorCustomField::getObjectFields(
 703            $object,
 704            PhabricatorCustomField::ROLE_APPLICATIONSEARCH);
 705          $fields->setViewer($this->requireViewer());
 706        } else {
 707          $fields = null;
 708        }
 709        $this->customFields = $fields;
 710      }
 711      return $this->customFields;
 712    }
 713  
 714  
 715    /**
 716     * Moves data from the request into a saved query.
 717     *
 718     * @param AphrontRequest Request to read.
 719     * @param PhabricatorSavedQuery Query to write to.
 720     * @return void
 721     * @task appsearch
 722     */
 723    protected function readCustomFieldsFromRequest(
 724      AphrontRequest $request,
 725      PhabricatorSavedQuery $saved) {
 726  
 727      $list = $this->getCustomFieldList();
 728      if (!$list) {
 729        return;
 730      }
 731  
 732      foreach ($list->getFields() as $field) {
 733        $key = $this->getKeyForCustomField($field);
 734        $value = $field->readApplicationSearchValueFromRequest(
 735          $this,
 736          $request);
 737        $saved->setParameter($key, $value);
 738      }
 739    }
 740  
 741  
 742    /**
 743     * Applies data from a saved query to an executable query.
 744     *
 745     * @param PhabricatorCursorPagedPolicyAwareQuery Query to constrain.
 746     * @param PhabricatorSavedQuery Saved query to read.
 747     * @return void
 748     */
 749    protected function applyCustomFieldsToQuery(
 750      PhabricatorCursorPagedPolicyAwareQuery $query,
 751      PhabricatorSavedQuery $saved) {
 752  
 753      $list = $this->getCustomFieldList();
 754      if (!$list) {
 755        return;
 756      }
 757  
 758      foreach ($list->getFields() as $field) {
 759        $key = $this->getKeyForCustomField($field);
 760        $value = $field->applyApplicationSearchConstraintToQuery(
 761          $this,
 762          $query,
 763          $saved->getParameter($key));
 764      }
 765    }
 766  
 767    protected function applyOrderByToQuery(
 768      PhabricatorCursorPagedPolicyAwareQuery $query,
 769      array $standard_values,
 770      $order) {
 771  
 772      if (substr($order, 0, 7) === 'custom:') {
 773        $list = $this->getCustomFieldList();
 774        if (!$list) {
 775          $query->setOrderBy(head($standard_values));
 776          return;
 777        }
 778  
 779        foreach ($list->getFields() as $field) {
 780          $key = $this->getKeyForCustomField($field);
 781  
 782          if ($key === $order) {
 783            $index = $field->buildOrderIndex();
 784  
 785            if ($index === null) {
 786              $query->setOrderBy(head($standard_values));
 787              return;
 788            }
 789  
 790            $query->withApplicationSearchOrder(
 791              $field,
 792              $index,
 793              false);
 794            break;
 795          }
 796        }
 797      } else {
 798        $order = idx($standard_values, $order);
 799        if ($order) {
 800          $query->setOrderBy($order);
 801        } else {
 802          $query->setOrderBy(head($standard_values));
 803        }
 804      }
 805    }
 806  
 807  
 808    protected function getCustomFieldOrderOptions() {
 809      $list = $this->getCustomFieldList();
 810      if (!$list) {
 811        return;
 812      }
 813  
 814      $custom_order = array();
 815      foreach ($list->getFields() as $field) {
 816        if ($field->shouldAppearInApplicationSearch()) {
 817          if ($field->buildOrderIndex() !== null) {
 818            $key = $this->getKeyForCustomField($field);
 819            $custom_order[$key] = $field->getFieldName();
 820          }
 821        }
 822      }
 823  
 824      return $custom_order;
 825    }
 826  
 827    /**
 828     * Get a unique key identifying a field.
 829     *
 830     * @param PhabricatorCustomField Field to identify.
 831     * @return string Unique identifier, suitable for use as an input name.
 832     */
 833    public function getKeyForCustomField(PhabricatorCustomField $field) {
 834      return 'custom:'.$field->getFieldIndex();
 835    }
 836  
 837  
 838    /**
 839     * Add inputs to an application search form so the user can query on custom
 840     * fields.
 841     *
 842     * @param AphrontFormView Form to update.
 843     * @param PhabricatorSavedQuery Values to prefill.
 844     * @return void
 845     */
 846    protected function appendCustomFieldsToForm(
 847      AphrontFormView $form,
 848      PhabricatorSavedQuery $saved) {
 849  
 850      $list = $this->getCustomFieldList();
 851      if (!$list) {
 852        return;
 853      }
 854  
 855      $phids = array();
 856      foreach ($list->getFields() as $field) {
 857        $key = $this->getKeyForCustomField($field);
 858        $value = $saved->getParameter($key);
 859        $phids[$key] = $field->getRequiredHandlePHIDsForApplicationSearch($value);
 860      }
 861      $all_phids = array_mergev($phids);
 862  
 863      $handles = array();
 864      if ($all_phids) {
 865        $handles = id(new PhabricatorHandleQuery())
 866          ->setViewer($this->requireViewer())
 867          ->withPHIDs($all_phids)
 868          ->execute();
 869      }
 870  
 871      foreach ($list->getFields() as $field) {
 872        $key = $this->getKeyForCustomField($field);
 873        $value = $saved->getParameter($key);
 874        $field->appendToApplicationSearchForm(
 875          $this,
 876          $form,
 877          $value,
 878          array_select_keys($handles, $phids[$key]));
 879      }
 880    }
 881  
 882  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1