How to add attributes to elements with HtmlPurifier? - htmlpurifier

I'm looking to purify HTML with the HtmlPurifier package and add attributes to certain elements. Specifically, I'd like to add classes to <div> and <p> elements so that this:
<div>
<p>
Hello
</p>
</div>
Gets purified/transformed into this:
<div class="div-class">
<p class="p-class">
Hello
</p>
</div>
How would one go about doing this with HtmlPurifier? Is it possible?

I believe you could do this by doing something along these lines (though please treat this as pseudocode, the last time this scenario worked for me was years ago):
class HTMLPurifier_AttrTransform_DivClass extends HTMLPurifier_AttrTransform
{
public function transform($attr, $config, $context) {
$attr['class'] = 'div-class';
return $attr;
}
}
class HTMLPurifier_AttrTransform_ParaClass extends HTMLPurifier_AttrTransform
{
public function transform($attr, $config, $context) {
$attr['class'] = 'p-class';
return $attr;
}
}
$htmlDef = $this->configuration->getHTMLDefinition(true);
$div = $htmlDef->addBlankElement('div');
$div->attr_transform_post[] = new HTMLPurifier_AttrTransform_DivClass();
$para = $htmlDef->addBlankElement('p');
$para->attr_transform_post[] = new HTMLPurifier_AttrTransform_ParaClass();
Remember to allowlist the class attribute for div and p as well, if you haven't already.
That said, at first glance, HTML Purifier doesn't seem to be the right place for this kind of logic, since adding class names isn't relevant for the security of your site (or is it?). If you're already using HTML Purifier to allowlist your HTML tags, attributes and values, and just want to leverage its HTML-parsing capabilities for some light-weight additional DOM manipulation, I see no particular reason not to. :) But it might be worth reflecting on whether you want to add the classes using some other process (e.g. in the frontend, if that's relevant for your use case).

Related

How to access Another Module's Content and Presentation Items after 2sxc v10.20+

Here is code that worked up through 2sxc 10.9.1. Though I am able to get the CmsBlock for the TabID, ModuleID and get that to .Render(), I need more. Here is the old code. Not sure it makes any difference, but this View is using the normal Link content-type and is running in an older version of the Content App (appx 3.03=ish). 2sxc has been upgraded and is now 11.22.0 LTS.
I have removed unnecessary stuff, so I doubt this runs as is...
#using ToSic.Razor.Blade
#using ToSic.SexyContent.Environment.Dnn7
#{
var Helpers = CreateInstance("_Helpers.cshtml");
// Display the items from the Manage Links module, we go in 'sideways'
// this gives us just the Content items with their Presentations settings, etc.
var sxci = Factory.SxcInstanceForModule(3360, 606); // ModuleID of Manage Links
var dyn = Factory.CodingHelpers(sxci);
var allLinks = dyn.AsDynamic(dyn.Data["Default"]);
}
#* other stuff *#
<div class="row co-documents justify-content-center align-items-center">
#foreach (var linkItem in allLinks) {
var linkInfo = Helpers.LinkInfos(linkItem.Link, linkItem.Window, linkItem.Icon);
string iconStyle = linkItem.IconStyle ?? "fas";
int linkColumns = (int)linkItem.Presentation.Columns;
string linkIconAlign = linkItem.Presentation.IconAlign;
string linkIconBGColor = linkItem.Presentation.IconBGColor;
#* other stuff *#
}
</div>
So the easy thing to figure out was how to get the module as a CmsBlock which I can Render() as is (below), but what I need to do instead is get proper access to the List of Content Items AND their Presentation data (like above, allLinks).
ToSic.Sxc.Dnn.Factory.CmsBlock(606, 3360).Render();
What am I missing? How can I get access to the other module's data like I was doing before? In this case, I do this in 3 different places on the website. So to outline this in English, I have a module that the client manages a few special links that get displayed in MegaMenus, other special nav, and directly on a couple of pages. In each place they render differently. In their "home" module, where they get edited, they just look boring like this:
I realize its something like this:
var allLinks = something1.AsList(something2.Data["Default"]);
I understand that something2 is an app instance, but how do I create it in the context of the other module?
And what is something1 nowadays? And how do instantiate it? Looks like its a new ToSic.Sxc.Code.DynamicCode() but I can't figure out how to construct that in a way that I can use or doesn't just throw errors.
Thanks in advance for any insight!!
Okay, it took a little testing, trial and error. And also I missed that DynamicCode() was a Method of the Factory class. In retrospect it does seem easy now.
So first you get the BlockBuilder
var block = Factory.CmsBlock(606, 3360);
Then you get the DynamicCode instance (Code.DnnDynamicCodeRoot) from that
var dc = Factory.DynamicCode(block);
And then things are normal
var allLinks = AsList(dc.Data["Default"]);
The rest of the code works like it did before; I can foreach through the links with Header (renamed from ListContent) and Presentation (now Content.Presentation) working just as expected.
The above answer works fine if you are inside the C# Razor template of the 2sxc View. But what if you are outside, for example in a Razor template for a DDR Menu?
Same two steps as above (get the block and the dc), but then you do NOT have access to AsList() or the App. Thankfully, you already have DynamicCode, so you could just get all the records in the Bibliography content-type like this:
<ul>
var items = dc.AsList(dc.App.Data["Bibliography"]);
foreach (var item in items)
{
<li>#item.EntityTitle</li>
}
</ul>
So once you've got your dc you've got access to all the usual 2sxc toys.

How to get immediate text with webdriverIO

I have html DOM like this:
<div class="alert alert-danger">
<button class="close" aria-hidden="true" data-dismiss="alert" type="button">×</button>
Your username or password was incorrect.
</div>
I would like to get Your username or password was incorrect. text.
If I do:
$('.global-alerts div.alert-danger').getText()
Then I get this ×.
Is there a way to get the only text part inside that div element?
I managed to do something like this:
getErrorMessageText() {
return browser.execute(function () {
const selector = '.global-alerts div.alert-danger';
// #ts-ignore
return $(selector).contents().not($(selector).children()).text().trim();
});
}
And it works.
But does anybody have better idea? Or more of like webdriverIO approach here?
Does it work if you use something like this?
var innerHTML = $('.global-alerts div.alert-danger').getHTML(false);
the false argument tells indicates whether or not to include the selector element tag in the output.
Serious solution
I do not quite see any other way but to use execute in order to "grab" that information from the page.
I would however place it in a browser command (either do it in the config "before" hook, or add a service that adds the command in the before hook)
This is what I ended up with considering typescript as main language, ignoring the use of jQuery, and considering that you use the before hook:
/**
* Gets executed before test execution begins. At this point you can access to all global
* variables like `browser`. It is the perfect place to define custom commands.
* #param {Array.<Object>} capabilities list of capabilities details
* #param {Array.<String>} specs List of spec file paths that are to be run
* #param {Object} browser instance of created browser/device session
*/
before: function (_capabilities: any, _specs: any, browser: WebdriverIO.Browser) {
browser.addCommand(
'getNodeText',
async function() {
return this.execute(
(selector: string) =>
Array.from( document.querySelector(selector).childNodes || [])
.filter((n: HTMLElement) => n.nodeType === n.TEXT_NODE)
.map(n => n.textContent)
.reduce(function(f, c) {return f+c;}, '')
.replace('\n', '')
.trim(),
this.selector
);
},
true
);
},
With this approach, typescript might complain about the function that passed to webdriver to get executed, so you can either write it properly, or just move it to a .js file and be done with it.
Just watch for document.querySelector(selector), in theory, it should not be null since the command is executed on an already found by webdriver element.
The way you grab the text there is just await (await $('.alert.alert-danger').getNodeText());
This should return the full string from within the node itself, but not any subchild.
Note: If you end up with an element like: <div id="mytxt">my text style is <strong>strong</strong> and <italic> italic </italic>. - html fan</div> and you do this getNodeText(), you probably end up with the value my text style is and . - html fan.
The "don't get bothered to much" solution
This approach will also sort of check that the "x" button is still there.
await expect($('.global-alerts div.alert-danger')).toHaveText('xYour username or password was incorrect.')
in latest version of WebDriverIO (v8), you can use this selector: aria/YourContent. For example:
With the DOM like this:
<h1>Hello World!</h1>
You can use this selector
console.log(await $('aria/Hello World!').getText()) // outputs: "Hello World!"
Ref: https://webdriver.io/docs/selectors/#fetch-by-content

In Typescript/Angular 2 how to find an object in an array by a property of the object

I've been doing some searching and there seem to be a few possible solutions.
First one that will probably work: Filtering an array in angular2
but I find using a pipe and a for loop not ideal.
My idea was using an interface.
This would be a typescript solution. Problem here is defining the collection.
public covers = COVERS;
public images = VECTORS; // a BIG image collection
imagesByCoverType: CoverVector = {}; // Array is made if
// I use Vector[]; but I wanted to use interface.
// images are correctly filtered to the relevant ones.
ngOnInit() {
this.imagesByCoverType = this.images.filter(
image => image.type === 'book-cover');
}
// defined outside of the component class of course.
interface CoverVector {
[book_id: number]: Vector;
}
<li *ngFor="let cover of covers")>
<p>{{cover.title}}</p>
<!-- Here I would like to print out a url to an image of an image object -->
<!-- cover has a property book_id, and also a
chapter_id because the book is subdivided -->
<!-- I was thinking something like this: -->
<p>{{imagesByCoverType[cover.id].url}}</p>
</li>
So I want to access an object in an array by using an interface. How do I do this? Considering also that I have a filtered array of vectors that it should go over.
Recap for clarity:
I want:
A big collection of data that has a unique identifier connected to an 'interface' or find method.
This interface should make it possible to input this unique id and access with it the desired object.
Then all properties of this object should be accessible. Or actually only the url in this case. Point being: it should be there, not just the interface property, but any desired object property.
Then all this elegantly wrapped up in a Angular 2 ngIf statement.
I wouldn't have thought this in-array-find-by-object-property thing would be so hard but it's been a struggle.
Solution I am currently using
It's beyond me why I have to resort to a entire for loop just to access one element I already know the identifier of - from a for loop above it - but I used a for loop using a custom pipe.
// Angular 2
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({
name: 'cover'
})
export class CoverVectorPipe{
transform(value: any, cover_id: number) {
return value.filter(
(item: any)=> item.aim_id === cover_id);
}
}
Any help solving this using an interface is still welcome.
Also I am wondering if this isn't computationally expensive.

advice on how to design an extendable backbone view

I have the following backbone application.
It's a generic crud view, with the following template:
<div id="<%= crudId %>">
<div class="header-view"></div>
<div class="table-view"></div>
<div class="form-view"></div>
</div>
You can see the crud live here: http://bbbootstrap.com.ar/index.html#Wine
The view itself has subviews, to be rendered in the table-view and the form-view.
The thing is I want it to be a base crud view, and to be easily entendable, adding new subviews, for example, adding a new panel to issue some bulk operations.
These are the possible solutions I came out with so far
1- inheritance: create a new CrudBulkView inheriting from CrudView, modify the template to have a bulk-view place holder.
pro: inheritance can provide quite an elegant and simple solution
cons: it's a bit limiting, I'd like to just be able to compose the BulkView and add it to the CrudView.
2- add a method to crudview like addView(view, place) with place being something like 'beforeForm', 'afterForm', 'beforeTable', etc... (it's much too hardcoded...
cons: too hardcoded
3- pass a function with each subview I want to add, that takes care of creating the dom and attaching to it, right after CrudView has rendered the container. the method could be called setEl and return the newly created el.
pro: really flexible
cons: adds some complexity to the process of attaching the subview to the dom
4-modify the crudView template and then attach to it, something like this:
<div id="<%= crudId %>">
<div class="header-view"></div>
<div class="table-view"></div>
<div class="form-view"></div>
<div class="bulk-view"></div
</div>
then bulkView.el would be '.bulk-view'
pro: simple approach
cons: have to mess around with strings, instead of dealing with the dom
I think it's not so strange what I'm trying to achieve. I just want to add a view to a container view, being as much decoupled as possible, and being able to establish where it should be rendered.
After reading your response to my previous answer I went through and modified my example to hopefully give you an idea of how you can implement a system with named views that allows you to control the ordering as you desire. Let me know if this helps or if you have any questions about how it works.
var viewCtor = Backbone.View.prototype.constructor;
// Assuming we have a reference to the subviews already
var BaseCrudView = Backbone.View.extend({
// This is null for an important reason, see comment in constructor
subViews: null,
// Override the constructor instead of initialize since this is meant to be a base object, so things that
// inherit don't have to remember to call the parent inialize every time.
constructor: function() {
viewCtor.apply(this, arguments);
// It is important this is initialized when instantiating the view rather than in the prototype.
// Backbone's extend() will "copy" the prototype properties of the parent when extending, which really
// just performs an assignment. If this were initialized above in the prototype then all children
// that inherit from that prototype would share the exact same instance of the array/object. If a child
// adds something to the array, it would be changed for all instances that inherit from the parent.
this.subViews = {
header: new HeaderView(),
table: new TableView
};
this.subViewOrder = [
'header',
'table'
];
},
addBefore: function(subView, name, beforeView) {
this.subViews[name] = subView;
var viewLoc = this.subViewOrder.indexOf(beforeView);
if(viewLoc == -1) {
viewLoc = 0;
}
this.subViewOrder.splice(viewLoc, 0, name);
},
addAfter: function(subView, name, afterView) {
this.subViews[name] = subView;
var viewLoc = this.subViewOrder.indexOf(afterView);
if(viewLoc == -1) {
viewLoc = this.subViewOrder.length - 1;
}
this.subViewOrder.splice(viewLoc + 1, 0, name);
},
moveBefore: function(name, beforeView) {
this.addBefore(this.subViews[name], name, this.subViewOrder.splice(this.subViewOrder.indexOf(name), 1));
},
moveAfter: function(name, afterView) {
this.addAfter(this.subViews[name], name, this.subViewOrder.splice(this.subViewOrder.indexOf(name), 1));
},
render: function() {
var that = this;
_.each(this.subViewOrder, function(viewName) {
// Assumes the render() call on any given view returns 'this' to get 'el'
that.$el.append(this.subViews[viewName].render().el);
});
return this;
}
});
var BulkCrudView = BaseCrudView.extend({
inialize: function() {
// Skipping the last parameter causes it to insert at the end
this.addAfter(new BulkView(), 'bulkView');
}
});
With this you could easily extend the BulkCrudView and modify its subViews array in initialize to add/insert whatever you want. Though, it'd work just as well to instantiate a BaseCrudView and work with the view methods. Just whatever feels cleaner and/or floats your boat.

Filtering spcific with checkboxes

My question is that I want a filtering system that will filter by checked checkboxes.
The tool is to my comparison website where I compare TV packages.
my visitors should filter the packages by the tv-channels they want to se.
example;
Checkbox 1: Discovery
Checkbox 2: Animal PLanet
Checkbox 3: Disney Channel
Output should be the matching TV-package
Package 1: (contains Discovery and Disney channel)
Package 2: (contains Animal Planet, Disney channel)
Package 3: (contains Animal Planet)
So if checkbox 1 is checked it should only show package 1.
if checkbox 1 + checkbox 2 is checked it should say "No match found, but this package was was closest to your choice"
if checkbox 2 + checkbox 3 is checked it should only show package 2 which match the visitors choice exactly.
I hope your can help me out. I have been searching a lot after this specific solution without any success.
I think it should be in Jquery. i have seen some simular filtering examples, but no one there are like my wish above.
This is an old question, but... I'll take a shot. Working jsfiddle: http://jsfiddle.net/a0nnrfua/
I think a lot really depends on how you intend to define "closest", but presuming a jQuery solution and hopefully your browser requirements aren't TOO far in the past, you could use the data- attributes and jQuery to come up with some relatively simple functions. Or even use the value portions of the checkboxes really.
Psuedocode, it would look like:
Define a click or change handler to detect whenever a checkbox has been touched/changed.
Define a function that will scan all checked items and pass the values into your "closest package" function.
Based on the results of that function, filter your package selection so that your choices are highlighted or marked in some way.
So let's presume the following HTML markup:
<h3>TV Channels</h3>
<div id="TVChannelSelections">
<input type="checkbox" class="tvchannel" name="tvchannel" id="tvchannel_Discovery" value="Discovery" />
<label for="tvchannel_Discovery">Discovery Channel</label>
<br/>
<input type="checkbox" class="tvchannel" name="tvchannel" id="tvchannel_AnimalPlanet" value="Animal Planet" />
<label for="tvchannel_AnimalPlanet">Animal Planet</label>
<br/>
<input type="checkbox" class="tvchannel" name="tvchannel" id="tvchannel_DisneyChannel" value="Disney Channel" />
<label for="tvchannel_Disney">Disney Channel</label>
<br/>
</div>
<div id="message"></div>
<h3>Packages</h3>
<div id="FilteredPackages">
<div class="package deselected" id="Package1" data-channels="Discovery,Disney Channel">Package #1</div>
<div class="package deselected" id="Package2" data-channels="Animal Planet,Disney Channel">Package #2</div>
<div class="package deselected" id="Package3" data-channels="Animal Planet">Package #3</div>
</div>
So in jQuery, your generic change or click handler would be defined in code: Note that I'm saying, any element on your page that has the class "tvchannel" defined, if there's ever a change that occurs, run my filter function.
<script type="text/javascript" src="../path/to/jQuery/library"></script>
<script type="text/javascript">
$(document).ready(function() {
$(".tvchannel").on("change", function() {
FilterMySelectedChannels();
});
});
</script>
Now we can define your Filter function. We're going to assume two things. #1, that we want to find all the selected checkboxes and their values. Then we're going to iterate through the data-channels property of all of our packages (defined as elements with class = "package"). We'll use some form of string comparison and boolean logic to define what a complete match is vs. a close but no cigar match vs. a complete fail.
In order to keep track of things I'm using 3 classes, selected, deselected, and close.
In css, you can decide whether you want notselected to mean "hide the package completely" (i.e. display: none;) or maybe you want it to be visible but greyed out and "struck out" (i.e. text-decoration: strikethrough; color: grey;}
I'm going to use kind of a brute force way of doing the comparison. There are better array functions and comparison functions in javascript, but this should be relatively clear for most people and I trust the good folks at stackoverflow to chime in with better solutions. But this should get you started. :)
<script type="text/javascript">
function FilterMySelectedChannels() {
$checkedboxes = $(".tvchannel:checked");
$packages = $(".package");
var bAnyFound = false;
$packages.each(function () {
var bCloseButNoCigar = false;
var bCompleteMatch = true;
var packagearray = $(this).data("channels").split(",");
var $currentPackage = $(this);
$checkedboxes.each(function () {
if ($.inArray($(this).val(), packagearray) != -1) {
bCloseButNoCigar = true;
} else {
bCompleteMatch = false;
}
});
if (bCompleteMatch) {
$currentPackage.removeClass("selected").removeClass("deselected").removeClass("close").addClass("selected");
bAnyFound = true;
} else if (bCloseButNoCigar) {
$currentPackage.removeClass("selected").removeClass("deselected").removeClass("close").addClass("close");
} else {
$currentPackage.removeClass("selected").removeClass("deselected").removeClass("close").addClass("deselected");
}
});
if (bAnyFound) {
$("#message").html("The following matches were found");
} else {
$("#message").html("No actual matches were found, but here are some close matches based on your selections");
$(".package.close").removeClass("deselected").removeClass("close").removeClass("selected").addClass("selected");
}
}
</script>
<style type="text/css">
.selected {
color: red;
background-color: yellow !important;
}
.deselected {
color: grey;
text-decoration: strike-through !important;
background-color: white !important;
}
</style>
There are obvious optimizations that could probably work here, but it's a start for those trying to do something similar. Note that it assumes that your markup is dynamically generated or properly coded. If you need to guard against human typos, converting your text using .toLowerCase/UpperCase and using the .Trim functions to eliminate extra space will assist. But you still have to choose your data values wisely so there's no overlap. And if you choose them well enough you can use better techniques such as regular expressions and wildcard searches to make the code a bit shorter.
Hope this helps someone!

Resources