diff --git a/resources/sql/20230427.semistructured.2.objectinstance.sql b/resources/sql/20230427.semistructured.2.objectinstance.sql index d6a56c7..0ec54f0 100644 --- a/resources/sql/20230427.semistructured.2.objectinstance.sql +++ b/resources/sql/20230427.semistructured.2.objectinstance.sql @@ -1,21 +1,20 @@ CREATE TABLE {$NAMESPACE}_semistructured.semistructured_objectinstance ( id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, phid VARBINARY(64) NOT NULL, dateCreated INT UNSIGNED NOT NULL, dateModified INT UNSIGNED NOT NULL, classID INT UNSIGNED NOT NULL, name VARCHAR(255) NOT NULL COLLATE {$COLLATE_SORT}, description LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, rawData LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, UNIQUE KEY `key_phid` (phid), KEY `classID` (classID) ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; CREATE TABLE {$NAMESPACE}_semistructured.semistructured_objectinstancetransaction LIKE {$NAMESPACE}_file.file_transaction; CREATE TABLE {$NAMESPACE}_semistructured.semistructured_objectinstancetransaction_comment LIKE {$NAMESPACE}_file.file_transaction_comment; - diff --git a/resources/sql/20230427.semistructured.3.objectinstance.customfield.sql b/resources/sql/20230427.semistructured.3.objectinstance.customfield.sql new file mode 100644 index 0000000..4d52db0 --- /dev/null +++ b/resources/sql/20230427.semistructured.3.objectinstance.customfield.sql @@ -0,0 +1,11 @@ +ALTER TABLE {$NAMESPACE}_semistructured.semistructured_objecttype + ADD customFieldsConfig LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}; + +CREATE TABLE {$NAMESPACE}_semistructured.semistructured_instanceconfiguredcustomfieldstorage + LIKE {$NAMESPACE}_user.user_configuredcustomfieldstorage; +CREATE TABLE {$NAMESPACE}_semistructured.semistructured_instancecustomfieldstringindex + LIKE {$NAMESPACE}_user.user_customfieldstringindex; +CREATE TABLE {$NAMESPACE}_semistructured.semistructured_instancecustomfieldnumericindex + LIKE {$NAMESPACE}_user.user_customfieldnumericindex; + + diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e881c7e..9f6804f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,107 +1,123 @@ 2, 'class' => array( 'SemiStructuredBaseController' => 'applications/semistruct/controller/SemiStructuredBaseController.php', 'SemiStructuredDAO' => 'applications/semistruct/storage/SemiStructuredDAO.php', 'SemiStructuredDataApplication' => 'applications/semistruct/application/SemiStructuredDataApplication.php', + 'SemiStructuredInstanceConfiguredCustomField' => 'applications/semistruct/customfield/SemiStructuredConfiguredCustomField.php', + 'SemiStructuredInstanceConfiguredCustomFieldStorage' => 'applications/semistruct/storage/SemiStructuredInstanceConfiguredCustomFieldStorage.php', + 'SemiStructuredInstanceCustomField' => 'applications/semistruct/customfield/SemiStructuredInstanceCustomField.php', + 'SemiStructuredInstanceCustomFieldNumericIndex' => 'applications/semistruct/storage/SemiStructuredInstanceCustomFieldNumericIndex.php', + 'SemiStructuredInstanceCustomFieldStringIndex' => 'applications/semistruct/storage/SemiStructuredInstanceCustomFieldStringIndex.php', 'SemiStructuredObjectInstance' => 'applications/semistruct/storage/SemiStructuredObjectInstance.php', 'SemiStructuredObjectInstanceClassTransaction' => 'applications/semistruct/xaction/instance/SemiStructuredObjectInstanceClassTransaction.php', 'SemiStructuredObjectInstanceController' => 'applications/semistruct/controller/instance/SemiStructuredObjectInstanceController.php', 'SemiStructuredObjectInstanceDescriptionTransaction' => 'applications/semistruct/xaction/instance/SemiStructuredObjectInstanceDescriptionTransaction.php', 'SemiStructuredObjectInstanceEditController' => 'applications/semistruct/controller/instance/SemiStructuredObjectInstanceEditController.php', 'SemiStructuredObjectInstanceEditEngine' => 'applications/semistruct/editor/SemiStructuredObjectInstanceEditEngine.php', 'SemiStructuredObjectInstanceNameTransaction' => 'applications/semistruct/xaction/instance/SemiStructuredObjectInstanceNameTransaction.php', 'SemiStructuredObjectInstancePHIDType' => 'applications/semistruct/phid/SemiStructuredObjectInstancePHIDType.php', 'SemiStructuredObjectInstanceQuery' => 'applications/semistruct/query/SemiStructuredObjectInstanceQuery.php', 'SemiStructuredObjectInstanceRawDataTransaction' => 'applications/semistruct/xaction/instance/SemiStructuredObjectInstanceRawDataTransaction.php', 'SemiStructuredObjectInstanceSearchEngine' => 'applications/semistruct/query/SemiStructuredObjectInstanceSearchEngine.php', 'SemiStructuredObjectInstanceTransaction' => 'applications/semistruct/storage/SemiStructuredObjectInstanceTransaction.php', 'SemiStructuredObjectInstanceTransactionComment' => 'applications/semistruct/storage/SemiStructuredObjectInstanceTransactionComment.php', 'SemiStructuredObjectInstanceTransactionEditor' => 'applications/semistruct/editor/SemiStructuredObjectInstanceTransactionEditor.php', 'SemiStructuredObjectInstanceTransactionQuery' => 'applications/semistruct/query/SemiStructuredObjectInstanceTransactionQuery.php', 'SemiStructuredObjectInstanceTransactionType' => 'applications/semistruct/xaction/instance/SemiStructuredObjectInstanceTransactionType.php', 'SemiStructuredObjectInstanceViewController' => 'applications/semistruct/controller/instance/SemiStructuredObjectInstanceViewController.php', 'SemiStructuredObjectNewInstanceController' => 'applications/semistruct/controller/class/SemiStructuredObjectNewInstanceController.php', 'SemiStructuredObjectType' => 'applications/semistruct/storage/SemiStructuredObjectType.php', 'SemiStructuredObjectTypeController' => 'applications/semistruct/controller/class/SemiStructuredObjectTypeController.php', + 'SemiStructuredObjectTypeCustomFieldsTransaction' => 'applications/semistruct/xaction/class/SemiStructuredObjectTypeCustomFieldsTransaction.php', 'SemiStructuredObjectTypeDescriptionTransaction' => 'applications/semistruct/xaction/class/SemiStructuredObjectTypeDescriptionTransaction.php', 'SemiStructuredObjectTypeEditController' => 'applications/semistruct/controller/class/SemiStructuredObjectTypeEditController.php', 'SemiStructuredObjectTypeEditEngine' => 'applications/semistruct/editor/SemiStructuredObjectTypeEditEngine.php', 'SemiStructuredObjectTypeFerretEngine' => 'applications/semistruct/engine/SemiStructuredObjectTypeFerretEngine.php', 'SemiStructuredObjectTypeListController' => 'applications/semistruct/controller/class/SemiStructuredObjectTypeListController.php', 'SemiStructuredObjectTypeNameTransaction' => 'applications/semistruct/xaction/class/SemiStructuredObjectTypeNameTransaction.php', 'SemiStructuredObjectTypePHIDType' => 'applications/semistruct/phid/SemiStructuredObjectTypePHIDType.php', 'SemiStructuredObjectTypeQuery' => 'applications/semistruct/query/SemiStructuredObjectTypeQuery.php', 'SemiStructuredObjectTypeSearchEngine' => 'applications/semistruct/query/SemiStructuredObjectTypeSearchEngine.php', 'SemiStructuredObjectTypeTransaction' => 'applications/semistruct/storage/SemiStructuredObjectTypeTransaction.php', 'SemiStructuredObjectTypeTransactionComment' => 'applications/semistruct/storage/SemiStructuredObjectTypeTransactionComment.php', 'SemiStructuredObjectTypeTransactionEditor' => 'applications/semistruct/editor/SemiStructuredObjectTypeTransactionEditor.php', 'SemiStructuredObjectTypeTransactionQuery' => 'applications/semistruct/query/SemiStructuredObjectTypeTransactionQuery.php', 'SemiStructuredObjectTypeTransactionType' => 'applications/semistruct/xaction/class/SemiStructuredObjectTypeTransactionType.php', 'SemiStructuredObjectTypeViewController' => 'applications/semistruct/controller/class/SemiStructuredObjectTypeViewController.php', 'SemiStructuredPatchList' => 'applications/semistruct/storage/SemiStructuredPatchList.php', ), 'function' => array(), 'xmap' => array( 'SemiStructuredBaseController' => 'PhabricatorController', 'SemiStructuredDAO' => 'PhabricatorLiskDAO', 'SemiStructuredDataApplication' => 'PhabricatorApplication', + 'SemiStructuredInstanceConfiguredCustomField' => array( + 'SemiStructuredInstanceCustomField', + 'PhabricatorStandardCustomFieldInterface', + ), + 'SemiStructuredInstanceConfiguredCustomFieldStorage' => 'PhabricatorCustomFieldStorage', + 'SemiStructuredInstanceCustomField' => 'PhabricatorCustomField', + 'SemiStructuredInstanceCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage', + 'SemiStructuredInstanceCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage', 'SemiStructuredObjectInstance' => array( 'SemiStructuredDAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFlaggableInterface', + 'PhabricatorCustomFieldInterface', 'PhabricatorDestructibleInterface', ), 'SemiStructuredObjectInstanceClassTransaction' => 'SemiStructuredObjectInstanceTransactionType', 'SemiStructuredObjectInstanceController' => 'SemiStructuredBaseController', 'SemiStructuredObjectInstanceDescriptionTransaction' => 'SemiStructuredObjectInstanceTransactionType', 'SemiStructuredObjectInstanceEditController' => 'SemiStructuredBaseController', 'SemiStructuredObjectInstanceEditEngine' => 'PhabricatorEditEngine', 'SemiStructuredObjectInstanceNameTransaction' => 'SemiStructuredObjectInstanceTransactionType', 'SemiStructuredObjectInstancePHIDType' => 'PhabricatorPHIDType', 'SemiStructuredObjectInstanceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'SemiStructuredObjectInstanceRawDataTransaction' => 'SemiStructuredObjectInstanceTransactionType', 'SemiStructuredObjectInstanceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'SemiStructuredObjectInstanceTransaction' => 'PhabricatorModularTransaction', 'SemiStructuredObjectInstanceTransactionComment' => 'PhabricatorApplicationTransactionComment', 'SemiStructuredObjectInstanceTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'SemiStructuredObjectInstanceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'SemiStructuredObjectInstanceTransactionType' => 'PhabricatorModularTransactionType', 'SemiStructuredObjectInstanceViewController' => 'SemiStructuredObjectInstanceController', 'SemiStructuredObjectNewInstanceController' => 'SemiStructuredObjectTypeController', 'SemiStructuredObjectType' => array( 'SemiStructuredDAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFlaggableInterface', 'PhabricatorDestructibleInterface', 'PhabricatorFerretInterface', ), 'SemiStructuredObjectTypeController' => 'SemiStructuredBaseController', + 'SemiStructuredObjectTypeCustomFieldsTransaction' => 'SemiStructuredObjectTypeTransactionType', 'SemiStructuredObjectTypeDescriptionTransaction' => 'SemiStructuredObjectTypeTransactionType', 'SemiStructuredObjectTypeEditController' => 'SemiStructuredBaseController', 'SemiStructuredObjectTypeEditEngine' => 'PhabricatorEditEngine', 'SemiStructuredObjectTypeFerretEngine' => 'PhabricatorFerretEngine', 'SemiStructuredObjectTypeListController' => 'SemiStructuredBaseController', 'SemiStructuredObjectTypeNameTransaction' => 'SemiStructuredObjectTypeTransactionType', 'SemiStructuredObjectTypePHIDType' => 'PhabricatorPHIDType', 'SemiStructuredObjectTypeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'SemiStructuredObjectTypeSearchEngine' => 'PhabricatorApplicationSearchEngine', 'SemiStructuredObjectTypeTransaction' => 'PhabricatorModularTransaction', 'SemiStructuredObjectTypeTransactionComment' => 'PhabricatorApplicationTransactionComment', 'SemiStructuredObjectTypeTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'SemiStructuredObjectTypeTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'SemiStructuredObjectTypeTransactionType' => 'PhabricatorModularTransactionType', 'SemiStructuredObjectTypeViewController' => 'SemiStructuredObjectTypeController', 'SemiStructuredPatchList' => 'PhabricatorSQLPatchList', ), )); diff --git a/src/controller/SemiStructuredBaseController.php b/src/controller/SemiStructuredBaseController.php index ab83928..d9fd017 100644 --- a/src/controller/SemiStructuredBaseController.php +++ b/src/controller/SemiStructuredBaseController.php @@ -1,16 +1,15 @@ newApplicationMenu() + return $this->newApplicationMenu(); // ->setSearchEngine(new PhabricatorBadgesSearchEngine()); - ; } public function shouldAllowPublic() { return true; } } diff --git a/src/controller/class/SemiStructuredObjectNewInstanceController.php b/src/controller/class/SemiStructuredObjectNewInstanceController.php index 8e1ba73..dd040fa 100644 --- a/src/controller/class/SemiStructuredObjectNewInstanceController.php +++ b/src/controller/class/SemiStructuredObjectNewInstanceController.php @@ -1,53 +1,29 @@ getViewer(); $id = $request->getURIData('id'); $object_type = id(new SemiStructuredObjectTypeQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->executeOne(); if (!$object_type) { return new Aphront404Response(); } $this->setObjectType($object_type); $object = SemiStructuredObjectInstance::initializeNewObjectInstance( $viewer, $object_type); return id(new SemiStructuredObjectInstanceEditEngine()) ->setTargetObject($object) ->setController($this) ->buildResponse(); - - - $crumbs = $this->buildApplicationCrumbs(); - $title = $object_type->getName(); - - $header = $this->buildHeaderView(); - // $curtain = $this->buildCurtain(); - - - $view = id(new PHUITwoColumnView()) - ->setHeader($header) - // ->setCurtain($curtain) - ->setMainColumn(array( - )) - ->addPropertySection(pht('Description'), "ixi"); - - $navigation = $this->buildSideNavView('view'); - - return $this->newPage() - ->setTitle($title) - ->setCrumbs($crumbs) - ->setPageObjectPHIDs(array($object_type->getPHID())) - ->setNavigation($navigation) - ->appendChild($view); - } } +} diff --git a/src/controller/class/SemiStructuredObjectTypeViewController.php b/src/controller/class/SemiStructuredObjectTypeViewController.php index b525313..2688005 100644 --- a/src/controller/class/SemiStructuredObjectTypeViewController.php +++ b/src/controller/class/SemiStructuredObjectTypeViewController.php @@ -1,166 +1,174 @@ getViewer(); $id = $request->getURIData('id'); $object_type = id(new SemiStructuredObjectTypeQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->executeOne(); if (!$object_type) { return new Aphront404Response(); } $this->setObjectType($object_type); $crumbs = $this->buildApplicationCrumbs(); $title = $object_type->getName(); $header = $this->buildHeaderView(); $curtain = $this->buildCurtain(); $details = $this->buildDetailsView(); $timeline = $this->buildTransactionTimeline( $object_type, new SemiStructuredObjectTypeTransactionQuery()); $comment_view = id(new SemiStructuredObjectTypeEditEngine()) ->setViewer($viewer) ->buildEditEngineCommentView($object_type); $instances_view = $this->buildInstancesView(); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) ->setMainColumn(array( $instances_view, $timeline, $comment_view, )) ->addPropertySection(pht('Description'), $details); $navigation = $this->buildSideNavView('view'); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($object_type->getPHID())) ->setNavigation($navigation) ->appendChild($view); } private function buildDetailsView() { $viewer = $this->getViewer(); $object_type = $this->getObjectType(); $view = id(new PHUIPropertyListView()) ->setUser($viewer); $description = $object_type->getDescription(); if (strlen($description)) { $view->addTextContent( new PHUIRemarkupView($viewer, $description)); } + $custom_fields_def = $object_type->getCustomFieldsConfig(); + $custom_fields_def = id(new PhutilJSON()) + ->encodeFormatted($custom_fields_def); + + $view->addProperty( + pht('Custom Fields'), + phutil_tag('pre', array(), $custom_fields_def)); + return $view; } private function buildCurtain() { $viewer = $this->getViewer(); $object_type = $this->getObjectType(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $object_type, PhabricatorPolicyCapability::CAN_EDIT); $can_create_instance = true; $id = $object_type->getID(); $edit_uri = $this->getApplicationURI("/editclass/{$id}/"); $create_uri = $this->getApplicationURI("/type/{$id}/new/"); $archive_uri = $this->getApplicationURI("/archive/{$id}/"); $curtain = $this->newCurtainView($object_type); $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Object Type')) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setHref($edit_uri)); $curtain->addAction( id(new PhabricatorActionView()) - ->setName(pht("Create New Instance")) - ->setIcon("fa-plus") + ->setName(pht('Create New Instance')) + ->setIcon('fa-plus') ->setDisabled(!$can_create_instance) ->setHref($create_uri)); if ($object_type->isArchived()) { $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Activate Object Type')) ->setIcon('fa-check') ->setDisabled(!$can_edit) ->setWorkflow($can_edit) ->setHref($archive_uri)); } else {/* $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Archive Object Type')) ->setIcon('fa-ban') ->setDisabled(!$can_edit) ->setWorkflow($can_edit) ->setHref($archive_uri)); */ } return $curtain; } private function buildInstancesView() { $viewer = $this->getViewer(); $object_type = $this->getObjectType(); $instances = id(new SemiStructuredObjectInstanceQuery()) ->setViewer($viewer) ->setLimit(10) ->withClassIds(array($object_type->getId())) ->execute(); $content = id(new SemiStructuredObjectInstanceSearchEngine()) ->setViewer($viewer) ->renderResultsDirectly($instances); // $content is PhabricatorApplicationSearchResultView $box = new PHUIObjectBoxView(); $interface = 'PhabricatorApplicationSearchResultView'; if ($content->getObjectList()) { $box->setObjectList($content->getObjectList()); } if ($content->getTable()) { $box->setTable($content->getTable()); } if ($content->getContent()) { $box->appendChild($content->getContent()); } $box ->setHeader(pht('Instances of this type')); return $box; } } diff --git a/src/controller/instance/SemiStructuredObjectInstanceController.php b/src/controller/instance/SemiStructuredObjectInstanceController.php index 4e7a41a..f1415c3 100644 --- a/src/controller/instance/SemiStructuredObjectInstanceController.php +++ b/src/controller/instance/SemiStructuredObjectInstanceController.php @@ -1,88 +1,88 @@ object = $object; return $this; } public function getObject() { return $this->object; } // TODO ??? public function buildApplicationMenu() { return $this->buildSideNavView()->getMenu(); } protected function buildHeaderView() { $viewer = $this->getViewer(); $object = $this->getObject(); $object_type = $object->getClass(); if ($object_type->isArchived()) { $status_icon = 'fa-ban'; $status_color = 'dark'; } else { $status_icon = 'fa-check'; $status_color = 'bluegrey'; } $status_name = idx( SemiStructuredObjectType::getStatusNameMap(), $object_type->getStatus()); return id(new PHUIHeaderView()) - ->setHeader(pht("%s: %d %s", $object_type->getName(), $object->getID(), $object->getName())) + ->setHeader(pht('%s: %d %s', $object_type->getName(), $object->getID(), $object->getName())) ->setUser($viewer) ->setPolicyObject($object_type) ->setStatus($status_icon, $status_color, $status_name) ->setHeaderIcon('fa-trophy'); } protected function buildApplicationCrumbs() { $object = $this->getObject(); $object_type = $object->getClass(); $crumbs = parent::buildApplicationCrumbs(); $crumbs->addTextCrumb($object_type->getName(), $object_type->getURI()); - $crumbs->addTextCrumb(pht("%d %s", $object->getID(), $object->getName()), $object->getURI()); + $crumbs->addTextCrumb(pht('%d %s', $object->getID(), $object->getName()), $object->getURI()); $crumbs->setBorder(true); return $crumbs; } protected function buildSideNavView($filter = null) { $viewer = $this->getViewer(); $object = $this->getObject(); $object_type = $object->getClass(); $id = $object->getID(); $nav = id(new AphrontSideNavFilterView()) ->setBaseURI(new PhutilURI($this->getApplicationURI())); $nav->addLabel(pht('Instance of %s', $object_type->getName())); // TODO figure out menu $nav->addFilter( 'view', pht('View Object Type'), $object_type->getURI(), 'fa-trophy'); $nav->addFilter( 'instances', pht('View Instances'), $this->getApplicationURI("/recipients/{$id}/"), 'fa-group'); $nav->selectFilter($filter); return $nav; } } diff --git a/src/controller/instance/SemiStructuredObjectInstanceViewController.php b/src/controller/instance/SemiStructuredObjectInstanceViewController.php index 3043977..b9cb67b 100644 --- a/src/controller/instance/SemiStructuredObjectInstanceViewController.php +++ b/src/controller/instance/SemiStructuredObjectInstanceViewController.php @@ -1,108 +1,114 @@ getViewer(); $id = $request->getURIData('id'); $object = id(new SemiStructuredObjectInstanceQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->executeOne(); if (!$object) { return new Aphront404Response(); } $this->setObject($object); $object_type = $object->getClass(); $crumbs = $this->buildApplicationCrumbs(); $title = pht( - "%s: %d %s", + '%s: %d %s', $object_type->getName(), $object->getID(), $object->getName()); $header = $this->buildHeaderView(); $curtain = $this->buildCurtain(); $details = $this->buildDetailsView(); $timeline = $this->buildTransactionTimeline( $object, new SemiStructuredObjectInstanceTransactionQuery()); $comment_view = id(new SemiStructuredObjectInstanceEditEngine()) ->setViewer($viewer) ->buildEditEngineCommentView($object); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) ->setMainColumn(array( $timeline, $comment_view, )) ->addPropertySection(pht('Details'), $details); $navigation = $this->buildSideNavView('view'); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($object->getPHID())) ->setNavigation($navigation) ->appendChild($view); } private function buildDetailsView() { $viewer = $this->getViewer(); $object = $this->getObject(); $view = id(new PHUIPropertyListView()) ->setUser($viewer); + $description = $object->getDescription(); + if (strlen($description)) { + $view->addTextContent( + new PHUIRemarkupView($viewer, $description)); + } + + $field_list = PhabricatorCustomField::getObjectFields( + $object, + PhabricatorCustomField::ROLE_VIEW); + $field_list->appendFieldsToPropertyList($object, $viewer, $view); + + $content = $object->getRawData(); $content = id(new PhutilJSON()) ->encodeFormatted(json_decode($content)); - $description = $object->getDescription(); - if (strlen($description)) { - $view->addTextContent( - new PHUIRemarkupView($viewer, $description)); - } - $view->addProperty( pht('Structured content'), phutil_tag('pre', array(), $content)); return $view; } private function buildCurtain() { $viewer = $this->getViewer(); $object = $this->getObject(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $object, PhabricatorPolicyCapability::CAN_EDIT); $id = $object->getID(); $edit_uri = $this->getApplicationURI("/editinstance/{$id}/"); $curtain = $this->newCurtainView($object); $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Instance')) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setHref($edit_uri)); return $curtain; } } diff --git a/src/customfield/SemiStructuredConfiguredCustomField.php b/src/customfield/SemiStructuredConfiguredCustomField.php new file mode 100644 index 0000000..8b8e825 --- /dev/null +++ b/src/customfield/SemiStructuredConfiguredCustomField.php @@ -0,0 +1,37 @@ +hasAttachedClass()) { + $class = $instance->getClass(); + $config = $class->getCustomFieldsConfig(); + } else { + $config = array(); + } + + return PhabricatorStandardCustomField::buildStandardFields($this, $config); + } + + public function newStorageObject() { + return new SemiStructuredInstanceConfiguredCustomFieldStorage(); + } + + protected function newStringIndexStorage() { + return new SemiStructuredInstanceCustomFieldStringIndex(); + } + + protected function newNumericIndexStorage() { + return new SemiStructuredInstanceCustomFieldNumericIndex(); + } + +} diff --git a/src/customfield/SemiStructuredInstanceCustomField.php b/src/customfield/SemiStructuredInstanceCustomField.php new file mode 100644 index 0000000..afaddc2 --- /dev/null +++ b/src/customfield/SemiStructuredInstanceCustomField.php @@ -0,0 +1,4 @@ +objectType = $object_type; return $this; } protected function newEditableObject() { $viewer = $this->getViewer(); return SemiStructuredObjectInstance::initializeNewObjectInstance( $viewer, $this->objectType); } protected function newObjectQuery() { return new SemiStructuredObjectInstanceQuery(); } protected function getObjectCreateTitleText($object) { return pht('Create Object Instance'); } protected function getObjectCreateButtonText($object) { return pht('Create Object Instance'); } protected function getObjectCreateCancelURI($object) { // TODO if objjec has link to class - go to that class return '/semistructured/'; } protected function getEditorURI() { return $this->getApplication()->getApplicationURI('editinstance/'); } protected function getObjectEditTitleText($object) { return pht('Edit Object: %d', $object->getID()); } protected function getObjectEditShortText($object) { return pht('Edit Object'); } protected function getObjectCreateShortText() { return pht('Create Object Instance'); } protected function getObjectName() { return pht('Object Instance'); } protected function getObjectViewURI($object) { return $object->getURI(); } protected function buildCustomEditFields($object) { $fields = array( id(id(new PhabricatorStaticEditField()) ->setKey('objecttype') - ->setLabel(pht("Object Type")) + ->setLabel(pht('Object Type')) ->setValue($object->getClass()->getName())), id(new PhabricatorIntEditField()) ->setKey('classID') ->setLabel(pht('Object Type')) ->setDescription(pht('Type of the object.')) ->setConduitDescription(pht('Set type of object.')) ->setIsHidden(true) ->setTransactionType( SemiStructuredObjectInstanceClassTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getClassID()), id(new PhabricatorTextEditField()) ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the object.')) ->setConduitDescription(pht('Rename the object.')) ->setConduitTypeDescription(pht('New object name.')) ->setTransactionType( SemiStructuredObjectInstanceNameTransaction::TRANSACTIONTYPE) ->setValue($object->getName()), id(new PhabricatorRemarkupEditField()) ->setKey('description') ->setLabel(pht('Free text')) ->setDescription(pht('Object instance free text (Remarkup).')) ->setConduitTypeDescription(pht('New object free type.')) ->setTransactionType( SemiStructuredObjectInstanceDescriptionTransaction::TRANSACTIONTYPE) ->setValue($object->getDescription()), id(new PhabricatorTextAreaEditField()) ->setKey('rawdata') ->setLabel(pht('Structured content')) ->setDescription(pht('Object structured data (JSON).')) ->setConduitTypeDescription(pht('Object body (JSON).')) ->setMonospaced(true) ->setIsRequired(true) ->setTransactionType( SemiStructuredObjectInstanceRawDataTransaction::TRANSACTIONTYPE) ->setValue($object->getRawData()), ); return $fields; } } diff --git a/src/editor/SemiStructuredObjectTypeEditEngine.php b/src/editor/SemiStructuredObjectTypeEditEngine.php index 5a1ff18..91e033a 100644 --- a/src/editor/SemiStructuredObjectTypeEditEngine.php +++ b/src/editor/SemiStructuredObjectTypeEditEngine.php @@ -1,99 +1,112 @@ getViewer(); return SemiStructuredObjectType::initializeNewObjectType($viewer); } protected function newObjectQuery() { return new SemiStructuredObjectTypeQuery(); } protected function getObjectCreateTitleText($object) { return pht('Create Object Type'); } protected function getObjectCreateButtonText($object) { return pht('Create Object Type'); } protected function getObjectCreateCancelURI($object) { return '/semistructured/'; } protected function getEditorURI() { return $this->getApplication()->getApplicationURI('editclass/'); } protected function getObjectEditTitleText($object) { return pht('Edit Object Type: %s', $object->getName()); } protected function getObjectEditShortText($object) { return pht('Edit Object Type'); } protected function getObjectCreateShortText() { return pht('Create Object Type'); } protected function getObjectName() { return pht('Object Type'); } protected function getObjectViewURI($object) { return $object->getURI(); } protected function buildCustomEditFields($object) { $fields = array( id(new PhabricatorTextEditField()) ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the object type.')) ->setConduitDescription(pht('Rename the type.')) ->setConduitTypeDescription(pht('New type name.')) ->setTransactionType( SemiStructuredObjectTypeNameTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getName()), id(new PhabricatorRemarkupEditField()) ->setKey('description') ->setLabel(pht('Description')) ->setDescription(pht('Object Type long description.')) ->setConduitTypeDescription(pht('New type description.')) ->setTransactionType( SemiStructuredObjectTypeDescriptionTransaction::TRANSACTIONTYPE) ->setValue($object->getDescription()), + + id(new PhabricatorTextAreaEditField()) + ->setKey('customfieldsdef') + ->setLabel(pht('Custom Fields Definition')) + ->setDescription(pht('Custom fields for the instances of this type(JSON).')) + ->setConduitTypeDescription(pht('Custom Fields Definition (JSON).')) + ->setMonospaced(true) + ->setIsRequired(false) + ->setTransactionType( + SemiStructuredObjectTypeCustomFieldsTransaction::TRANSACTIONTYPE) + ->setValue( + id(new PhutilJSON()) + ->encodeFormatted($object->getCustomFieldsConfig())), ); return $fields; } } diff --git a/src/phid/SemiStructuredObjectInstancePHIDType.php b/src/phid/SemiStructuredObjectInstancePHIDType.php index 02fa5b8..b838e3c 100644 --- a/src/phid/SemiStructuredObjectInstancePHIDType.php +++ b/src/phid/SemiStructuredObjectInstancePHIDType.php @@ -1,44 +1,44 @@ withPHIDs($phids); } public function loadHandles( PhabricatorHandleQuery $query, array $handles, array $objects) { foreach ($handles as $phid => $handle) { $item = $objects[$phid]; $id = $item->getID(); $class = $item->getClass(); - $handle->setName(pht("%s %d", $class->getName(), $id)); + $handle->setName(pht('%s %d', $class->getName(), $id)); $handle->setURI("/semistruct/instance/{$id}/"); } } } diff --git a/src/query/SemiStructuredObjectInstanceQuery.php b/src/query/SemiStructuredObjectInstanceQuery.php index 54687e7..9dae453 100644 --- a/src/query/SemiStructuredObjectInstanceQuery.php +++ b/src/query/SemiStructuredObjectInstanceQuery.php @@ -1,119 +1,120 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withClassIDs(array $class_ids) { $this->classIDs = $class_ids; return $this; } public function withStatuses(array $statuses) { $this->statuses = $statuses; return $this; } public function withCanEdit($can_edit) { $this->canEdit = $can_edit; return $this; } public function newResultObject() { + // TODO maybe if I have classId, I can attach the class directly? return new SemiStructuredObjectInstance(); } protected function didFilterPage(array $items) { $phids = mpull($items, 'getPHID'); if ($this->canEdit) { $items = id(new PhabricatorPolicyFilter()) ->setViewer($this->getViewer()) ->requireCapabilities(array( PhabricatorPolicyCapability::CAN_EDIT, )) ->apply($items); } return $items; } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); if ($this->ids !== null) { $where[] = qsprintf( $conn, 'instances.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, 'instances.phid IN (%Ls)', $this->phids); } if ($this->classIDs !== null) { $where[] = qsprintf( $conn, 'instances.classID IN (%Ls)', $this->classIDs); } if ($this->statuses !== null) { $where[] = qsprintf( $conn, 'instances.status IN (%Ls)', $this->statuses); } return $where; } protected function willFilterPage(array $objects) { $classes = id(new SemiStructuredObjectTypeQuery()) ->setViewer($this->getViewer()) ->withIDs(mpull($objects, 'getClassID')) ->execute(); foreach ($objects as $key => $object) { $class = idx($classes, $object->getClassID()); if (!$class) { unset($objects[$key]); continue; } $object->attachClass($class); } return $objects; } public function getQueryApplicationClass() { return 'SemiStructuredDataApplication'; } protected function getPrimaryTableAlias() { return 'instances'; } } diff --git a/src/storage/SemiStructuredInstanceConfiguredCustomFieldStorage.php b/src/storage/SemiStructuredInstanceConfiguredCustomFieldStorage.php new file mode 100644 index 0000000..603c3bf --- /dev/null +++ b/src/storage/SemiStructuredInstanceConfiguredCustomFieldStorage.php @@ -0,0 +1,10 @@ +setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) // TODO take policies from class? // ->setEditPolicy($actor->getPHID()) ->setStatus(self::STATUS_ACTIVE); if ($object_class !== null) { $object ->setClassID($object_class->getID()) ->attachClass($object_class); } return $object; } public function attachClass( SemiStructuredObjectType $object_class = null) { $this->class = $object_class; return $this; } public function getClass() { return $this->assertAttached($this->class); } + public function hasAttachedClass() { + return $this->class !== self::ATTACHABLE; + } + public function getURI() { return urisprintf('/semistruct/instance/%d/', $this->getID()); } public function getIcon() { // TODO return 'fa-google'; } public static function getStatusNameMap() { return array( // TODO remove this self::STATUS_ACTIVE => pht('Active'), self::STATUS_ARCHIVED => pht('Archived'), ); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'rawData' => self::SERIALIZATION_JSON, ), self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'sort255', 'description' => 'text', 'status' => 'text32', ), self::CONFIG_KEY_SCHEMA => array( 'classID' => array( 'columns' => array('classID'), ), ), ) + parent::getConfiguration(); } public function getPHIDType() { return SemiStructuredObjectInstancePHIDType::TYPECONST; } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new SemiStructuredObjectInstanceTransactionEditor(); } public function getApplicationTransactionTemplate() { return new SemiStructuredObjectInstanceTransaction(); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { return; switch ($capability) { // TODO case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return true; } + /* -( PhabricatorCustomFieldInterface )------------------------------------ */ + + + public function getCustomFieldSpecificationForRole($role) { + return PhabricatorEnv::getEnvConfig('user.fields'); + } + + public function getCustomFieldBaseClass() { + return 'SemiStructuredInstanceCustomField'; + } + + public function getCustomFields() { + return $this->assertAttached($this->customFields); + } + + public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { + $this->customFields = $fields; + return $this; + } + /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $this->delete(); $this->saveTransaction(); } } diff --git a/src/storage/SemiStructuredObjectType.php b/src/storage/SemiStructuredObjectType.php index be647b1..9c95246 100644 --- a/src/storage/SemiStructuredObjectType.php +++ b/src/storage/SemiStructuredObjectType.php @@ -1,133 +1,136 @@ setName('') ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) ->setEditPolicy($actor->getPHID()) ->setStatus(self::STATUS_ACTIVE); } public static function getStatusNameMap() { return array( self::STATUS_ACTIVE => pht('Active'), self::STATUS_ARCHIVED => pht('Archived'), ); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, + self::CONFIG_SERIALIZATION => array( + 'customFieldsConfig' => self::SERIALIZATION_JSON, + ), self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'sort255', 'status' => 'text32', 'description' => 'text', ), ) + parent::getConfiguration(); } public function getPHIDType() { return SemiStructuredObjectTypePHIDType::TYPECONST; } public function isArchived() { return ($this->getStatus() == self::STATUS_ARCHIVED); } public function getURI() { return urisprintf('/semistruct/type/%d/', $this->getID()); } public function getObjectName() { return pht('Object Type %d', $this->getID()); } public function getIcon() { // TODO return 'fa-bear'; } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new SemiStructuredObjectTypeTransactionEditor(); } public function getApplicationTransactionTemplate() { return new SemiStructuredObjectTypeTransaction(); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, // TODO can-create-instances ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return false; } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $this->delete(); $this->saveTransaction(); } /* -( PhabricatorFerretInterface )----------------------------------------- */ public function newFerretEngine() { return new SemiStructuredObjectTypeFerretEngine(); } } diff --git a/src/xaction/class/SemiStructuredObjectTypeCustomFieldsTransaction.php b/src/xaction/class/SemiStructuredObjectTypeCustomFieldsTransaction.php new file mode 100644 index 0000000..de54ad7 --- /dev/null +++ b/src/xaction/class/SemiStructuredObjectTypeCustomFieldsTransaction.php @@ -0,0 +1,87 @@ +encodeFormatted($object->getCustomFieldsConfig()); + } + + public function applyInternalEffects($object, $value) { + + if ($value !== null && strlen($value)) { + $value = phutil_json_decode($value); + } + + $object->setCustomFieldsConfig($value); + } + + public function getTitle() { + return pht( + '%s updated the custom fields definition of the object type.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the custom fields definition for object type %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO CUSTOM FIELDS DEFINITION OF OBJECT TYPE'); + } + + public function hasChangeDetailView() { + return true; + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($old) + ->setNewText($new); + } + + + public function validateTransactions($object, array $xactions) { + $errors = array(); + + if (!$xactions) { + return $errors; + } + + $json_parser = new PhutilJSONParser(); + + if ($this->isEmptyTextTransaction($object->getCustomFieldsConfig(), $xactions)) { + // TODO custom fields are optional. + // $errors[] = $this->newRequiredError( + // pht('Object must have content.')); + } + + foreach ($xactions as $xaction) { + $new_value = $xaction->getNewValue(); + + try { + if (strlen($new_value)) { + phutil_json_decode($new_value); + } + } catch (PhutilJSONParserException $ex) { + $errors[] = $this->newInvalidError( + pht('Definition must be valid json!')); + } + } + + return $errors; + } + +}