diff --git a/resources/sql/autopatches/20210802.legalpad_document_signature.01.phid.sql b/resources/sql/autopatches/20210802.legalpad_document_signature.01.phid.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20210802.legalpad_document_signature.01.phid.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_legalpad.legalpad_documentsignature + ADD phid VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20210802.legalpad_document_signature.02.phid-populate.php b/resources/sql/autopatches/20210802.legalpad_document_signature.02.phid-populate.php new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20210802.legalpad_document_signature.02.phid-populate.php @@ -0,0 +1,79 @@ +establishConnection('w'); +$table_name = $docsig_table->getTableName(); + +$chunk_size = 4096; + +$temporary_table = 'tmp_20210802_docsig_id_map'; + +try { + queryfx( + $conn, + 'CREATE TEMPORARY TABLE %T ( + docsig_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + docsig_phid VARBINARY(64) NOT NULL)', + $temporary_table); +} catch (AphrontAccessDeniedQueryException $ex) { + throw new PhutilProxyException( + pht( + 'Failed to "CREATE TEMPORARY TABLE". You may need to "GRANT" the '. + 'current MySQL user this permission.'), + $ex); +} + +$table_iterator = id(new LiskRawMigrationIterator($conn, $table_name)) + ->setPageSize($chunk_size); + +$chunk_iterator = new PhutilChunkedIterator($table_iterator, $chunk_size); +foreach ($chunk_iterator as $chunk) { + + $map = array(); + foreach ($chunk as $docsig_row) { + $phid = $docsig_row['phid']; + + if (strlen($phid)) { + continue; + } + + $phid = PhabricatorPHID::generateNewPHID($phid_type); + $id = $docsig_row['id']; + + $map[(int)$id] = $phid; + } + + if (!$map) { + continue; + } + + $sql = array(); + foreach ($map as $docsig_id => $docsig_phid) { + $sql[] = qsprintf( + $conn, + '(%d, %s)', + $docsig_id, + $docsig_phid); + } + + queryfx( + $conn, + 'TRUNCATE TABLE %T', + $temporary_table); + + queryfx( + $conn, + 'INSERT INTO %T (docsig_id, docsig_phid) VALUES %LQ', + $temporary_table, + $sql); + + queryfx( + $conn, + 'UPDATE %T c JOIN %T x ON c.id = x.docsig_id + SET c.phid = x.docsig_phid', + $table_name, + $temporary_table); +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1723,6 +1723,7 @@ 'LegalpadDocumentQuery' => 'applications/legalpad/query/LegalpadDocumentQuery.php', 'LegalpadDocumentRemarkupRule' => 'applications/legalpad/remarkup/LegalpadDocumentRemarkupRule.php', 'LegalpadDocumentRequireSignatureTransaction' => 'applications/legalpad/xaction/LegalpadDocumentRequireSignatureTransaction.php', + 'LegalpadDocumentSearchConduitAPIMethod' => 'applications/legalpad/conduit/LegalpadDocumentSearchConduitAPIMethod.php', 'LegalpadDocumentSearchEngine' => 'applications/legalpad/query/LegalpadDocumentSearchEngine.php', 'LegalpadDocumentSignController' => 'applications/legalpad/controller/LegalpadDocumentSignController.php', 'LegalpadDocumentSignature' => 'applications/legalpad/storage/LegalpadDocumentSignature.php', @@ -1742,6 +1743,7 @@ 'LegalpadRequireSignatureHeraldAction' => 'applications/legalpad/herald/LegalpadRequireSignatureHeraldAction.php', 'LegalpadSchemaSpec' => 'applications/legalpad/storage/LegalpadSchemaSpec.php', 'LegalpadSignatureNeededByObjectEdgeType' => 'applications/legalpad/edge/LegalpadSignatureNeededByObjectEdgeType.php', + 'LegalpadSignatureSearchConduitAPIMethod' => 'applications/legalpad/conduit/LegalpadSignatureSearchConduitAPIMethod.php', 'LegalpadTransaction' => 'applications/legalpad/storage/LegalpadTransaction.php', 'LegalpadTransactionComment' => 'applications/legalpad/storage/LegalpadTransactionComment.php', 'LegalpadTransactionQuery' => 'applications/legalpad/query/LegalpadTransactionQuery.php', @@ -3659,8 +3661,11 @@ 'PhabricatorLabelProfileMenuItem' => 'applications/search/menuitem/PhabricatorLabelProfileMenuItem.php', 'PhabricatorLanguageSettingsPanel' => 'applications/settings/panel/PhabricatorLanguageSettingsPanel.php', 'PhabricatorLegalpadApplication' => 'applications/legalpad/application/PhabricatorLegalpadApplication.php', + 'PhabricatorLegalpadBodySearchEngineAttachment' => 'applications/legalpad/engineextension/PhabricatorLegalpadBodySearchEngineAttachment.php', 'PhabricatorLegalpadDocumentPHIDType' => 'applications/legalpad/phid/PhabricatorLegalpadDocumentPHIDType.php', + 'PhabricatorLegalpadDocumentSignaturePHIDType' => 'applications/legalpad/phid/PhabricatorLegalpadDocumentSignaturePHIDType.php', 'PhabricatorLegalpadSignaturePolicyRule' => 'applications/legalpad/policyrule/PhabricatorLegalpadSignaturePolicyRule.php', + 'PhabricatorLegalpadSignaturesSearchEngineAttachment' => 'applications/legalpad/engineextension/PhabricatorLegalpadSignaturesSearchEngineAttachment.php', 'PhabricatorLibraryTestCase' => '__tests__/PhabricatorLibraryTestCase.php', 'PhabricatorLinkProfileMenuItem' => 'applications/search/menuitem/PhabricatorLinkProfileMenuItem.php', 'PhabricatorLipsumArtist' => 'applications/lipsum/image/PhabricatorLipsumArtist.php', @@ -7992,6 +7997,7 @@ 'PhabricatorSubscribableInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorDestructibleInterface', + 'PhabricatorConduitResultInterface', ), 'LegalpadDocumentBody' => array( 'LegalpadDAO', @@ -8008,11 +8014,13 @@ 'LegalpadDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'LegalpadDocumentRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'LegalpadDocumentRequireSignatureTransaction' => 'LegalpadDocumentTransactionType', + 'LegalpadDocumentSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'LegalpadDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine', 'LegalpadDocumentSignController' => 'LegalpadController', 'LegalpadDocumentSignature' => array( 'LegalpadDAO', 'PhabricatorPolicyInterface', + 'PhabricatorConduitResultInterface', ), 'LegalpadDocumentSignatureAddController' => 'LegalpadController', 'LegalpadDocumentSignatureListController' => 'LegalpadController', @@ -8030,6 +8038,7 @@ 'LegalpadRequireSignatureHeraldAction' => 'HeraldAction', 'LegalpadSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'LegalpadSignatureNeededByObjectEdgeType' => 'PhabricatorEdgeType', + 'LegalpadSignatureSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'LegalpadTransaction' => 'PhabricatorModularTransaction', 'LegalpadTransactionComment' => 'PhabricatorApplicationTransactionComment', 'LegalpadTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -10233,8 +10242,11 @@ 'PhabricatorLabelProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorLanguageSettingsPanel' => 'PhabricatorEditEngineSettingsPanel', 'PhabricatorLegalpadApplication' => 'PhabricatorApplication', + 'PhabricatorLegalpadBodySearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'PhabricatorLegalpadDocumentPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorLegalpadDocumentSignaturePHIDType' => 'PhabricatorPHIDType', 'PhabricatorLegalpadSignaturePolicyRule' => 'PhabricatorPolicyRule', + 'PhabricatorLegalpadSignaturesSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'PhabricatorLibraryTestCase' => 'PhutilLibraryTestCase', 'PhabricatorLinkProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorLipsumArtist' => 'Phobject', diff --git a/src/applications/legalpad/conduit/LegalpadDocumentSearchConduitAPIMethod.php b/src/applications/legalpad/conduit/LegalpadDocumentSearchConduitAPIMethod.php new file mode 100644 --- /dev/null +++ b/src/applications/legalpad/conduit/LegalpadDocumentSearchConduitAPIMethod.php @@ -0,0 +1,18 @@ +needDocumentBodies(true); + } + + public function getAttachmentForObject($object, $data, $spec) { + return array( + 'body' => $object->getDocumentBody()->getText(), + 'preamble' => $object->getPreamble(), + ); + } + +} diff --git a/src/applications/legalpad/engineextension/PhabricatorLegalpadSignaturesSearchEngineAttachment.php b/src/applications/legalpad/engineextension/PhabricatorLegalpadSignaturesSearchEngineAttachment.php new file mode 100644 --- /dev/null +++ b/src/applications/legalpad/engineextension/PhabricatorLegalpadSignaturesSearchEngineAttachment.php @@ -0,0 +1,38 @@ +needSignatures(true); + } + + public function getAttachmentForObject($object, $data, $spec) { + $signatures = array(); + foreach ($object->getSignatures() as $signature) { + $signatures[] = array( + 'phid' => $signature->getPHID(), + 'signerPHID' => $signature->getSignerPHID(), + 'exemptionPHID' => $signature->getExemptionPHID(), + 'isExemption' => $signature->getIsExemption(), + 'signerName' => $signature->getSignerName(), + 'signerEmail' => $signature->getSignerEmail(), + 'documentVersion' => $signature->getDocumentVersion(), + 'dateCreated' => (int)$signature->getDateCreated(), + ); + } + + return array( + 'signatures' => $signatures, + ); + } + +} diff --git a/src/applications/legalpad/phid/PhabricatorLegalpadDocumentSignaturePHIDType.php b/src/applications/legalpad/phid/PhabricatorLegalpadDocumentSignaturePHIDType.php new file mode 100644 --- /dev/null +++ b/src/applications/legalpad/phid/PhabricatorLegalpadDocumentSignaturePHIDType.php @@ -0,0 +1,47 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $sig = $objects[$phid]; + $id = $sig->getID(); + $handle->setName('Signature '.$id); + + $signer_name = $sig->getSignerName(); + $handle->setFullName("Signature {$id} by {$signer_name}"); + $handle->setURI("/legalpad/signature/{$id}"); + } + } +} diff --git a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php --- a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php +++ b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php @@ -53,7 +53,7 @@ } if ($map['contributorPHIDs']) { - $query->withContributorPHIDs($map['creatorPHIDs']); + $query->withContributorPHIDs($map['contributorPHIDs']); } if ($map['creatorPHIDs']) { diff --git a/src/applications/legalpad/query/LegalpadDocumentSignatureQuery.php b/src/applications/legalpad/query/LegalpadDocumentSignatureQuery.php --- a/src/applications/legalpad/query/LegalpadDocumentSignatureQuery.php +++ b/src/applications/legalpad/query/LegalpadDocumentSignatureQuery.php @@ -4,6 +4,7 @@ extends PhabricatorCursorPagedPolicyAwareQuery { private $ids; + private $phids; private $documentPHIDs; private $signerPHIDs; private $documentVersions; @@ -16,6 +17,11 @@ return $this; } + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + public function withDocumentPHIDs(array $phids) { $this->documentPHIDs = $phids; return $this; @@ -46,20 +52,14 @@ return $this; } - protected function loadPage() { - $table = new LegalpadDocumentSignature(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); + public function newResultObject() { + return new LegalpadDocumentSignature(); + } + protected function loadPage() { + $table = $this->newResultObject(); + $data = $this->loadStandardPageRows($table); $signatures = $table->loadAllFromArray($data); - return $signatures; } @@ -98,6 +98,13 @@ $this->ids); } + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'phid IN (%Ls)', + $this->phids); + } + if ($this->documentPHIDs !== null) { $where[] = qsprintf( $conn, diff --git a/src/applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php b/src/applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php --- a/src/applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php +++ b/src/applications/legalpad/query/LegalpadDocumentSignatureSearchEngine.php @@ -5,6 +5,73 @@ private $document; + public function newQuery() { + return new LegalpadDocumentSignatureQuery(); + } + + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorUsersSearchField()) + ->setLabel(pht('Signed By')) + ->setKey('signerPHIDs') + ->setAliases(array('signer', 'signers', 'signerPHID')) + ->setDescription( + pht('Search for signatures by given users.')), + id(new PhabricatorPHIDsSearchField()) + ->setLabel(pht('Documents')) + ->setKey('documentPHIDs') + ->setAliases(array('document', 'documents', 'documentPHID')) + ->setDescription( + pht('Search for signatures on the given documents')), + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name Contains')) + ->setKey('nameContains') + ->setDescription( + pht('Search for signatures with a name that contains a given string.')), + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Email Contains')) + ->setKey('emailContains') + ->setDescription( + pht('Search for signatures with an email that contains a given string.')), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created After')) + ->setKey('createdStart'), + id(new PhabricatorSearchDateField()) + ->setLabel(pht('Created Before')) + ->setKey('createdEnd'), + ); + } + + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); + + if ($map['signerPHIDs']) { + $query->withSignerPHIDs($map['signerPHIDs']); + } + + if ($map['documentPHIDs']) { + $query->withDocumentPHIDs($map['documentPHIDs']); + } + + if ($map['createdStart']) { + $query->withDateCreatedAfter($map['createdStart']); + } + + if ($map['createdEnd']) { + $query->withDateCreatedAfter($map['createdStart']); + } + + if ($map['nameContains']) { + $query->withNameContains($map['nameContains']); + } + + if ($map['emailContains']) { + $query->withEmailContains($map['emailContains']); + } + + return $query; + } + public function getResultTypeDescription() { return pht('Legalpad Signatures'); } diff --git a/src/applications/legalpad/storage/LegalpadDocument.php b/src/applications/legalpad/storage/LegalpadDocument.php --- a/src/applications/legalpad/storage/LegalpadDocument.php +++ b/src/applications/legalpad/storage/LegalpadDocument.php @@ -5,7 +5,8 @@ PhabricatorPolicyInterface, PhabricatorSubscribableInterface, PhabricatorApplicationTransactionInterface, - PhabricatorDestructibleInterface { + PhabricatorDestructibleInterface, + PhabricatorConduitResultInterface { protected $title; protected $contributorCount; @@ -159,6 +160,10 @@ return idx($map, $type, 'fa-user grey'); } + public function getPreamble() { + return $this->preamble; + } + /* -( PhabricatorSubscribableInterface )----------------------------------- */ @@ -167,6 +172,48 @@ return ($this->creatorPHID == $phid); } +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('title') + ->setType('string') + ->setDescription(pht('The title of this document')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('creatorPHID') + ->setType('phid') + ->setDescription(pht('This user who created this document')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('versions') + ->setType('int') + ->setDescription(pht('The number of versions of this document')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('requireSignature') + ->setType('bool') + ->setDescription(pht( + 'Whether signatures on this doc are required to use this install')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'title' => $this->title, + 'creatorPHID' => $this->creatorPHID, + 'versions' => $this->versions, + 'requireSignature' => (bool)$this->requireSignature, + ); + } + + public function getConduitSearchAttachments() { + return array( + id(new PhabricatorLegalpadBodySearchEngineAttachment()) + ->setAttachmentKey('body'), + id(new PhabricatorLegalpadSignaturesSearchEngineAttachment()) + ->setAttachmentKey('signatures'), + ); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/legalpad/storage/LegalpadDocumentSignature.php b/src/applications/legalpad/storage/LegalpadDocumentSignature.php --- a/src/applications/legalpad/storage/LegalpadDocumentSignature.php +++ b/src/applications/legalpad/storage/LegalpadDocumentSignature.php @@ -2,7 +2,9 @@ final class LegalpadDocumentSignature extends LegalpadDAO - implements PhabricatorPolicyInterface { + implements + PhabricatorPolicyInterface, + PhabricatorConduitResultInterface { const VERIFIED = 0; const UNVERIFIED = 1; @@ -23,6 +25,7 @@ protected function getConfiguration() { return array( + self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'signatureData' => self::SERIALIZATION_JSON, ), @@ -51,6 +54,14 @@ ) + parent::getConfiguration(); } + public function getPHIDType() { + return PhabricatorLegalpadDocumentSignaturePHIDType::TYPECONST; + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID($this->getPHIDType()); + } + public function save() { if (!$this->getSecretKey()) { $this->setSecretKey(Filesystem::readRandomCharacters(20)); @@ -71,6 +82,76 @@ return $this; } + public function getSignerPHID() { + return $this->signerPHID; + } + + public function getIsExemption() { + return (bool)$this->isExemption; + } + + public function getExemptionPHID() { + return $this->exemptionPHID; + } + + public function getSignerName() { + return $this->signerName; + } + + public function getSignerEmail() { + return $this->signerEmail; + } + + public function getDocumentVersion() { + return (int)$this->documentVersion; + } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('documentPHID') + ->setType('phid') + ->setDescription(pht('The PHID of the document')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('signerPHID') + ->setType('phid?') + ->setDescription(pht('The PHID of the signer')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('exemptionPHID') + ->setType('phid?') + ->setDescription(pht('The PHID of the user who granted the exemption')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('signerName') + ->setType('string') + ->setDescription(pht('The name used by the signer.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('signerEmail') + ->setType('string') + ->setDescription(pht('The email used by the signer.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('isExemption') + ->setType('bool') + ->setDescription(pht('Whether or not this signature is an exemption')), + ); + } + + public function getFieldValuesForConduit() { + return array( + 'documentPHID' => $this->getDocumentPHID(), + 'signerPHID' => $this->getSignerPHID(), + 'exemptionPHID' => $this->getExemptionPHID(), + 'signerName' => $this->getSignerName(), + 'signerEmail' => $this->getSignerEmail(), + 'isExemption' => $this->getIsExemption(), + ); + } + + public function getConduitSearchAttachments() { + return array(); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */