Path: blob/master/src/infrastructure/customfield/field/PhabricatorCustomFieldList.php
12242 views
<?php12/**3* Convenience class to perform operations on an entire field list, like reading4* all values from storage.5*6* $field_list = new PhabricatorCustomFieldList($fields);7*8*/9final class PhabricatorCustomFieldList extends Phobject {1011private $fields;12private $viewer;1314public function __construct(array $fields) {15assert_instances_of($fields, 'PhabricatorCustomField');16$this->fields = $fields;17}1819public function getFields() {20return $this->fields;21}2223public function setViewer(PhabricatorUser $viewer) {24$this->viewer = $viewer;25foreach ($this->getFields() as $field) {26$field->setViewer($viewer);27}28return $this;29}3031public function readFieldsFromObject(32PhabricatorCustomFieldInterface $object) {3334$fields = $this->getFields();3536foreach ($fields as $field) {37$field38->setObject($object)39->readValueFromObject($object);40}4142return $this;43}4445/**46* Read stored values for all fields which support storage.47*48* @param PhabricatorCustomFieldInterface Object to read field values for.49* @return void50*/51public function readFieldsFromStorage(52PhabricatorCustomFieldInterface $object) {5354$this->readFieldsFromObject($object);5556$fields = $this->getFields();57id(new PhabricatorCustomFieldStorageQuery())58->addFields($fields)59->execute();6061return $this;62}6364public function appendFieldsToForm(AphrontFormView $form) {65$enabled = array();66foreach ($this->fields as $field) {67if ($field->shouldEnableForRole(PhabricatorCustomField::ROLE_EDIT)) {68$enabled[] = $field;69}70}7172$phids = array();73foreach ($enabled as $field_key => $field) {74$phids[$field_key] = $field->getRequiredHandlePHIDsForEdit();75}7677$all_phids = array_mergev($phids);78if ($all_phids) {79$handles = id(new PhabricatorHandleQuery())80->setViewer($this->viewer)81->withPHIDs($all_phids)82->execute();83} else {84$handles = array();85}8687foreach ($enabled as $field_key => $field) {88$field_handles = array_select_keys($handles, $phids[$field_key]);8990$instructions = $field->getInstructionsForEdit();91if ($instructions !== null && strlen($instructions)) {92$form->appendRemarkupInstructions($instructions);93}9495$form->appendChild($field->renderEditControl($field_handles));96}97}9899public function appendFieldsToPropertyList(100PhabricatorCustomFieldInterface $object,101PhabricatorUser $viewer,102PHUIPropertyListView $view) {103104$this->readFieldsFromStorage($object);105$fields = $this->fields;106107foreach ($fields as $field) {108$field->setViewer($viewer);109}110111// Move all the blocks to the end, regardless of their configuration order,112// because it always looks silly to render a block in the middle of a list113// of properties.114$head = array();115$tail = array();116foreach ($fields as $key => $field) {117$style = $field->getStyleForPropertyView();118switch ($style) {119case 'property':120case 'header':121$head[$key] = $field;122break;123case 'block':124$tail[$key] = $field;125break;126default:127throw new Exception(128pht(129"Unknown field property view style '%s'; valid styles are ".130"'%s' and '%s'.",131$style,132'block',133'property'));134}135}136$fields = $head + $tail;137138$add_header = null;139140$phids = array();141foreach ($fields as $key => $field) {142$phids[$key] = $field->getRequiredHandlePHIDsForPropertyView();143}144145$all_phids = array_mergev($phids);146if ($all_phids) {147$handles = id(new PhabricatorHandleQuery())148->setViewer($viewer)149->withPHIDs($all_phids)150->execute();151} else {152$handles = array();153}154155foreach ($fields as $key => $field) {156$field_handles = array_select_keys($handles, $phids[$key]);157$label = $field->renderPropertyViewLabel();158$value = $field->renderPropertyViewValue($field_handles);159if ($value !== null) {160switch ($field->getStyleForPropertyView()) {161case 'header':162// We want to hide headers if the fields they're associated with163// don't actually produce any visible properties. For example, in a164// list like this:165//166// Header A167// Prop A: Value A168// Header B169// Prop B: Value B170//171// ...if the "Prop A" field returns `null` when rendering its172// property value and we rendered naively, we'd get this:173//174// Header A175// Header B176// Prop B: Value B177//178// This is silly. Instead, we hide "Header A".179$add_header = $value;180break;181case 'property':182if ($add_header !== null) {183// Add the most recently seen header.184$view->addSectionHeader($add_header);185$add_header = null;186}187$view->addProperty($label, $value);188break;189case 'block':190$icon = $field->getIconForPropertyView();191$view->invokeWillRenderEvent();192if ($label !== null) {193$view->addSectionHeader($label, $icon);194}195$view->addTextContent($value);196break;197}198}199}200}201202public function buildFieldTransactionsFromRequest(203PhabricatorApplicationTransaction $template,204AphrontRequest $request) {205206$xactions = array();207208$role = PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS;209foreach ($this->fields as $field) {210if (!$field->shouldEnableForRole($role)) {211continue;212}213214$transaction_type = $field->getApplicationTransactionType();215$xaction = id(clone $template)216->setTransactionType($transaction_type);217218if ($transaction_type == PhabricatorTransactions::TYPE_CUSTOMFIELD) {219// For TYPE_CUSTOMFIELD transactions only, we provide the old value220// as an input.221$old_value = $field->getOldValueForApplicationTransactions();222$xaction->setOldValue($old_value);223}224225$field->readValueFromRequest($request);226227$xaction228->setNewValue($field->getNewValueForApplicationTransactions());229230if ($transaction_type == PhabricatorTransactions::TYPE_CUSTOMFIELD) {231// For TYPE_CUSTOMFIELD transactions, add the field key in metadata.232$xaction->setMetadataValue('customfield:key', $field->getFieldKey());233}234235$metadata = $field->getApplicationTransactionMetadata();236foreach ($metadata as $key => $value) {237$xaction->setMetadataValue($key, $value);238}239240$xactions[] = $xaction;241}242243return $xactions;244}245246247/**248* Publish field indexes into index tables, so ApplicationSearch can search249* them.250*251* @return void252*/253public function rebuildIndexes(PhabricatorCustomFieldInterface $object) {254$indexes = array();255$index_keys = array();256257$phid = $object->getPHID();258259$role = PhabricatorCustomField::ROLE_APPLICATIONSEARCH;260foreach ($this->fields as $field) {261if (!$field->shouldEnableForRole($role)) {262continue;263}264265$index_keys[$field->getFieldIndex()] = true;266267foreach ($field->buildFieldIndexes() as $index) {268$index->setObjectPHID($phid);269$indexes[$index->getTableName()][] = $index;270}271}272273if (!$indexes) {274return;275}276277$any_index = head(head($indexes));278$conn_w = $any_index->establishConnection('w');279280foreach ($indexes as $table => $index_list) {281$sql = array();282foreach ($index_list as $index) {283$sql[] = $index->formatForInsert($conn_w);284}285$indexes[$table] = $sql;286}287288$any_index->openTransaction();289290foreach ($indexes as $table => $sql_list) {291queryfx(292$conn_w,293'DELETE FROM %T WHERE objectPHID = %s AND indexKey IN (%Ls)',294$table,295$phid,296array_keys($index_keys));297298if (!$sql_list) {299continue;300}301302foreach (PhabricatorLiskDAO::chunkSQL($sql_list) as $chunk) {303queryfx(304$conn_w,305'INSERT INTO %T (objectPHID, indexKey, indexValue) VALUES %LQ',306$table,307$chunk);308}309}310311$any_index->saveTransaction();312}313314public function updateAbstractDocument(315PhabricatorSearchAbstractDocument $document) {316317$role = PhabricatorCustomField::ROLE_GLOBALSEARCH;318foreach ($this->getFields() as $field) {319if (!$field->shouldEnableForRole($role)) {320continue;321}322$field->updateAbstractDocument($document);323}324}325326327}328329330