rdfui-8.x-1.0-beta4/rdf_builder/src/Form/ContentBuilderForm.php
rdf_builder/src/Form/ContentBuilderForm.php
<?php
namespace Drupal\rdf_builder\Form;
use Drupal\Component\Utility\Html;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
use Drupal\rdfui\SchemaOrgConverter;
class ContentBuilderForm extends FormBase {
/**
* Easy_RDF Converter from rdfui.
*
* @var /Drupal/rdfui/EasyRdfConverter
*/
protected $converter;
/**
* The field type manager.
*
* @var \Drupal\node\Entity\NodeType
*/
protected $entity;
/**
* List of properties selected by user.
*
* @var array
*/
protected $properties;
/**
* Existing or created RDF Mapping.
*
* @var \Drupal\rdf\Entity\RdfMapping
*/
protected $rdfMapping;
/**
* Prefix for the content type.
*
* @var string
*/
private $prefix;
/**
* Array mapping schema.org data types to field types.
*
* @var array
*/
protected $datatype_field_mappings;
/**
* Constructs a new ContentBuilder.
*/
public function __construct() {
$this->converter = new SchemaOrgConverter();
$this->datatype_field_mappings = array(
'http://schema.org/Text' => 'string',
'http://schema.org/PostalAddress' => 'string_long',
'http://schema.org/Number' => 'integer',
'http://schema.org/MediaObject' => 'file',
'http://schema.org/AudioObject' => 'file',
'http://schema.org/DateTime' => 'datetime',
'http://schema.org/Date' => 'datetime',
'http://schema.org/Integer' => 'integer',
'http://schema.org/Time' => 'datetime',
'http://schema.org/ImageObject' => 'image',
'http://schema.org/Boolean' => 'boolean',
);
}
/**
* Submit handler for Content Builder next button.
* Capture the values from page one and store them away so they can be used
* at final submit time.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function nextSubmit(array &$form, FormStateInterface &$form_state) {
$form_state->set(['page_values', 1], $form_state->getValues());
if ($form_state->has(['page_values', 2])) {
$form_state->setValues($form_state->get(['page_values', 2]));
}
// When form rebuilds, build method would be chosen based on to page_num.
$form_state->set('page_num', 2);
$form_state->setRebuild();
}
/**
* @inheritdoc
*/
public function getFormId() {
return "rdf_builder_content_builder_form";
}
/**
* @inheritdoc
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Display page 2 if $form_state->get('page_num') == 2.
if ($form_state->has('page_num') && $form_state->get('page_num') == 2) {
return $this->buildFormPageTwo($form, $form_state);
}
// Otherwise build page 1.
$form_state->set('page_num', 1);
$form['#title'] = $this->t('Content types');
$form['description'] = array(
'#type' => 'item',
'#title' => $this->t('Create a content type by importing Schema.Org entity type.'),
);
$form['rdf-type'] = array(
'#title' => $this->t('Type'),
'#id' => 'rdf-predicate',
'#type' => 'select',
'#required' => TRUE,
'#options' => $this->converter->getListTypes(),
'#empty_option' => '',
'#default_value' => $form_state->getValue('rdf-type', ''),
'#attached' => array(
'library' => array(
'rdfui/drupal.rdfui.autocomplete',
),
),
'#description' => $this->t('Specify the type you want to associated to this content type e.g. Article, Blog, etc.'),
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['next'] = array(
'#type' => 'submit',
'#value' => $this->t('Next >>'),
'#button_type' => 'primary',
'#submit' => array(array($this, 'nextSubmit')),
'#validate' => array(array($this, 'nextValidate')),
);
return $form;
}
/**
* Returns the form for the second page.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return array
* The form structure.
*/
protected function buildFormPageTwo(array $form, FormStateInterface $form_state) {
$form['#title'] = $this->t('Content types');
$form['description'] = array(
'#type' => 'item',
'#title' => $this->t('Choose fields to start with.'),
);
$rdf_type = $form_state->get(['page_values', 1, 'rdf-type']);
$properties = $this->converter->getTypeProperties($rdf_type);
$field_types = \Drupal::service('plugin.manager.field.field_type')
->getUiDefinitions();
$field_type_options = array();
foreach ($field_types as $name => $field_type) {
// Skip field types which should not be added via user interface.
if (empty($field_type['no_ui'])) {
$field_type_options[$name] = $field_type['label'];
}
}
asort($field_type_options);
$table = array(
'#type' => 'table',
'#tree' => TRUE,
'#header' => array(
$this->t('Enable'),
$this->t('Property'),
$this->t('Data Type'),
),
'#regions' => array(),
'#attributes' => array(
'class' => array('rdfui-field-mappings'),
'id' => Html::getId('rdf-builder'),
),
);
foreach ($properties as $key => $value) {
$table[$key] = array(
'#attributes' => array(
'id' => Html::getClass($key),
),
'enable' => array(
'#type' => 'checkbox',
'#title' => $this->t('Enable'),
'#title_display' => 'invisible',
),
'property' => array(
'#markup' => Html::escape($value),
),
'type' => array(
'#type' => 'select',
'#title' => $this->t('Data Type'),
'#title_display' => 'invisible',
'#options' => $field_type_options,
'#default_value' => $this->getDefaultFieldType($key),
'#empty_option' => $this->t('- Select a field type -'),
'#attributes' => array('class' => array('field-type-select')),
'#cell_attributes' => array('colspan' => 2),
),
);
}
// Fields.
$table['#regions']['content']['rows_order'] = array();
foreach (Element::children($table) as $name) {
$table['#regions']['content']['rows_order'][] = $name;
}
$form['fields'] = $table;
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#button_type' => 'primary',
'#value' => $this->t('Save'),
);
$form['actions']['previous'] = array(
'#type' => 'submit',
'#value' => $this->t('< Back'),
'#submit' => array(array($this, 'pageTwoBackSubmit')),
'#limit_validation_errors' => array(),
'#validate' => array(array($this, 'pageTwoBackValidate')),
'#weight' => -1,
);
return $form;
}
/**
* Validate handler for the next button on first page.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function nextValidate(array $form, FormStateInterface $form_state) {
// @TODO validate if required.
}
/**
* Validate handler for the back button on second page.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function pageTwoBackValidate(array $form, FormStateInterface $form_state) {
// @TODO validate if required.
}
/**
* Back button handler submit handler.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function pageTwoBackSubmit(array &$form, FormStateInterface &$form_state) {
$form_state->setValues($form_state->get(['page_values', 1]));
$form_state->set('page_num', 1);
$form_state->setRebuild();
}
/**
* @inheritdoc
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
foreach ($form_state->getValue('fields') as $key => $property) {
if ($property['enable'] == 1) {
if (empty($property['type'])) {
$form_state->setErrorByName('fields][$key][type', $this->t('Create field: you need to provide a data type for %field.', array('%field' => explode(':', $key)[1])));
}
}
}
}
/**
* @inheritdoc
*
* Final submit handler- gather all data together and create new content type.
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->prefix = $this->randomString(4);
$this->properties = array();
foreach ($form_state->getValue('fields') as $key => $property) {
if ($property['enable'] == 1) {
$this->properties[$key] = $property;
}
}
$page_one_values = $form_state->get(['page_values', 1]);
$rdf_type = $page_one_values['rdf-type'];
$this->createNodeType($rdf_type);
$this->rdfMapping = rdf_get_mapping('node', $this->entity->id());
$this->rdfMapping->setBundleMapping(array('types' => array($rdf_type)));
$this->createField();
$this->rdfMapping->save();
$this->messenger()->addMessage($this->t('Content Type %label created', ['%label' => $this->entity->label()]));
/*@TODO Revert all saved content type and fields in case of error*/
$form_state->setRedirectUrl(new Url('entity.node.field_ui_fields', array(
'node_type' => $this->entity->id(),
)));
}
/**
* Creates a new node_type.
*
* @param string $rdf_type
* URI of the resource.
*/
protected function createNodeType($rdf_type) {
$type = explode(':', $rdf_type);
$type = $this->prefix . $type[1];
// Truncate if machine_name is longer than 32 char.
if (strlen($type) > 32) {
$type = substr($type, 0, 32);
}
$values = array(
'name' => $this->converter->label($rdf_type),
'type' => strtolower($type),
'description' => $this->converter->description($rdf_type),
);
try {
$this->entity = \Drupal::entityTypeManager()->getStorage('node_type')->create($values);
$this->entity->save();
}
catch (\Exception $e) {
$this->messenger()->addMessage($this->t('Error saving content type %invalid.', ['%invalid' => $rdf_type]));
}
}
/**
* Create fields for the selected properties.
*/
protected function createField() {
$entity_type = 'node';
$bundle = $this->entity->id();
foreach ($this->properties as $key => $value) {
$label = $this->converter->label($key);
// Add the field prefix and truncate if longer than 32 char.
$field_name = $this->prefix . strtolower($label);
if (strlen($field_name) > 32) {
$field_name = substr($field_name, 0, 32);
}
$field_storage = array(
'field_name' => $field_name,
'entity_type' => $entity_type,
'type' => $value['type'],
'translatable' => TRUE,
);
$instance = array(
'field_name' => $field_name,
'entity_type' => $entity_type,
'bundle' => $bundle,
'label' => $label,
// Field translatability should be explicitly enabled by the users.
'translatable' => FALSE,
);
// Create the field and instance.
try {
\Drupal::entityTypeManager()->getStorage('field_storage_config')->create($field_storage)->save();
\Drupal::entityTypeManager()->getStorage('field_config')->create($instance)->save();
// Make sure the field is displayed in the 'default' form mode (using
// default widget and settings). It stays hidden for other form modes
// until it is explicitly configured.
\Drupal::service('entity_display.repository')->getFormDisplay($entity_type, $bundle, 'default')
->setComponent($field_name)
->save();
// Make sure the field is displayed in the 'default' view mode (using
// default formatter and settings). It stays hidden for other view
// modes until it is explicitly configured.
\Drupal::service('entity_display.repository')->getFormDisplay($entity_type, $bundle, 'default')
->setComponent($field_name)
->save();
// RDF Mapping.
$this->rdfMapping->setFieldMapping($field_name, array(
'properties' => array($key),
)
);
}
catch (\Exception $e) {
$this->messenger()->addError($this->t('There was a problem creating field %label: !message', array(
'%label' => $instance['label'],
'!message' => $e->getMessage(),
)));
}
}
}
/**
* Generates a random string of lower case letters of a given length.
*
* @param int $length
* Length of the random string.
*
* @return string
* Return a random string.
*/
private function randomString($length = 4) {
$result = '';
for ($i = 0; $i < $length; $i++) {
$num = rand(97, 122);
$result .= chr($num);
}
$result = $result . '_';
return $result;
}
/**
* Gets default datatype for a given URI.
*
* @param string $uri
* URI of Schema.org property.
*
* @return string
* Default field type or text if there is no better match.
*/
protected function getDefaultFieldType($uri) {
$range_datatypes = $this->converter->getRangeDataTypes($uri);
foreach ($range_datatypes as $datatype) {
if (array_key_exists($datatype, $this->datatype_field_mappings)) {
return $this->datatype_field_mappings[$datatype];
}
}
return 'string';
}
}
