diff --git a/ethereum.links.action.yml b/ethereum.links.action.yml index fbcc203..5210db9 100644 --- a/ethereum.links.action.yml +++ b/ethereum.links.action.yml @@ -3,3 +3,9 @@ entity.ethereum_server.add_server_form: title: 'Add Ethereum node' appears_on: - ethereum.settings + +entity.ethereum_address.add_form: + route_name: entity.ethereum_address.add_form + title: 'Add address' + appears_on: + - entity.ethereum_address.collection diff --git a/ethereum.links.menu.yml b/ethereum.links.menu.yml index 62a38fa..2b077a7 100644 --- a/ethereum.links.menu.yml +++ b/ethereum.links.menu.yml @@ -14,3 +14,9 @@ ethereum.settings: parent: ethereum.admin_index title: 'Ethereum Network Configuration' description: 'Configure how Drupal connects to Ethereum.' + +entity.ethereum_address.collection: + title: 'Ethereum addresses' + parent: ethereum.admin_index + description: 'Create and manage Ethereum addresses.' + route_name: entity.ethereum_address.collection diff --git a/ethereum.links.task.yml b/ethereum.links.task.yml new file mode 100644 index 0000000..21c5a6f --- /dev/null +++ b/ethereum.links.task.yml @@ -0,0 +1,4 @@ +entity.ethereum_address.collection: + title: List + route_name: entity.ethereum_address.collection + base_route: entity.ethereum_address.collection diff --git a/ethereum.module b/ethereum.module index 8a8b5ec..389219b 100644 --- a/ethereum.module +++ b/ethereum.module @@ -51,6 +51,15 @@ function ethereum_field_formatter_info_alter(array &$info) { $info['basic_string']['field_types'][] = 'ethereum_address'; } +/** + * Implements hook_views_data_alter(). + */ +function ethereum_views_data_alter(array &$data) { + // Provide a nice list of options for the 'network' filter. + $data['ethereum_address']['network']['filter']['id'] = 'in_operator'; + $data['ethereum_address']['network']['filter']['options callback'] = 'Drupal\ethereum\Controller\EthereumController::getNetworksAsOptions'; +} + /** * Implements hook_js_settings_alter(). * diff --git a/src/Entity/EthereumAddress.php b/src/Entity/EthereumAddress.php new file mode 100644 index 0000000..90aabc5 --- /dev/null +++ b/src/Entity/EthereumAddress.php @@ -0,0 +1,168 @@ +get('address')->value; + } + + /** + * {@inheritdoc} + */ + public function setAddress($address) { + $this->set('address', $address); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getNetworkId() { + return $this->get('network')->value; + } + + /** + * {@inheritdoc} + */ + public function setNetworkID($network_id) { + $this->set('network', $network_id); + return $this; + } + + /** + * {@inheritdoc} + */ + public function isContract() { + return (bool) $this->get('contract')->value; + } + + /** + * {@inheritdoc} + */ + public function setContract($is_contract) { + $this->set('contract', $is_contract); + return $this; + } + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { + $fields = parent::baseFieldDefinitions($entity_type); + + // Ensure that we can store as many addresses as possible. + $fields['id']->setSetting('size', 'big'); + + $fields['address'] = BaseFieldDefinition::create('ethereum_address') + ->setLabel(new TranslatableMarkup('Address')) + ->setRequired(TRUE) + ->setReadOnly(TRUE) + ->setDisplayOptions('view', [ + 'label' => 'hidden', + 'type' => 'ethereum_address', + ]) + ->setDisplayOptions('form', [ + 'type' => 'ethereum_address', + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE);; + + $fields['network'] = BaseFieldDefinition::create('list_string') + ->setLabel(new TranslatableMarkup('Network')) + ->setDescription(new TranslatableMarkup('The Ethereum network on which this address exists.')) + ->setRequired(TRUE) + ->setReadOnly(TRUE) + ->setDefaultValueCallback('\Drupal\ethereum\Entity\EthereumAddress::getCurrentNetworkId') + ->setSetting('allowed_values_function', '\Drupal\ethereum\Controller\EthereumController::getNetworksAsOptions') + ->setDisplayOptions('form', [ + 'type' => 'options_select', + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE); + + + $fields['contract'] = BaseFieldDefinition::create('boolean') + ->setLabel(new TranslatableMarkup('Is contract')) + ->setDefaultValue(FALSE) + ->setReadOnly(TRUE) + ->setDisplayOptions('form', [ + 'type' => 'boolean_checkbox', + ]) + ->setDisplayConfigurable('view', TRUE) + ->setDisplayConfigurable('form', TRUE); + + return $fields; + } + + /** + * Default value callback for the 'network' base field. + * + * @see ::baseFieldDefinitions() + * + * @return array + * An array of default values. + */ + public static function getCurrentNetworkId() { + $current_server = \Drupal::config('ethereum.settings')->get('current_server'); + $server = EthereumServer::load($current_server); + return [$server->getNetworkId()]; + } + +} diff --git a/src/EthereumAddressInterface.php b/src/EthereumAddressInterface.php new file mode 100644 index 0000000..dea51f8 --- /dev/null +++ b/src/EthereumAddressInterface.php @@ -0,0 +1,66 @@ + $this->t('Address'), + 'network' => $this->t('Network'), + ]; + return $header + parent::buildHeader(); + } + + /** + * {@inheritdoc} + */ + public function buildRow(EntityInterface $entity) { + /** @var \Drupal\ethereum\EthereumAddressInterface $entity */ + $networks = EthereumController::getNetworks(); + $row['address'] = $entity->getAddress(); + $row['network'] = $networks[$entity->getNetworkId()]['label']; + return $row + parent::buildRow($entity); + } + +} diff --git a/src/EthereumAddressStorageSchema.php b/src/EthereumAddressStorageSchema.php new file mode 100644 index 0000000..9d951c2 --- /dev/null +++ b/src/EthereumAddressStorageSchema.php @@ -0,0 +1,51 @@ +storage->getBaseTable()]['unique keys'] += [ + 'ethereum_address__address_network' => ['address', 'network'], + ]; + + return $schema; + } + + /** + * {@inheritdoc} + */ + protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) { + $schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping); + $field_name = $storage_definition->getName(); + + if ($table_name == $this->storage->getBaseTable()) { + switch ($field_name) { + case 'address': + $schema['fields'][$field_name]['not null'] = TRUE; + break; + case 'network': + $schema['fields'][$field_name]['not null'] = TRUE; + $schema['fields'][$field_name]['length'] = 10; + break; + } + } + + return $schema; + } + +} diff --git a/src/Plugin/EntityReferenceSelection/EthereumAddressSelection.php b/src/Plugin/EntityReferenceSelection/EthereumAddressSelection.php new file mode 100644 index 0000000..18f7b39 --- /dev/null +++ b/src/Plugin/EntityReferenceSelection/EthereumAddressSelection.php @@ -0,0 +1,66 @@ + TRUE, + ] + parent::defaultConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $configuration = $this->getConfiguration(); + + $form['restrict_to_current_network'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Filter the available addresses by the current network'), + '#default_value' => $configuration['restrict_to_current_network'], + ]; + $form += parent::buildConfigurationForm($form, $form_state); + + return $form; + } + + /** + * {@inheritdoc} + */ + protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') { + $query = parent::buildEntityQuery($match, $match_operator); + + $configuration = $this->getConfiguration(); + + // Filter by the current network if needed. + if ($configuration['restrict_to_current_network']) { + $current_server = \Drupal::config('ethereum.settings')->get('current_server'); + $server = EthereumServer::load($current_server); + + $query->condition('network', $server->getNetworkId(), '='); + } + + return $query; + } + +} diff --git a/src/Plugin/Field/FieldFormatter/EthereumAddressFormatter.php b/src/Plugin/Field/FieldFormatter/EthereumAddressFormatter.php new file mode 100644 index 0000000..aeda39c --- /dev/null +++ b/src/Plugin/Field/FieldFormatter/EthereumAddressFormatter.php @@ -0,0 +1,173 @@ + '', + 'rel' => '', + 'target' => '', + ] + parent::defaultSettings(); + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $elements = parent::settingsForm($form, $form_state); + + $elements['link_to_network'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Link to the network address page'), + '#default_value' => $this->getSetting('link_to_network'), + ]; + $elements['rel'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Add rel="nofollow" to the link'), + '#return_value' => 'nofollow', + '#default_value' => $this->getSetting('rel'), + '#states' => array( + 'visible' => array( + array( + 'input[name="fields[address][settings_edit_form][settings][link_to_network]"]' => array('checked' => TRUE), + ), + ), + ), + ]; + $elements['target'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Open link in new window'), + '#return_value' => '_blank', + '#default_value' => $this->getSetting('target'), + '#states' => array( + 'visible' => array( + array( + 'input[name="fields[address][settings_edit_form][settings][link_to_network]"]' => array('checked' => TRUE), + ), + ), + ), + ]; + + return $elements; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = []; + + $settings = $this->getSettings(); + + if (!empty($settings['link_to_network'])) { + $summary[] = $this->t('Link to the network address page'); + } + if (!empty($settings['rel'])) { + $summary[] = $this->t('Add rel="@rel"', ['@rel' => $settings['rel']]); + } + if (!empty($settings['target'])) { + $summary[] = $this->t('Open link in new window'); + } + + return $summary; + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $elements = []; + $entity = $items->getEntity(); + $settings = $this->getSettings(); + + if (empty($settings['link_to_network'])) { + return parent::viewElements($items, $langcode); + } + + $current_server = \Drupal::config('ethereum.settings')->get('current_server'); + $server = EthereumServer::load($current_server); + $networks = EthereumController::getNetworks(); + + if ($entity->getEntityTypeId() === 'ethereum_address') { + $network = $networks[$entity->get('network')->first()->value]; + } + else { + $network = $networks[$server->getNetworkId()]; + } + + foreach ($items as $delta => $item) { + $url = $this->buildUrl($item, $network); + + $elements[$delta] = [ + '#type' => 'link', + '#title' => $item->value, + '#url' => $url, + '#options' => $url->getOptions(), + ]; + } + + return $elements; + } + + /** + * Builds the \Drupal\Core\Url object for a field item. + * + * @param \Drupal\ethereum\Plugin\Field\FieldType\EthereumAddressItem $item + * The field item being rendered. + * @param array $network + * An associative array with network information. + * + * @return \Drupal\Core\Url + * A Url object. + */ + protected function buildUrl(EthereumAddressItem $item, array $network) { + if (!empty($network['link_to_address'])) { + $network_link = str_replace('@address', $item->value, $network['link_to_address']); + $url = Url::fromUri($network_link); + } + else { + $url = Url::fromRoute(''); + } + + $settings = $this->getSettings(); + $options = $url->getOptions(); + + // Add optional 'rel' attribute to link options. + if (!empty($settings['rel'])) { + $options['attributes']['rel'] = $settings['rel']; + } + // Add optional 'target' attribute to link options. + if (!empty($settings['target'])) { + $options['attributes']['target'] = $settings['target']; + } + $url->setOptions($options); + + return $url; + } + +} diff --git a/src/Plugin/Field/FieldType/EthereumAddressItem.php b/src/Plugin/Field/FieldType/EthereumAddressItem.php index 8303009..e724764 100644 --- a/src/Plugin/Field/FieldType/EthereumAddressItem.php +++ b/src/Plugin/Field/FieldType/EthereumAddressItem.php @@ -14,7 +14,7 @@ * label = @Translation("Ethereum address"), * description = @Translation("Provides a field for Ethereum addresses."), * default_widget = "ethereum_address", - * default_formatter = "basic_string" + * default_formatter = "ethereum_address" * ) */ class EthereumAddressItem extends StringItem { diff --git a/src/Plugin/Validation/Constraint/EthereumAddressNetworkConstraint.php b/src/Plugin/Validation/Constraint/EthereumAddressNetworkConstraint.php new file mode 100644 index 0000000..ac01c25 --- /dev/null +++ b/src/Plugin/Validation/Constraint/EthereumAddressNetworkConstraint.php @@ -0,0 +1,32 @@ +ethereumAddressStorage = $ethereum_address_storage; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static($container->get('entity_type.manager')->getStorage('ethereum_address')); + } + + /** + * {@inheritdoc} + */ + public function validate($entity, Constraint $constraint) { + /** @var \Drupal\ethereum\EthereumAddressInterface $entity */ + $network_id = $entity->getNetworkId(); + $existing_address = $this->ethereumAddressStorage->loadByProperties([ + 'address' => $entity->getAddress(), + 'network' => $network_id, + ]); + + if ($existing_address) { + $networks = EthereumController::getNetworks(); + $this->context->buildViolation($constraint->message, ['%network_label' => $networks[$network_id]['label']]) + ->atPath('address') + ->addViolation(); + } + } + +}