I am building a custom module for Drupal 8.4.4 and is not detecting the hook_theme from a block. I get an error message saying "Theme hook gardentheme not found".
If I uninstall the module and install it again, it works fine, but as soon as I clear the cache, it doesn't find the theme_hook anymore.
I notice that die() and exit; wont do anything on my .module file after clearing the cache, I feel as after clearing the cache the .module is not run anymore.
My module file called garden.module
<?php
/**
* #file
*
*/
/**
* Implements hook_theme()
*/
function garden_theme($existing, $type, $theme, $path){
return array('gardentheme' =>
array(
'variables' => array(
'description' => NULL
),
)
);
}
My block placed on src/Plugin/Block/GardenScheduleBlock.php
<?php
namespace Drupal\garden\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Provides a 'GardenSchedule' Block.
*
* #Block(
* id = "garden_schedule_block",
* admin_label = #Translation("Garden Schedule"),
* category = #Translation("Garden Schedule_Category"),
* )
*/
class GardenScheduleBlock extends BlockBase {
/**
* {#inheritdoc}
*/
public function build() {
return array(
'#theme' => 'gardentheme',
'#description' => "description test"
);
}
}
Thanks in advance for any tips.
public function build() {
$renderable = [
'#theme' => 'my_template',
'#test_var' => 'test variable',
];
return $renderable;
}
try this
Related
I have this form: https://greektoenglish.com/translation
After I complete the form, provide it with a file, and finally submit it, I get this error: "field is required". That the file field is required. But I already completed the field.
If I remove "'#required' => TRUE," from the code where the file upload field is declared, fill the form out, and submit it, then the form is submitted correctly.
How can I solve this?
This is my code:
<?php
namespace Drupal\submit_translation\Form;
use Drupal\Component\Utility\EmailValidatorInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Mail\MailManagerInterface;
use Drupal\mimemail\Utility\MimeMailFormatHelper;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* The example email contact form.
*/
class SubmitTranslation extends FormBase {
/**
* The email.validator service.
*
* #var \Drupal\Component\Utility\EmailValidatorInterface
*/
protected $emailValidator;
/**
* The language manager service.
*
* #var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The mail manager service.
*
* #var \Drupal\Core\Mail\MailManagerInterface
*/
protected $mailManager;
/**
* Constructs a new ExampleForm.
*
* #param \Drupal\Component\Utility\EmailValidatorInterface $email_validator
* The email validator service.
* #param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager service.
* #param \Drupal\Core\Mail\MailManagerInterface $mail_manager
* The mail manager service.
*/
public function __construct(EmailValidatorInterface $email_validator, LanguageManagerInterface $language_manager, MailManagerInterface $mail_manager) {
$this->emailValidator = $email_validator;
$this->languageManager = $language_manager;
$this->mailManager = $mail_manager;
}
/**
* {#inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('email.validator'),
$container->get('language_manager'),
$container->get('plugin.manager.mail')
);
}
/**
* {#inheritdoc}
*/
public function getFormId() {
return 'submit_translation_form';
}
/**
* {#inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $dir = NULL, $img = NULL) {
$form['intro'] = [
'#markup' => $this->t('Use this form to send us the document that we\'ll translate!'),
];
$form['from'] = [
'#type' => 'textfield',
'#title' => $this->t('Name'),
'#description' => $this->t("Your full name."),
'#required' => TRUE,
];
$form['from_mail'] = [
'#type' => 'textfield',
'#title' => $this->t('Email address'),
'#description' => $this->t("Your email address."),
'#required' => TRUE,
];
$form['params'] = [
'#tree' => TRUE,
'subject' => [
'#type' => 'textfield',
'#title' => $this->t('Title'),
'#description' => $this->t("The title of the document."),
'#required' => TRUE,
],
'count' => [
'#type' => 'textfield',
'#title' => $this->t('Word Count'),
'#description' => $this->t("The word count of the document."),
'#required' => TRUE,
],
'body' => [
'#type' => 'textarea',
'#title' => $this->t('Comments'),
'#description' => $this->t("Tell us if you have any special requirements."),
'#required' => TRUE,
],
// This form element forces plaintext-only email when there is no HTML
// content (that is, when the 'body' form element is empty).
'plain' => [
'#type' => 'hidden',
'#states' => [
'value' => [
':input[name="body"]' => ['value' => ''],
],
],
],
'attachments' => [
'#name' => 'files[attachment]',
'#type' => 'file',
'#title' => $this->t('Choose a file to send for translation.'),
'#required' => TRUE,
],
];
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Send message'),
];
return $form;
}
/**
* {#inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
// Extract the address part of the entered email before trying to validate.
// The email.validator service does not work on RFC2822 formatted addresses
// so we need to extract the RFC822 part out first. This is not as good as
// actually validating the full RFC2822 address, but it is better than
// either just validating RFC822 or not validating at all.
$pattern = '/<(.*?)>/';
$address = $form_state->getValue('from_mail');
preg_match_all($pattern, $address, $matches);
$address = isset($matches[1][0]) ? $matches[1][0] : $address;
if (!$this->emailValidator->isValid($address)) {
$form_state->setErrorByName('from_mail', $this->t('That email address is not valid.'));
}
$file = file_save_upload('attachment', [ 'file_validate_extensions' => array('doc docx pdf')], 'temporary://', 0);
if ($file) {
$form_state->setValue(['params', 'attachments'], [['filepath' => $file->getFileUri()]]);
}
}
/**
* {#inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// First, assemble arguments for MailManager::mail().
$module = 'submit_translation';
$key = "solon_key";
$to = "info#gexl.eu";
$langcode = $this->languageManager->getDefaultLanguage()->getId();
$params = $form_state->getValue('params');
$reply = "";
$send = TRUE;
$params['body'] .= " Count: " . $params['count'];
// Second, add values to $params and/or modify submitted values.
// Set From header.
if (!empty($form_state->getValue('from_mail'))) {
$params['headers']['From'] = MimeMailFormatHelper::mimeMailAddress([
'name' => $form_state->getValue('from'),
'mail' => $form_state->getValue('from_mail')
]);
}
elseif (!empty($form_state->getValue('from'))) {
$params['headers']['From'] = $from = $form_state->getValue('from');
}
else {
// Empty 'from' will result in the default site email being used.
}
// Handle empty attachments - we require this to be an array.
if (empty($params['attachments'])) {
$params['attachments'] = [];
}
// Remove empty values from $param['headers'] - this will force the
// the formatting mailsystem and the sending mailsystem to use the
// default values for these elements.
foreach ($params['headers'] as $header => $value) {
if (empty($value)) {
unset($params['headers'][$header]);
}
}
// Finally, call MailManager::mail() to send the mail.
$result = $this->mailManager->mail($module, $key, $to, $langcode, $params, $reply, $send);
if ($result['result'] == TRUE) {
$this->messenger()->addMessage($this->t('Your message has been sent.'));
}
else {
// This condition is also logged to the 'mail' logger channel by the
// default PhpMail mailsystem.
$this->messenger()->addError($this->t('There was a problem sending your message and it was not sent.'));
}
}
}
This happens because the form element '#type' => 'file' has no #value to validate. #required fields must have a #value set otherwise validation fails.
This is (now considered) a very old issue that has been fixed in Drupal 9.5.x, but this was assumed in the good old days of Drupal 7, as mentioned in the Form API reference :
#required: Indicates whether or not the element is required. This
automatically validates for empty fields, and flags inputs as
required. File fields are NOT allowed to be required.
So I guess the best solution is to upgrade to 9.5.x or above, if feasible, but as sometimes upgrading makes things complicated, you might prefer to review and apply the patch manually to your current code base.
[EDIT]: If still having issues after upgrade to >= 9.5.2,
Looking at the patch, a default valueCallback is now used to provide a #value to file form elements, but.. well there is another issue :
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
if ($input === FALSE) {
return NULL;
}
$parents = $element['#parents'];
$element_name = array_shift($parents); # <- problem here :/
$uploaded_files = \Drupal::request()->files->get('files', []);
$uploaded_file = $uploaded_files[$element_name] ?? NULL;
if ($uploaded_file) {
// Cast this to an array so that the structure is consistent regardless of
// whether #value is set or not.
return (array) $uploaded_file;
}
return NULL;
}
See how it doesn't care about whether or not the element has a #name explicitly defined ? and whether or not #parents is a tree ? Now because of those wrong assumptions on the element's name and its parents, you are somehow forced to either :
Leave the #name property unset and refer to the file later on validation/submit as 'params' (the parents root) instead of 'attachment'. Or,
Stick with #tree => FALSE. Or,
Provide your own #value_callback (deprecated ...?)
I am using Drupal 9. I want to give the ability to the admin to place a block and select from the block a taxonomy term in which will filter a content type.
I did the above by creating a custom block with the taxonomy "Sponsor Type" as you can see the screenshot bellow.
Also I use views_embed_view to pass the taxonomy as an argument and filter the data using Contextual filters
Custom block code:
<?php
namespace Drupal\aek\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a 'SponsorsBlock' block.
*
* #Block(
* id = "sponsors_block",
* admin_label = #Translation("Sponsors"),
* )
*/
class SponsorsBlock extends BlockBase {
/**
* {#inheritdoc}
*/
public function defaultConfiguration() {
return [
"max_items" => 5,
] + parent::defaultConfiguration();
}
public function blockForm($form, FormStateInterface $form_state) {
$sponsors = \Drupal::entityTypeManager()
->getStorage('taxonomy_term')
->loadTree("horigoi");
$sponsorsOptions = [];
foreach ($sponsors as $sponsor) {
$sponsorsOptions[$sponsor->tid] = $sponsor->name;
}
$form['sponsor_types'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Sponsor Type'),
'#description' => $this->t('Select from which sponsor type you want to get'),
'#options' => $sponsorsOptions,
'#default_value' => $this->configuration['sponsor_types'],
'#weight' => '0',
];
$form['max_items'] = [
'#type' => 'number',
'#title' => $this->t('Max items to display'),
'#description' => $this->t('Max Items'),
'#default_value' => $this->configuration['max_items'],
'#weight' => '0',
];
return $form;
}
/**
* {#inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
$this->configuration['sponsor_types'] = $form_state->getValue('sponsor_types');
$this->configuration['max_items'] = $form_state->getValue('max_items');
}
/**
* {#inheritdoc}
*/
public function build() {
$selectedSponsorTypes = $this->configuration['sponsor_types'];
$cnxFilter = '';
foreach ($selectedSponsorTypes as $type) {
if ($type !== 0) {
$cnxFilter .= $type . ",";
}
}
return views_embed_view('embed_sponsors', 'default', $cnxFilter);
}
}
My issue now is how to limit the results. If you look above I have added an option "Max items to display" but using the Contextual filters I can't find any way to pass that argument to handle this.
If you use views_embed_view(), you will not be able to access the view object.
Load your view manually then you can set any properties before executing it:
use Drupal\views\Views;
public function build() {
$selectedSponsorTypes = $this->configuration['sponsor_types'];
$cnxFilter = '';
foreach ($selectedSponsorTypes as $type) {
if ($type !== 0) {
$cnxFilter .= $type . ",";
}
}
$view = Views::getView('embed_sponsors');
$display_id = 'default';
$view->setDisplay($display_id);
$view->setArguments([$cnxFilter]);
$view->setItemsPerPage($this->configuration['max_items']);
$view->execute();
return $view->buildRenderable($display_id);
}
I have built a custom class that adds a custom font library to visual composer, and it works except that the icon selector always showing even if i change the library, as you can see in this image
, on the top there is the icon selector of flat icon library while i have selected the fontawesome library.
Note: Fontawesome's icon selector also showing in the same time
Here is the class:
<?php
if (!class_exists('Fama_VC_Fonts')) {
class Fama_VC_Fonts{
/**
* #var string
*/
public $font_library;
/**
* #var array
*/
public $fonts_list;
/**
* #var array
*/
public $fonts_lib_meta;
/**
* Class constructor
* #param array $library_data Font library data
* #return void
*/
public function __construct($library_data){
//check if visual composer is active
if(!fama_is_active_plugin('js_composer/js_composer.php')) return;
if (!is_array($library_data)) return;
if(empty($library_data['font_library']) || empty($library_data['fonts_list'])) return;
$this->font_library = $library_data['font_library'];
$this->fonts_list = $library_data['fonts_list'];
$this->fonts_lib_meta = $library_data['fonts_lib_meta'];
// In the 'Icon library' dropdown for an icon content type, add a new family of icons.
add_filter( 'vc_after_init', array($this, 'add_vc_icon_library'), 40 );
/**
* This adds a new parameter to the vc_icon content block.
*
* This parameter is an icon_picker element, that displays when flaticon is picked from the dropdown.
*/
add_filter( 'vc_after_init', array($this, 'define_vc_icon_picker'), 50 );
// Add all the icons we want to display in our font family.
add_filter( 'vc_iconpicker-type-'.$this->font_library , array($this, 'add_vc_icon_list') );
// Enqueue the CSS file so that the icons display in the backend editor.
add_action( 'vc_backend_editor_enqueue_js_css', array($this, 'enqueue_vc_icon_styles') );
// Enqueue the CSS file so that the icons display in the frontend editor.
add_action( 'vc_frontend_editor_enqueue_js_css', array($this, 'enqueue_vc_icon_styles') );
add_action( 'vc_enqueue_font_icon_element', array($this,'enqueue_vc_icon_styles_on_request'), 10 );
}
/**
* In the 'Icon library' dropdown for an icon content type, add a new family of icons.
*
* #return void
*/
public function add_vc_icon_library(){
$param = WPBMap::getParam( 'vc_icon', 'type' );
$param['value'][ __( 'Flat icon', 'js_composer' ) ] = $this->font_library;
vc_update_shortcode_param( 'vc_icon', $param );
}
/**
* This adds a new parameter to the vc_icon content block.
*
* This parameter is an icon_picker element, that displays when flaticon is picked from the dropdown.
*/
public function define_vc_icon_picker(){
$settings = array(
'type' => 'iconpicker',
'heading' => $this->fonts_lib_meta['heading'],
'param_name' => 'icon_'.$this->font_library,
'settings' => array(
'emptyIcon' => false,
'type' => $this->font_library,
'iconsPerPage' => 20,
),
'dependency' => array(
'element' => 'icon_type',
'value' => $this->font_library,
),
'weight' => '2',
'description' => $this->fonts_lib_meta['description'],
);
vc_add_param( 'vc_icon', $settings );
}
/**
* Add all the icons we want to display in our font family.
* #param array $icons
* #return array Icons array
*/
public function add_vc_icon_list($icons){
$icons = $this->fonts_list;
return $icons;
}
/**
* Enqueue font library style
* #return void
*/
public function enqueue_vc_icon_styles(){
wp_enqueue_style( $this->font_library , $this->fonts_lib_meta['fonts_css_uri'] );
}
/**
* Optional - Conditionally load CSS for your icon font as requested by modules on the live site, Only required if you aren't already loading the custom font globally
* #param string $font
* #return void
*/
public function enqueue_vc_icon_styles_on_request( $font ) {
if ( ! empty( $font ) && $this->font_library == $font ) {
wp_enqueue_style( $this->font_library , $this->fonts_lib_meta['fonts_css_uri'] );
}
}
}
}
$vc_font_lib = [
'font_library' => 'flaticon',
'fonts_list' =>
[
['flaticon-world' => 'World'],
['flaticon-gliding' => 'Gliding'],
['flaticon-tour-guide' => 'Tour guide'],
['flaticon-map-of-roads' => 'Map of roads'],
['flaticon-alarm-clock' =>'Alarm Clock'],
['flaticon-manager' =>'Manager'],
['flaticon-layers' =>'Layers'],
['flaticon-wallet' =>'Wallet'],
],
'fonts_lib_meta' => [
'heading' => __( 'Icon', 'js_composer' ),
'description' => __( 'Select flat icon ', 'js_composer' ),
'fonts_css_uri' => FMA_STYLESHEET_DIR . '/assets/css/flaticon.css',
],
];
$vc_fonts = new Fama_VC_Fonts ( $vc_font_lib);
I have figured out where the problem is. It is this line:
'element' => 'icon_type',
Which should be:
'element' => 'type',
Now only selected library is showing.
I'm trying to build a custom block with Sonata, for the moment i'm just trying a simple block that just display text, but I cannot render the template as it seems that the service can't be found.
I do have the block declared in config.yml
sonata_block:
default_contexts: [cms]
blocks:
app.service.block.portfolio:
dashboard:
blocks:
- { position: right, type: app.service.block.portfolio, settings: { content: "<h2>This is a test block</h2>"} }
I also can see my service is up:
app/console debug:container | grep app.service.block.portfolio
app.service.block.portfolio App\CoreBundle\Block\BlockPortfolio
This is the code I used:
<?php
namespace App\CoreBundle\Block;
use Sonata\BlockBundle\Block\Service\AbstractAdminBlockService;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\CoreBundle\Model\Metadata;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
use JMS\DiExtraBundle\Annotation as DI;
/**
* #DI\Service("app.service.block.portfolio")
*/
class BlockPortfolio extends AbstractAdminBlockService
{
/**
* {#inheritdoc}
*/
public function execute(BlockContextInterface $blockContext, Response $response = null)
{
return $this->renderResponse($blockContext->getTemplate(), array(
'block' => $blockContext->getBlock(),
'settings' => $blockContext->getSettings(),
), $response);
}
/**
* {#inheritdoc}
*/
public function buildEditForm(FormMapper $formMapper, BlockInterface $block)
{
$formMapper->add('settings', 'sonata_type_immutable_array', array(
'keys' => array(
array('content', 'textarea', array()),
),
));
}
/**
* {#inheritdoc}
*/
public function configureSettings(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'content' => 'Insert your custom content here',
'template' => 'SonataBlockBundle:Block:block_core_text.html.twig',
));
}
/**
* {#inheritdoc}
*/
public function getBlockMetadata($code = null)
{
return new Metadata($this->getName(), (!is_null($code) ? $code : $this->getName()), false, 'SonataBlockBundle', array(
'class' => 'fa fa-file-text-o',
));
}
}
anyone has a clue ?
Regards,
Julien
Ok, I found the answer, I was missing the tag in the service.
/**
* #DI\Service("app.service.block.portfolio")
* #DI\Tag(name="sonata.block")
*/
I'm not sure why you need this tag, though. If someone knows the reason, I'd be happy to here it.
Cheers,
Julien
Is there a Drupal module to display the user name in a block, when he is logged in ?
thanks
Creat a new block.
Format: PHP code
Block Body:
<?
global $user;
print $user->name;
?>
In Drupal 7, using a custom module named YOURMODULE:
/**
* Implements hook_block_info().
*/
function YOURMODULE_block_info() {
return array(
'YOURMODULE_logged_in_as' => array(
'info' => t('Login information ("Logged in as...").'),
'cache' => DRUPAL_CACHE_PER_USER,
),
);
}
/**
* Implements hook_block_view().
*/
function YOURMODULE_block_view($delta = '') {
if ($delta == 'YOURMODULE_logged_in_as') {
global $user;
return array(
'subject' => NULL,
'content' => t('Logged in as !name', array('!name' => theme('username', array('account' => $user)))),
);
}
}