drupal_add_html_head just after drupal_add_js - drupal

I want add the cookiebot script external
<script id="Cookiebot" src="https://consent.cookiebot.com/uc.js" data-cbid="idcookiebot" data-culture="languageId" type="text/javascript"></script>
Just after this
drupal_add_js("window.dataLayer = window.dataLayer || [];
function gtag() { window.dataLayer.push(arguments); }
gtag('consent', 'default', {
ad_storage: 'denied',
analytics_storage: 'denied',
wait_for_update: 500,
});",
array('type' => 'inline')
);
But, with drupal_add_html_head, the external script is located in the first line, but a I need this after the script.
I can't find how drupal_add_js add external JS with data elements

You can set scope and weight for extra javascript like this:
drupal_add_js("window.dataLayer = window.dataLayer || [];
function gtag() { window.dataLayer.push(arguments); }
gtag('consent', 'default', {
ad_storage: 'denied',
analytics_storage: 'denied',
wait_for_update: 500,
});",
array(
'type' => 'inline',
'scope' => 'footer',
'weight' => 5,
)
);

Related

How to authenticate for remote access to protected data in LearnDash via the Wordpress REST API?

I am developing a mobile app (in Flutter) that needs to access course data of the user from a Wordpress LearnDash LMS environment. The Wordpress site is accessible via the standard REST API through the https://<site>/wp-json/ldlms/v1/ path.
But whenever I try to access e.g. <...>/ldlms/v1/sfwd-courses/<id>/steps, the result is a 401 (Unauthorized) status.
From the Wordpress documentation I learned it uses cookie authentication. But adding the wordpress_logged_in_<hash> cookie to the request header does not seem to make a difference.
Where can I find the missing authentication details required to access this information?
I just built my REST API; so I may be of some help. The example below will:
Register a custom rest api endpoint
Localize server-side variables on first load (endpoint, user object, etc)
Append the NONCE to all rest requests avoiding the hash BS
Rest API Authentication Reference
functions.php
add_action('rest_api_init', 'register_custom_fields');
function register_custom_fields(){
register_rest_route(
'app', '/login/',
[ 'methods' => 'POST', 'callback' => 'asset_login' ]
);
}
function asset_login(){
$user = wp_signon($_POST);
if (is_wp_error($user)){
return [ 'result' => 'fail', 'error' => $user->get_error_message() ];
} else {
return [ 'result' => 'success', 'user' => [ 'ID' => $user->data->ID, 'name' => $user->data->display_name ] ];
}
}
add_action('init','stage_script');
function stage_script(){
wp_enqueue_script('asset-library', trailingslashit(get_stylesheet_directory_uri()) . 'js/asset-library.js', ['jquery']);
$assetUser = null;
if (is_user_logged_in()){
$user = wp_get_current_user();
$assetUser = [
'ID' => $user->data->ID,
'name' => $user->data->display_name,
'nickname' => $user->data->user_nicename,
'email' => $user->data->user_email,
'allcaps' => $user->allcaps
];
}
wp_localize_script( 'your-app', 'App', [
'user' => $assetUser,
'api_endpoint' => esc_url_raw( rest_url() ),
'api_version' => 'wp/v2/',
'nonce' => wp_create_nonce( 'wp_rest' )
]);
}
script.js
// The Login Controller
let AppUser = {
loggedIn: false,
login: function(data){
$.ajax( {
url: App.api_endpoint + 'your-app/login',
method: 'POST',
beforeSend: function ( xhr ) {
xhr.setRequestHeader( 'X-WP-Nonce', App.nonce );
},
data: data,
success: function(response){
/* ... parse response ... */
},
error: function(response){
/* ... parse response ... */
}
});
}
};
// The API Controller
let QueryController = {
Objects: [],
getObjects: async function(){
let q = {};
/* ... build query stuff extracted */
try {
const response = await $.ajax( {
url: App.api_endpoint + App.api_version + 'post_object',
method: 'GET',
beforeSend: function ( xhr ) {
xhr.setRequestHeader( 'X-WP-Nonce', App.nonce );
},
data: q
});
this.Objects = response;
return response;
} catch(e) {
console.log('Error: ', e);
}
}
};

Add button to Drupal ckeditor

I'm just learning Drupal and would like to add a button to the CKEditor. I've tried a few techniques and I'm never able to get a new button to appear. Here's the module I have so far.
Can anyone tell me why this code doesn't work?
Here's a list of other files besides the ones included:
image.png (/sites/all/modules/excel_to_html/images/image.png) - icon
excel_to_html.info (/sites/all/modules/excel_to_html/excel_to_html.info)
excel_to_html.module
location : /sites/all/modules/excel_to_html/excel_to_html.module
<?php
function excel_to_html_ckeditor_plugin() {
return array(
'plugin_name' => array(
'name' => 'excel_to_html',
'desc' => t('Plugin description'),
'path' => drupal_get_path('module', 'excel_to_html') .'/plugins/excel_to_html/',
'buttons' => array(
array('label' => 'Button label', 'icon' => '/images/image.png'),
)
)
);
}
plugin.js
location : /sites/all/modules/excel_to_html/plugins/excel_to_html/plugin.js
(function() {
CKEDITOR.plugins.add('excel_to_html',
{
icons: 'images/image.png',
init: function(editor)
{
editor.addCommand('excel_convert',
{
exec : function(editor)
{
alert('testing button!');
//var selected_text = editor.getSelection().getSelectedText();
}
});
editor.ui.addButton('excel_to_html',
{
label: 'Convert Excel to table',
command: 'excel_convert',
icon: this.path + 'images/image.png'
});
},
afterInit: function (editor)
{
alert('done');
}
});
})();

Added options in Highcharts download/context buttons get repeated

I am adding a few options to my download/export/context buttons in Highcharts. However, the process is a bit complex, as this works as an API. A call is being received by one file, another file is being called where all the options for the graph is being produced, it comes back to the first file, where the graph is generated. Here, the additional options are introduced into the context buttons:
function drawGraph(selectedID, selectedCountries, selectedYears, per_capita, graphBorder, graphSource, graphDefinition, graphStyle, graphXAxis, renderToGraph, renderToDescription)
{
jQuery(document).ready(function() {
var options = {};
url = "xxx.com";
jQuery.getJSON(url, {selectedCountries: selectedCountries , selectedID: selectedID, selectedYears: selectedYears, per_capita: per_capita, graphBorder: graphBorder, graphSource: graphSource, graphDefinition: graphDefinition, graphStyle: graphStyle, graphXAxis: graphXAxis, renderToGraph: renderToGraph, type: "jsonp"})
.done(function(data)
{
//console.log(data);
options.chart = data["chart"];
options.tooltip = data["tooltip"];
options.series = data["series"];
options.title = data["title"];
options.subtitle = data["subtitle"];
options.yAxis = data["yAxis"];
options.xAxis = data["xAxis"];
options.legend = data["legend"];
options.exporting = data["exporting"];
options.plotOptions = data["plotOptions"];
options.credits = data["credits"];
if ((graphDefinition == "true") || (graphDefinition == "on"))
{
jQuery('#'+renderToDescription).html(data["description"]);
}
var chart = new Highcharts.Chart(options);
})
var buttons = Highcharts.getOptions().exporting.buttons.contextButton.menuItems;
// add "separator line"
buttons.push({
separator: true,
});
// add "Link to metadata"
buttons.push({
text: 'Metadata',
onclick: function () {
window.open('http://xxx/metadata.php?selectedID=' + selectedID + '&selectedDatasettype=1', "_blank");
}
});
}
// add "separator line"
buttons.push({
separator: true,
});
// add "Link to more data functions"
buttons.push({
text: 'Link to more data functions',
onclick: function () {
window.open('http://xxx/options.php?selectedID=' + selectedID + '&selectedDatasettype=1', "_blank");
}
});
});
}
And from the other end, I have the file which generates the JSON code:
$data_ = array( "chart" => array("renderTo" => $container, "type" => $graphStyle, "zoomType" => "xy", "marginTop" => $marginTop, "marginRight" => 20, "marginBottom" => 60, "marginLeft" => 80),
"title" => array("useHTML" => true, "text" => "<div style='text-align: center'>".str_replace("CO2", "CO<sub>2</sub>", $selectedDataset -> name)."</div>", "align" => "center", "style" => array("fontFamily" => "Helvetica", "fontSize" => "20px")),
"subtitle" => array("text" => "Original data source: <a href='" . $provider_url . "' style='font-family: Helvetica; color: #428bcc; text-decoration: none' target='_blank'>" . $selectedDataset -> data_provider . "</a>", "useHTML" => true),
"xAxis" => array("tickWidth" => 0, "showFirstLabel" => true, "showLastLabel" => true, "tickInterval" => $step),
"yAxis" => array("min" => $min, "title" => array("useHTML" => true, "text" => str_replace("CO2", "CO<sub>2</sub>", $yTitle), "fontWeight" => "bold", "align" => "high", "textAlign" => "left", "rotation" => 0, "y" => $yAxisY)),
"legend" => array("enabled" => $flagValues, "layout" => "vertical", "align" => "center", "verticalAlign" => "middle", "backgroundColor" => "#efefef", "borderWidth" => 0, "floating" => false, "x" => -185, "y" => 100, "title" => array("text" => ":: Legend ::"), "floating" => true, "draggable" => true, "zIndex" => 20),
"plotOptions" => array("series" => array("connectNulls" => true, "shadow" => false, "lineWidth" => 2, "marker" => array("enabled" => false))),
"tooltip" => array("shared" => true, "crosshairs" => true),
"credits" => array("text" => $copyright, "href" => "http://ede.grid.unep.ch", "position" => array("x" => 10, "y" => -20, "align" => "left"), "style" => array("fontSize" => "9px", "lineHeight" => "9px")),
"exporting" => array("buttons" => array("contextButton" => array("symbol" => "url(http://geodata.grid.unep.ch/images/button_download_4.png)", "x" => -10, "y" => 10))),
"series" => $data,
"protected" => $selectedDataset -> protected_,
"description" => $selectedDataset -> abstract,
"noData" => $flagValues);
header("content-type: application/json");
echo $_GET['callback']. '('. json_encode($data_) . ')';
Now, strangely enough, it seems that if a user from the same site chooses one graph after another, the additional context items are being added up. So, the first call, the separator line and the link to metadata are being added; for the second call, I see the separator line and the link to metadata two times... Very strange. No clue.
One thought: Can the contextButtons first be emptied for each call? And then the additional options added? Something like
Highcharts.getOptions().exporting.buttons.contextButton.menuItems.empty()
Thanks for any hints.
I finally found an approach which seems to work well. This is, loop through the array of menuItems of the contextButton. Check if the item already exists, replace its function. Otherwise, use the standard push-option.
Hopes this helps someone else in a similar situation.
var buttons = [];
buttons = Highcharts.getOptions().exporting.buttons.contextButton.menuItems;
flag_metadata = false;
flag_link_functions = false;
for (var x in Highcharts.getOptions().exporting.buttons.contextButton.menuItems)
{
if (buttons[x].id == "Metadata")
{
buttons[x].onclick = function () {
window.open('http://ede.grid.unep.ch/mod_metadata/metadata.php?selectedID=' + selectedID + '&selectedDatasettype=1', "_blank");}
flag_metadata = true;
}
if (buttons[x].id == "Link")
{
buttons[x].onclick = function () {
window.open('http://ede.grid.unep.ch/options.php?selectedID=' + selectedID + '&selectedDatasettype=1', "_blank");}
flag_metadata = true;
}
}
if (flag_metadata == false)
{
// add "separator line"
Highcharts.getOptions().exporting.buttons.contextButton.menuItems.push({
separator: true,
id: 'sep1',
});
// add "Link to metadata"
Highcharts.getOptions().exporting.buttons.contextButton.menuItems.push({
id: 'Metadata',
text: 'Metadata',
onclick: function () {
window.open('http://ede.grid.unep.ch/mod_metadata/metadata.php?selectedID=' + selectedID + '&selectedDatasettype=1', "_blank");
}
});
// add "separator line"
Highcharts.getOptions().exporting.buttons.contextButton.menuItems.push({
separator: true,
id: 'sep2',
});
// add "Link to more data functions"
Highcharts.getOptions().exporting.buttons.contextButton.menuItems.push({
id: 'Link',
text: 'Link to more data functions',
onclick: function () {
window.open('http://ede.grid.unep.ch/options.php?selectedID=' + selectedID + '&selectedDatasettype=1', "_blank");
}
});
}

check a condition before a ajax call back of form in drupal 7

I have written ajax callback on a textfield and it is called on blur event.
But before It going for ajax callback I want to check a condition that textfield not empty.
So I want,If textfield is not empty then the ajax callback is get called otherwise it should not get called.
Thanks in advance
form_example is my module name
Form control
$form['price_form']['item'] = array(
'#type' => 'textfield',
'#title' => 'Item Name?',
'#size' => 10,
'#maxlength' => 25,
'#id' => 'nameId',
'#required' => TRUE,
'#ajax' => array(
// #ajax has two required keys: callback and wrapper.
// 'callback' is a function that will be called when this element changes.
'callback' => 'form_example_simplest_callback',
'wrapper' => 'listDiv',
'effect' => 'fade',
),
'#autocomplete_path' => 'examples/form_example/item_name_autocomplete_callback',
);
JS Code
(function($){
$(document).ready(function(){
alert('Hi, Javascript successfully attached');
Drupal.behaviors.form_example = {
attach: function (context, settings) {
// Overwrite beforeSubmit
Drupal.ajax['nameId'].options.beforeSubmit = function (form_values, element, options) {
alert('dsf');
}
}
};
});
})(jQuery);
I am printing alert for testing.I tried by its name and by its id but not getting alert.I got below alert so js inclusion fine.
alert('Hi, Javascript successfully attached');
The ajax implementation in the form api lets you specify a 'beforeSubmit' handler that will be run before submission. According to this : http://malsup.com/jquery/form/#options-object if that function returns false the form won't be submitted.
You should be able to add a beforesubmit handler something like:
Drupal.behaviors.MyModule = {
attach: function (context, settings) {
// Overwrite beforeSubmit
Drupal.ajax['your_element'].options.beforeSubmit = function (form_values, element, options) {
// check the textfield isn't blank then return false;
}
}
};

Drupal autocomplete, callback with multiple parameters

I am adding some autocomplete on a form alter. The problem is that in the callback, only the string in the textfield The autocomplete is on, is available. I also want to access a value from another textfield in the callback. How is this possible ?
/**
* Implements hook_form_alter().
*/
function webform_conversion_jquery_form_webform_client_form_1_alter(&$form, &$form_state, $form_id) {
//Load some extra function to process data
module_load_include('inc', 'webform_conversion_jquery', '/includes/dataqueries');
//Add extra js files
drupal_add_js(drupal_get_path('module', 'webform_conversion_jquery') . '/js/conversionform.js');
$form['submitted']['correspondentadress']['cor_street']['#autocomplete_path'] = 'conversionform/conversion_street';
}
}
/**
* Implements hook_menu().
*/
function webform_conversion_jquery_menu() {
$items = array();
$items['conversionform/conversion_street'] = array(
'title' => 'Conversion street autocomplete',
'page callback' => 'conversion_street_autocomplete',
'access callback' => 'user_access',
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Retrieve a JSON object containing autocomplete suggestions for streets depending on the zipcode.
*/
function conversion_street_autocomplete($street = '') {
$street = "%" . $street . "%";
$matches = array();
$result = db_select('conversion_adresslist')
->fields('conversion_adresslist', array('street'))
->condition('street', $street, 'like')
->execute();
foreach ($result as $street) {
$matches[$street->street] = $street->street;
}
drupal_json_output($matches);
}
I just want to be able to post extra information in the function:
conversion_street_autocomplete($street = '', $extraparameter)
I had the same problem and have figured out a way, which is not too strenuous. It involves overriding the textfield theme and then passing your parameter to the theme function.
First create declare your theme function:
function mymodule_theme() {
$theme_hooks = array(
'my_module_autocomplete' => array(
'render element' => 'element',
),
);
return $theme_hooks;
}
Next we need to add the theme and the variable to our form element. In my case, the form element is part of a field widget:
function my_module_field_widget_form($form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
if($instance['widget']['type'] == 'my_module_field_type') {
$element['my_module_field'] = array(
'#type' => 'textfield',
'#autocomplete_path' => 'my-module/autocomplete',
// THIS IS THE IMPORTANT PART - ADD THE THEME AND THE VARIABLE.
'#theme' => 'my_module_autocomplete',
'#my_module_variable' => $field['field_name'],
);
}
return $element;
}
Then implement the theme function. This is a copy of theme_textfield from includes/form.inc with one important difference - we append the variable to the autocomplete path:
function theme_my_module_autocomplet($variables) {
$element = $variables['element'];
$element['#attributes']['type'] = 'text';
element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength'));
_form_set_class($element, array('form-text'));
$extra = '';
if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) {
drupal_add_library('system', 'drupal.autocomplete');
$element['#attributes']['class'][] = 'form-autocomplete';
$attributes = array();
$attributes['type'] = 'hidden';
$attributes['id'] = $element['#attributes']['id'] . '-autocomplete';
// THIS IS THE IMPORTANT PART. APPEND YOUR VARIABLE TO THE AUTOCOMPLETE PATH.
$attributes['value'] = url($element['#autocomplete_path'] . '/' . $element['#my_module_variable'], array('absolute' => TRUE));
$attributes['disabled'] = 'disabled';
$attributes['class'][] = 'autocomplete';
$extra = '<input' . drupal_attributes($attributes) . ' />';
}
$output = '<input' . drupal_attributes($element['#attributes']) . ' />';
return $output . $extra;
}
Now the variable will be available as the first parameter on the autocomplete callback function:
function _my_module_autocomplete($my_module_variable, $search_string) {
// Happy days, we now have access to our parameter.
}
Just in case anyone is still having trouble with this I found a great solution while trying to figure out how to do this. I had a year select list and that dictated what data was displayed in the autocomplete field. The solution basically has an ajax callback function for the select list that can then update the autocomplete field with an extra parameter in the url. Anyways, it is really well explained in the following article.
http://complexdan.com/passing-custom-arguments-drupal-7-autocomplete/
*A note of caution, I was going crazy trying to figure out why it did not work and it turns out you can't have the same form on the page twice (I needed to because I was displaying it differently for mobile devices) because you are using an id for the ajax callback. I added an extra argument to accomplish that. It is called uniqueid in the below example.
function report_cards_comparison_form($form, &$form_state, $uniqueid) {
$curryear = t('2012');
$form['year_select'] = array(
'#title' => t('School Year'),
'#type' => 'select',
'#options' => array(
'2012' => t('2012'),
'2013' => t('2013'),
'2014' => t('2014'),
'2015' => t('2015'),
),
'#default_value' => $curryear,
'#ajax' => array(
'callback' => 'report_cards_comparison_form_callback',
'wrapper' => $uniqueid,
'progress' => array(
'message' => 'Updating Schools...',
'type' => 'throbber'
),
),
);
$form['choice'] = array(
//'#title' => t('Search By: School Name'),
'#type' => 'textfield',
'#attributes' => array(
'class' => array('school-choice'),
'placeholder' => t('Start Typing School Name...'),
),
'#required' => TRUE,
'#autocomplete_path' => 'reportcards/autocomplete/' . $curryear,
'#prefix' => '<div id="' . $uniqueid . '">',
'#suffix' => '</div>',
);
$form['submit'] = array(
'#type' => 'submit',
'#prefix' => '<div class="submit-btn-wrap">',
'#suffix' => '</div>',
'#value' => t('Search'),
'#attributes' => array('id' => 'add-school-submit'),
);
return $form;
}
/**
* Ajax Callback that updates the autocomplete ajax when there is a change in the Year Select List
*/
function report_cards_comparison_form_callback($form, &$form_state) {
unset($form_state['input']['choice'], $form_state['values']['choice']);
$curryear = $form_state['values']['year_select'];
$form_state['input']['choice'] = '';
$form['choice']['#value'] = '';
$form['choice']['#autocomplete_path'] = 'reportcards/autocomplete/' . $curryear;
return form_builder($form['#id'], $form['choice'], $form_state);
}
and I can call the form by doing this...
print render(drupal_get_form('report_cards_comparison_form', 'desktop-schoolmatches'));
You can do it by overriding methods from autocomplete.js in your own js. Here is example:
(function($) {
Drupal.behaviors.someModuleOverrideAC = {
attach: function(context, settings) {
// Next is copied and adjusted method from autocomplete.js
Drupal.jsAC.prototype.populatePopup = function() {
var $input = $(this.input);
var position = $input.position();
// Show popup.
if (this.popup) {
$(this.popup).remove();
}
this.selected = false;
this.popup = $('<div id="autocomplete"></div>')[0];
this.popup.owner = this;
$(this.popup).css({
top: parseInt(position.top + this.input.offsetHeight, 10) + 'px',
left: parseInt(position.left, 10) + 'px',
width: $input.innerWidth() + 'px',
display: 'none'
});
$input.before(this.popup);
// Do search.
this.db.owner = this;
if ($input.attr('name') === 'field_appartment_complex') {
// Overriden search
// Build custom search string for apartments autocomplete
var $wrapper = $('div.apartments-autocomplete');
var $elements = $('input, select', $wrapper);
var searchElements = {string: this.input.value};
$elements.each(function() {
searchElements[$(this).data('address-part')] = $(this).val();
});
var string = encodeURIComponent(JSON.stringify(searchElements));
this.db.search(string);
}
else {
// Default search
this.db.search(this.input.value);
}
};
}
};
}(jQuery));
In your server callback:
function some_module_autocomplete_ajax($string) {
// Decode custom string obtained using overriden autocomplete js.
$components = drupal_json_decode(rawurldecode($string));
// Do you search here using multiple params from $components
}
Ok, for as far as I can see it is not possible. maybe you can roll your own with the ajax functionality in fapi http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/7#ajax
For now I solved it by implementing jquery.ui.autocomplete which is included in drupal 7

Resources