I have an entity called Entity Product. And this entity has a form, if you change the Title field in this form, Drupal will automatically save the new value of this field in the appropriate table in the database, but in addition, I also save the change of the Title field in the form to another database table product_product by creating a _custom_product_save_title function. It is necessary. And this function is called in the hook_ENTITY_TYPE_update() which tracks changes in Entity Product.
I need to add a check to see if the title is saved and if other fields are not saved. Please tell me what such a check should look like and where exactly should it be in the code?
function _custom_product_save_title($custom_product_id, $entity_product_title) {
if (isset($fields['url']) && $fields['url'] == '') {
if (isset($fields['name'])) {
$fields['url'] = strtolower(str_replace(' ', '-', $fields['name']));
}
}
$id = $form_state->getValue('cid');
if (!empty($form_state->getValue('cid'))) {
$query = $this->connection->update($this->getTableName())
->condition('cid', $form_state->getValue('cid'));
}
else {
$query = $this->connection->insert($this->getTableName());
}
$result = $query
->fields($fields)
->execute();
if (!$id) {
$id = $result;
}
Cache::invalidateTags([
"product:" . $form_state->getValue('cid'),
]);
Cache::invalidateTags([
"product:$custom_product_id",
]);
if (!$custom_product_id) {
Cache::invalidateTags([
"product_list",
]);
}
}
/**
* Implements hook_ENTITY_TYPE_update().
*/
function product_admin_node_update(\Drupal\Core\Entity\EntityInterface $entity) {
if ($entity->bundle() == 'product') {
$custom_product_id = $entity->get('field_product_cid')->value;
$entity_product_title = $entity->getTitle();
_custom_product_save_title($custom_product_id, $entity_product_title);
}
}
After debugging the code using xdebug, I see the following structure of what comes in the standard argument $entity of the hook:
$entity
fields
field_product_cid
field_second
.....
title
x-default
list
0
values
value = “My title”
I am working a on a form with, at it's core, two fieldset: main and "other recipient"; at the end of of the "other recipient" fieldset, I have a "add another recipient" link.
Here's who needs what:
Main recipient: everything
Other recipient: the "other recipient" fieldset;
Sub-sequent recipients: Respective fieldsets
So far, I've been looking at the Documentation but not much luck there, not that I expected any, either.
Edit
I think this is unclear, so I will be a little more explicit as to what is the context. My form is a registration where we can sign up multiple people; one of the fields is labeled "Your email". Since we can register more than one person at once, I need to duplicate the fieldset containing "Your email".
Edit 2
To help clarify, imagine that we are signing up kids for a summer camp. The first fieldset is general, say the parent's billing information, and the second fieldset is the child's information. The parent needs to be able to fill out a single form and dynamically add as many children as the parent desires.
In each of the children's fieldset, their email is required and they receive the information relevant to this child, where the email would be similar to:
Hello {children's name},
You've been registered to StackOverflow Summer Camp. Here's the information you need to know:
[...]
Thanks for being a good sport!
Hope this helps.
When you've got a specific use case like this, shoehorning the functionality into peripherally related plugins often results in frustration. That being said - there are times where you're married to a specific plugin or approach, and just have to build on top of it.
With that caveat out of the way, I think you should approach this from the angle of creating a new fieldtype for Contact Form 7. This way you have control over rendering the field's HTML, the data validation, among other things. It might also provide an launch point for DB storage and sending reminders, later, as you've mentioned in a comment on another answer.
Here's this approach in action:
The new fieldtype is called emailplus, and you include it into a form like this:
<div class="cf7-duplicable-recipients">
<label>Main Recipient (required)</label>
[emailplus emails]
[submit "Sign Up"]
</div>
Additionally, I've set the recipient under the "mail" panel in the form's settings to [emails].
If an emailsplus field is set as the recipient, the class overrides the default wpcf7 behaviour and sends mail to each value in the email array, substituting any [emails] placeholders in the body of the message on a per-email basis.
The emailplus fieldtype is encapsulated here in a class, and commented liberally:
<?php
class WPCF7_Duplicable_Email
{
/**
* The emails this form's field is addressed to
*
* #var array
*/
public $emails = array();
/**
* The name of the tag bearing the emailplus type
*
* #var string
*/
public $tag_name;
/**
* Instantiate the class & glom onto the wpcf7 hooks
*
* #return void
*/
public function __construct()
{
add_action('wpcf7_init', array($this, 'add_emailplus_tag'));
add_action('wpcf7_form_tag', array($this, 'assign_values_to_field'));
add_filter('wpcf7_validate_emailplus', array($this, 'validate'), 2);
add_action('wpcf7_before_send_mail', array($this, 'send_email'));
add_action('wp_enqueue_scripts', array($this, 'emailplus_scripts'));
}
/**
* Add our the [emailplus __YOUR_FIELD_NAME__] shortcode for use in wpcf7 forms.
*
* #uses wpcf7_add_shortcode
*
* #return void
*/
public function add_emailplus_tag()
{
wpcf7_add_shortcode(
array('emailplus'),
array($this, 'shortcode_handler'),
true
);
}
/**
* Renders the HTML for the [emailplus] shortcode
*
* Referenced from wpcf7_text_shortcode_handler in wp-content/plugins/contact-form-7/modules/text.php
*
* #param array $tag The data relating to the emailplus form field.
*
* #uses wpcf7_get_validation_error
* #uses wpcf7_form_controls_class
*
* #return string
*/
public function shortcode_handler(array $tag) {
$tag = new WPCF7_Shortcode($tag);
if (true === empty($tag->name)) {
return '';
}
$validation_error = wpcf7_get_validation_error($tag->name);
$class = wpcf7_form_controls_class($tag->type);
if ($validation_error) {
$class .= ' wpcf7-not-valid';
}
$atts = array(
'class' => $tag->get_class_option($class),
'id' => $tag->get_id_option(),
'tabindex' => $tag->get_option('tabindex', 'int', true),
'aria-invalid' => $validation_error ? 'true' : 'false',
'type' => 'email',
'name' => $tag->name.'[]', // <-- Important! the trailing [] Tells PHP this is an array of values
'value' => $tag->get_default_option()
);
$atts = wpcf7_format_atts($atts);
$html = sprintf(
'<div class="emailplus-wrapper %1$s"><input %2$s />%3$s</div>',
sanitize_html_class($tag->name),
$atts,
$validation_error
);
// We identify the container that will hold cloned fields with the [data-wpcf7-duplicable-email] attr
return '<div class="wpcf7-form-control-wrap %1$s" data-wpcf7-duplicable-email>'.$html.'</div>';
}
/**
* Validates the value of the emailplus tag.
*
* Must be handled separately from other text-based form inputs,
* since the submitted POST value is an array.
*
* We can safely assume emailplus while creating the WPCF7_Shortcode,
* because we know we hooked this function onto 'wpcf7_validate_emailplus'
*
* #uses wpcf7_is_email
* #uses WPCF7_Validation::invalidate
*
* #param WPCF7_Validation $result The validation helper class from wpcf7.
* #param array $tag The array of values making up our emailplus tag
*
*/
public function validate(WPCF7_Validation $result, $tag)
{
$tag = new WPCF7_Shortcode(
array(
'basename' => 'emailplus',
'name' => $this->tag_name,
'raw_values' => $this->emails
)
);
// Check each value of the form field.
// Emails must be validated individually.
foreach($tag->raw_values as $email) {
if (false === wpcf7_is_email($email)) {
$result->invalidate($tag, wpcf7_get_message('invalid_email'));
}
}
return $result;
}
/**
* For completeness' sake, manually assign the value to the emailplus fieldtype.
*
* Wpcf7 doesn't know how to treat our fieldtype's value by default.
*
* As a side effect, this sets the email addresses that are up for delivery.
*
* #param array $scanned_tag The tag that wpcf7 is scanning through, and processing.
*
* #return $array;
*/
public function assign_values_to_field($scanned_tag)
{
if ($scanned_tag['type'] !== 'emailplus') {
return $scanned_tag;
}
$this->tag_name = $scanned_tag['name'];
if (key_exists($scanned_tag['name'], $_POST)) {
// Stores the emails on a class property for use later.
$this->emails = $_POST[$scanned_tag['name']];
$scanned_tag['raw_values'] = $this->emails;
$scanned_tag['values'] = $this->emails;
}
return $scanned_tag;
}
/**
* Performs a substitution on the emailplus field's fieldname, on a per-value basis.
*
* Ex. in two parts
* 1 - The shortcode [emailsplus emails] renders into <input type="email" name="emails[]" value="" >
* Which the user clones and submits, processing into something like
* ['test1#gmail.com', 'test2#gmail.com'].
* 2 - The user has set [emails] as the recipient in the "mail" panel for the form.
*
* Because wpcf7 isn't aware of how to process a field w/ multiple values when emailing,
* we loop over the values of [emails], replace the tag, and invoke WPCF7_Mail::send()
* for each value.
*
* #param WPCF7_ContactForm $form The contact form object.
*
* #uses WPCF7_Mail::send
*
* #return void
*/
public function send_email(WPCF7_ContactForm $form)
{
$placeholder = '['.$this->tag_name.']';
if (false === strpos($form->prop('mail'), $placeholder)) {
return;
}
foreach ($this->emails as $email) {
$template = $form->prop('mail');
$template['recipient'] = str_replace($placeholder, $email, $template['recipient']);
$template['body'] = str_replace($placeholder, $email, $template['body']);
WPCF7_Mail::send($template);
}
// Tell cf7 to skip over the default sending behaviour in WPCF7_Submission->mail()
$form->skip_mail = true;
}
/**
* Adds our js that will clone the emailplus field.
*
* Could be optimized with a conditional that checks if there is a form with the [emailplus]
* fieldtype somewhere on the page
*
* #return void
*/
public function emailplus_scripts()
{
wp_enqueue_script(
'cf7-duplication',
get_template_directory_uri() . '/js/cf7-duplication.js',
array('jquery'),
'20161006',
true
);
}
}
$wpcf7DuplicableEmail = new WPCF7_Duplicable_Email();
And, the .js file that handles the cloning. It should live in /path/to/your/theme/js/cf7-duplication.js'.
(function($) {
$(document).ready(function() {
var addEmailField = function(inputRow, container) {
inputRow.find('input[type=email]').val('');
var removeButton = $('×')
.click(function(e) {
e.preventDefault();
inputRow.remove();
});
inputRow.append(removeButton);
inputRow.insertAfter(container.find('.emailplus-wrapper').last());
}
$.each($('[data-wpcf7-duplicable-email]'), function(i, el) {
var container = $(el);
var inputRow = container.find('.emailplus-wrapper');
var addButton = $('Add another email');
addButton.click(function(e) {
e.preventDefault();
var newEmailField = inputRow.clone();
addEmailField(newEmailField, container);
});
container.append(addButton);
});
});
})(jQuery);
Last, but not least, if you'd like the form to fadeout when it's valid and the emails have gone out, add this to the "additional settings" panel.
on_sent_ok: "jQuery('.cf7-duplicable-recipients').fadeOut();"
This approach is best for CF7's AJAX submissions. If you want to extend it to handle vanilla POST requests, you could update the shortcode handler to render multiple <input> fields where you need to preserve the value attr on invalid submissions.
Options
1) Under the Mail tap in the setup menu, after you click Mail(2), in the TO: field add this line. Should the parent have less than the number of kids stated, the extra email addresses should do nothing. [email],[email] is the basic format.
[parents-email], [kid-email1], [kid-email2], [kid-email3], [kid-email4], [kid-email5], [kid-email6], [kid-email7]
Option 2) In the TO: field, just place 1 email address such as the parents. and in the Additional Headers: place the code below
CC: [kid-email1], [kid-email2], [kid-email3], [kid-email4], [kid-email5], [kid-email6], [kid-email7]
or
BCC: [kid-email1], [kid-email2], [kid-email3], [kid-email4], [kid-email5], [kid-email6], [kid-email7]
A possible problem that could arise: Many hosts block things like this to prevent spam. If theses do not work, then this is probably the case. You would need to contact your hosting provider about removing the block.
Contact form 7 has only one recipient field, however you can enter multiple email address with comma separated in that field, example "email1#domain.com,email2#domain.com,email3#domain.com".
So for your case, use JavaScript to add multiple duplicate recipient fields dynamically, and finally on form submit write a JavaScript function to concat all the recipient email addresses then keep that in main recipient field and submit the form. Hope you got my point.
I have added some custom code in a block using PHP code format to show that block on a specific page. I have checked all the things working fine on Devel PHP page but contents are not showing on page. The code below fetches the field value of a destination node.
$refer = $_SERVER[HTTP_REFERER];
$parsed = parse_url($refer);
$alias = array_pop($parsed);
$dst = \Drupal::service('path.alias_manager')->getPathByAlias($alias , $langcode);
$nid = array_pop(explode('/', $dst));
$dest_node = node_load($nid);
$body = $dest_node->get('body')->getValue();
print $body; //have tried other printing methods also but invain
Hope this clarifies the question.
Thanks
Are you sure that it works in Devel? I've just tried to execute your code, and this line:
$body = $dest_node->get('body')->getValue();
returns Array.
Try to use this one instead:
$body = $dest_node->body->value;
First of all, your first block of code (getting current node) can be replaced with just one line:
$node = \Drupal::service('current_route_match')->getParameter('node');
And the whole block can be changed in the following way:
if ($node = \Drupal::service('current_route_match')->getParameter('node')) {
print $node->body->value;
}
P.S. And it's definitely a bad idea to use PHP text filter. You may easily write your own custom module providing required block. The simplest block plugin requires several lines of code:
/**
* #file
* Contains \Drupal\my_module\Plugin\Block\MyBlock.
*/
namespace Drupal\my_module\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Provides my super block.
*
* #Block(
* id = "my_module_block",
* admin_label = #Translation("My Block"),
* category = #Translation("My Module"),
* )
*/
class MyBlock extends BlockBase{
/**
* Builds and returns the renderable array for this block plugin.
*
* #return array
* A renderable array representing the content of the block.
*
* #see \Drupal\block\BlockViewBuilder
*/
public function build() {
if ($node = \Drupal::service('current_route_match')->getParameter('node')) {
return [ '#markup' => $node->body->value ];
}
}
}
This file MyBlock.php must be placed in /src/Plugin/Block/ directory inside your custom module named my_module.
I have a vocab category and four terms within it. what i want to do is if content is tagged with a termin in particular say "term1" to have the url generated as word1/[node:title] and for all the other tags just the standard url formatting.
If i wanted the term in the url obviously id use pattern replacement but i want another word to be used if a particular tag is used
I can't think of an easy plug-and-play way of achieving this. You may have to create your own token for the "Default path pattern" in Pathauto's URL alias settings:
/**
* Implementation of hook_token_info().
*/
function MODULE_token_info() {
$info['tokens']['node']['node-term-path'] = array(
'name' => t('Node path by term'),
'description' => t('The path to a node based on its taxonomy terms.'),
);
return $info;
}
/**
* Implementation of hook_tokens().
*/
function MODULE_tokens($type, $tokens, array $data = array(), array $options = array()) {
$replacements = array();
if ($type == 'node' && !empty($data['node'])) {
$node = $data['node'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'node-term-path':
$items = field_get_items('node', $node, 'TAXONOMY_FIELD_NAME');
foreach ($items as $item) {
$tids[] = $item['tid'];
}
if (in_array(TID_OF_TERM1, $tids)) {
// Path for nodes with term1
$replacements[$original] = 'word1/'. pathauto_cleanstring($node->title);
}
else {
// Path for other nodes
$replacements[$original] = 'content/'. pathauto_cleanstring($node->title);
}
break;
}
}
}
return $replacements;
}
Found a simple way actually to anyone who need a similar solution use the module Entity Reference.
http://drupal.org/project/entityreference
I just created a new field for the user account select entity reference then you can choose any entity within drupal to reference.
(ie so you can select a term/content/anything)
I need to implement an OR operator between some filters in a Drupal View.
By default, Drupal AND's every filter together.
By using
hook_views_query_alter(&$view, &$query)
I can access the query ( var $query ) , and I can change either :
$query->where[0]['type']
to 'OR', or
$query->group_operator
to 'OR'
The problem is however, that I do not need OR's everywhere. I've tried changing both of them to OR seperately, and it doesn't yield the desired result.
It seems changing those values, puts OR's everywhere, while I need => ( filter 1 AND filter 2 ) OR ( filter 3 ), so just 1 OR.
I could just check the Query of the View, copy it, modify it, and run it through db_query, but that's just dirty ..
Any suggestions ?
Thx in advance.
If you are using Views 3 / Drupal 7 and looking for the answer to this question, it is baked right into Views. Where it says "add" next to filters, click the dropdown, then click "and/or; rearrange". It should be obvious from there.
Unfortunately this is still a missing feature in Views2. It has long been asked for and was promised a while ago, but seems to be a tricky piece of work and is now scheduled for Views3.
In the meantime you could try the Views Or module mentioned in that thread. As of today, it is still in dev status, but seems to be actively maintained and the issue queue does not look to bad, so you might want to give it a try.
if you want do it with view_query_alter hook, you should use $query->add_where() where you can specify if it's AND or OR. From views/include/query.inc
/**
* Add a simple WHERE clause to the query. The caller is responsible for
* ensuring that all fields are fully qualified (TABLE.FIELD) and that
* the table already exists in the query.
*
* #param $group
* The WHERE group to add these to; groups are used to create AND/OR
* sections. Groups cannot be nested. Use 0 as the default group.
* If the group does not yet exist it will be created as an AND group.
* #param $clause
* The actual clause to add. When adding a where clause it is important
* that all tables are addressed by the alias provided by add_table or
* ensure_table and that all fields are addressed by their alias wehn
* possible. Please use %d and %s for arguments.
* #param ...
* A number of arguments as used in db_query(). May be many args or one
* array full of args.
*/
function add_where($group, $clause)
I added it by concatenating the string.
It is relatively specific to the implementation - people would need to play with field to match for OR - node.title in the following code and the field to match it with - node_revisions.body in this case.
Extra piece of code to make sure that node_revisions.body is in the query.
/**
* Implementation of hook_views_api().
*/
function eventsor_views_api() { // your module name into hook_views_api
return array(
'api' => 2,
// might not need the line below, but in any case, the last arg is the name of your module
'path' => drupal_get_path('module', 'eventsor'),
);
}
/**
*
* #param string $form
* #param type $form_state
* #param type $form_id
*/
function eventsor_views_query_alter(&$view, &$query) {
switch ($view->name) {
case 'Events':
_eventsor_composite_filter($query);
break;
}
}
/**
* Add to the where clause.
* #param type $query
*/
function _eventsor_composite_filter(&$query) {
// If we see "UPPER(node.title) LIKE UPPER('%%%s%%')" - then add and to it.
if (isset($query->where)) {
$where_count = 0;
foreach ($query->where as $where) {
$clause_count = 0;
if (isset($where['clauses'])) {
foreach ($where['clauses'] as $clause) {
$search_where_clause = "UPPER(node.title) LIKE UPPER('%%%s%%')";
// node_data_field_long_description.field_long_description_value
$desirable_where_clause = "UPPER(CONCAT_WS(' ', node.title, node_revisions.body)) LIKE UPPER('%%%s%%')";
if ($clause == $search_where_clause) {
// $query->add_where('or', 'revisions.body = %s'); - outside of what we are looking for
$query->where[$where_count]['clauses'][$clause_count] = $desirable_where_clause;
// Add the field to the view, just in case.
if (!isset($query->fields['node_revisions_body'])) {
$query->fields['node_revisions_body'] = array(
'field' => 'body',
'table' => 'node_revisions',
'alias' => 'node_revisions_body'
);
}
}
$clause_count++;
}
}
$where_count++;
}
}
}