scanner-8.x-1.0-rc3/src/Form/ScannerForm.php
src/Form/ScannerForm.php
<?php
namespace Drupal\scanner\Form;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\Renderer;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\Core\Url;
use Drupal\scanner\Plugin\ScannerPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form for performing searching.
*/
class ScannerForm extends FormBase {
use StringTranslationTrait;
/**
* Constructs a ScannerForm object.
*
* @param \Drupal\Core\TempStore\PrivateTempStoreFactory $tempStore
* The private temp story factory.
* @param \Drupal\scanner\Plugin\ScannerPluginManager $scannerManager
* The scanner manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
* The language manager.
* @param \Drupal\Core\Render\Renderer $render
* The renderer.
* @param \Drupal\Core\Session\AccountProxyInterface $currentUser
* The current user.
*/
public function __construct(
protected PrivateTempStoreFactory $tempStore,
protected ScannerPluginManager $scannerManager,
protected LanguageManagerInterface $languageManager,
protected Renderer $render,
protected AccountProxyInterface $currentUser,
) {
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container): static {
return new static(
$container->get('tempstore.private'),
$container->get('plugin.manager.scanner'),
$container->get('language_manager'),
$container->get('renderer'),
$container->get('current_user'),
);
}
/**
* {@inheritdoc}
*/
public function getFormId(): string {
return 'scanner_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
$config = $this->configFactory()->get('scanner.admin_settings');
$mode = $form_state->getValue('scanner_mode') ? $form_state->getValue('scanner_mode') : $config->get('scanner_mode');
$wholeword = $form_state->getValue('scanner_wholeword') ? $form_state->getValue('scanner_wholeword') : $config->get('scanner_wholeword');
$regex = $form_state->getValue('scanner_regex') ? $form_state->getValue('scanner_regex') : $config->get('scanner_regex');
$published = $form_state->getValue('scanner_published') ? $form_state->getValue('scanner_published') : $config->get('scanner_published');
$language = $form_state->getValue('scanner_language') ? $form_state->getValue('scanner_language') : $config->get('scanner_language');
$form['settings_link'] = [
'#prefix' => '<p>',
'#markup' => $this->t('To configure settings go to <a href="@href">Search and Replace Scanner configuration</a>>', [
'@href' => Url::fromRoute('scanner.admin_config')->toString(),
]),
'#suffix' => '</p>',
];
$form['search'] = [
'#type' => 'textfield',
'#default_value' => '',
'#title' => $this->t('Step 1: Search for'),
'#description' => $this->t('Enter the search term to search for.'),
'#maxlength' => 256,
];
$form['submit_search'] = [
'#type' => 'submit',
'#value' => $this->t('Search'),
'#suffix' => '<hr>',
];
$form['replace'] = [
'#type' => 'textfield',
'#default_value' => '',
'#title' => $this->t('Step 2: Replace with'),
'#description' => $this->t('Enter the term to replace the searched term.'),
'#maxlength' => 256,
'#access' => $this->currentUser->hasPermission('perform search and replace'),
];
$form['submit_replace'] = [
'#type' => 'submit',
'#value' => $this->t('Replace'),
'#access' => $this->currentUser->hasPermission('perform search and replace'),
];
$form['options'] = [
'#type' => 'details',
'#title' => $this->t('Search Options'),
];
$form['options']['surrounding'] = [
'#type' => 'details',
'#title' => $this->t('Surrounding Text'),
'#description' => $this->t('You can limit matches by providing the text that should appear immediately before or after the search text. Remember to account for spaces. Note: Case sensitivity and regular expression options will all apply here, too. Whole word is not recommended.'),
];
$form['options']['surrounding']['preceded'] = [
'#type' => 'textfield',
'#title' => $this->t('Preceded by'),
'#default_value' => '',
'#maxlength' => 256,
];
$form['options']['surrounding']['followed'] = [
'#type' => 'textfield',
'#title' => $this->t('Followed by'),
'#default_value' => '',
'#maxlength' => 256,
];
$form['options']['message'] = [
'#type' => 'markup',
'#markup' => $this->t('The below settings override the values configured in the <a href="@url" target="_blank">admin settings page</a>.', ['@url' => '/admin/config/content/scanner']),
];
$form['options']['mode'] = [
'#type' => 'checkbox',
'#title' => $this->t('Case sensitive search'),
'#default_value' => $mode,
'#description' => $this->t('Check this if the search should only return results that exactly match the capitalization of your search terms.'),
];
$form['options']['wholeword'] = [
'#type' => 'checkbox',
'#title' => $this->t('Match whole word'),
'#default_value' => $wholeword,
'#description' => $this->t("Check this if you don't want the search to match any partial words. For instance, if you search for 'run', a whole word search will <em>not</em> match 'running'."),
];
$form['options']['regex'] = [
'#type' => 'checkbox',
'#title' => $this->t('Use regular expressions in search'),
'#default_value' => $regex,
'#description' => $this->t('Check this if you want to use regular expressions in your search terms.'),
];
$form['options']['published'] = [
'#type' => 'checkbox',
'#title' => $this->t('Published nodes only'),
'#default_value' => $published,
'#description' => $this->t('Check this if you only want your search and replace to affect fields in nodes that are published.'),
];
$form['options']['language'] = [
'#type' => 'select',
'#title' => $this->t('Content language'),
'#default_value' => $language,
'#options' => $this->getLanguages(),
'#description' => $this->t('The language of the content you would like to search through.'),
];
$scannerStore = $this->tempStore->get('scanner');
// Empty the results on initial load, otherwise results from previous query
// will be displayed.
if (empty($form_state->getValues())) {
$scannerStore->set('results', '');
}
else {
$renderable = $scannerStore->get('results');
$markup = $this->render->render($renderable);
$form['results'] = [
'#type' => 'markup',
'#markup' => $markup,
];
}
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
// Validation logic here.
}
/**
* {@inheritdoc}
*
* @throws \Drupal\Core\TempStore\TempStoreException
*/
public function submitForm(array &$form, FormStateInterface $form_state): void {
$form_state->cleanValues();
$scannerStore = $this->tempStore->get('scanner');
$op = $form_state->getUserInput()['op'];
// Save the $form_state values into the user tempstore for later.
foreach ($form_state->getValues() as $key => $value) {
$scannerStore->set($key, $form_state->getValue($key));
}
$scannerStore->set('op', $op);
if ($op == $this->t('Search')) {
$config = $this->configFactory->get('scanner.admin_settings');
$fields = $config->get('fields_of_selected_content_type');
// Build an array of batch operation jobs.
// Batch job will need the field and the $form_state values.
$operations = [];
foreach ($fields as $field) {
$operations[] = [
'\Drupal\scanner\Form\ScannerForm::batchSearch',
[
$field,
$form_state->getValues(),
],
];
}
$batch = [
'title' => $this->t('Scanner Search Batch'),
'operations' => $operations,
'finished' => '\Drupal\scanner\Form\ScannerForm::batchFinished',
'progress_message' => $this->t('Processed @current out of @total'),
];
batch_set($batch);
$form_state->setRebuild();
}
elseif ($op == $this->t('Replace')) {
// Redirect to the confirmation form.
$form_state->setRedirect('scanner.admin_confirm');
}
}
/**
* Batch operation function.
*
* @param string $field
* The name of the field.
* @param array $values
* The $form_state values.
* @param array $context
* An array containing data that is persisted across batch jobs.
*
* @see https://api.drupal.org/api/drupal/core%21includes%21form.inc/group/batch/8.5.x
*/
public static function batchSearch(string $field, array $values, array &$context): void {
$pluginManager = \Drupal::service('plugin.manager.scanner');
[$entityType, $bundle, $fieldname] = explode(':', $field);
// Attempt to load the plugin.
try {
$plugin = $pluginManager->createInstance('scanner_entity');
$results = $plugin->search($field, $values);
if (!isset($context['results']['count'])) {
$context['results']['count'] = [
'entities' => 0,
'matches' => 0,
];
}
if (!empty($results)) {
$context['results'][$entityType][$bundle][$fieldname] = $results;
// Number of entities with search term.
$context['results']['count']['entities'] += count($results);
foreach ($results as $data) {
// Number of matches within each field of each entity.
$context['results']['count']['matches'] += count($data['field']);
}
$context['message'] = 'Searching through field...';
}
}
catch (PluginException $e) {
// The instance could not be found so fail gracefully and let the user
// know.
\Drupal::logger('scanner')->error($e->getMessage());
\Drupal::messenger()->addError(t('An error occurred @e:', ['@e' => $e->getMessage()]));
}
}
/**
* The batch process has finished.
*
* @param bool $success
* Indicates whether the batch process finish successfully.
* @param array $results
* Contains the output from the batch operations.
* @param array $operations
* A list of operations that were processed.
*/
public static function batchFinished(bool $success, array $results, array $operations): void {
if ($success && isset($results['count'])) {
$count = $results['count'];
if (isset($results['count']['matches'])) {
// Handle regex results.
$count_for_theme = $results['count']['matches'];
}
elseif (isset($results['count']['entities'])) {
// Handle other results.
$count_for_theme = $results['count']['entities'];
}
else {
// Handle other results.
$count_for_theme = $results['count'];
}
// $count expected to be a numerical value.
unset($results['count']);
$renderable = [
'#theme' => 'scanner_results',
'#data' => ['values' => $results, 'count' => $count_for_theme],
];
$scannerStore = \Drupal::service('tempstore.private')->get('scanner');
// Persist the results to the tempstore.
$scannerStore->set('results', $renderable);
}
else {
\Drupal::messenger()->addMessage(t('There were some errors.'));
}
if (!isset($count['matches'])) {
$count['matches'] = 0;
$count['entities'] = 0;
}
\Drupal::messenger()->addMessage(t('Found @matches matches in @entities entities.', [
'@matches' => $count['matches'],
'@entities' => $count['entities'],
]));
}
/**
* Helper function to fetch languaged enabled on the site.
*
* @return array
* The languages keyed by langcode, with an option "all" for all languages.
*/
public function getLanguages(): array {
$languages = $this->languageManager->getLanguages();
foreach ($languages as $language) {
$langs[$language->getId()] = $language->getName();
}
$langs['all'] = $this->t('All languages');
return $langs;
}
}
