6

Hello Joomla Community,

How can a css class be appended to the <body> from inside a system plugin?

Any advice is appreciated, Thank you.

  • Maybe editing the template directly is easier? – Bakual Jun 1 '15 at 6:20
  • Thank you for the advice, I got it running already. Editing the template could perhaps also be an option, but because I was already forced to write a plugin, I assumed that adding a css class in the plugin is the easiest option. This way neither the template nor any other third party component gets altered. – SePröbläm Jun 1 '15 at 11:38
6

If you want to inject code in the html code (adding class in body tag) using OnAfterRender event:

$content= JFactory::getApplication()->getBody();
/* your modification of $content here */
if (preg_match('/(<body.*?)(class *= *"|\')(.*)("|\')(.*>)/', $content))
{
    $content = preg_replace(
        '/(<body.*?)(class *= *"|\')(.*)("|\')(.*>)/',
        '$1$2$3 new_class$4$5',
        $content);
} 
elseif (preg_match('/(<body.*?)(>)/', $content))
{
    $content = preg_replace(
        '/(<body.*?)(>)/',
        '$1 class ="new_class">',
        $content);
}
JFactory::getApplication()->setBody($content);

Editor's note:

According to Joomla Documentation, JResponse::getBody() and JResponse::setBody() have been deprecated since Joomla 3.2.

3.2 Use JApplicationWeb::getBody() and JApplicationWeb::setBody() instead

Also, regex can be an unreliable tool for parsing HTML; PHP provides access to several DOM parsers.

  • Thank you very much! How can I append a new css class to $content then and ensure it's appended to <body>? – SePröbläm May 31 '15 at 21:11
  • Edited. Actually regex should check for class and add additional ones like class="first_class second_class third_class" – pl71 May 31 '15 at 21:46
  • Wow, that are some seriously advanced regexes. Thank you very much for sharing this! – SePröbläm Jun 1 '15 at 11:11
1

Using regular expressions to manipulate valid HTML is not advisable because regex is "DOM-unaware" meaning that it doesn't know if it is actually modifying a tag or just text that seems like a tag. Also, unless expressly told to replace only once, it will happily modify multiple matching strings (effectively damaging your content). Finally, using DomDocument is a superior technique because it will intelligently/reliably parse and modify a class attribute regardless of the position of the attribute in the tag and regardless of the character used to quote the value (single or double quotes).

Code: (PHP Demo)

$new_class = "new_class_attr";

$body = JFactory::getApplication()->getBody();

$dom = new DOMDocument; 
$dom->loadHTML($body, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$bodytag = $dom->getElementsByTagName('body')->item(0);
if ($bodytag->hasAttribute("class")) {
    $new_class = $bodytag->getAttribute("class") . " " . $new_class;
}
$bodytag->setAttribute("class", $new_class);
JFactory::getApplication()->setBody($dom->saveHTML());

And just in case anyone mistakes my endorsement of DOMDocument for fear of regular expressions, here is how I would code it up using refined regex calls.

Code: (PHP Demo) (First Regex Demo) (Second Regex Demo)

$new_class = "new_class_attr";

$body = JFactory::getApplication()->getBody();

$body = preg_replace('~<body[^>]*class *= *["\']\K~', "$new_class ", $body, 1, $count);  // add to existing class attribute
if (!$count) {
    $body = preg_replace('~<body\K~', " class=\"$new_class\"", $body, 1);  // create class with attribute
}
JFactory::getApplication()->setBody($body);

But, again, I don't recommend regex for this job because it will be less stable... Imagine if you had an attribute (and didn't realize it) like specialclass or data-class. So look, let's not go down the tit-for-tat rabbit hole of creating a body string that will break the pattern then adjust the pattern to suit the vulnerability -- just use a DOM parser.

  • Why wouldn't using JavaScript be a better solution? – Eoin Jan 2 at 21:12
  • 1
    The point that I am making is not about client-side versus server-side. This is about regex versus dom parsing. Using regex on valid html is more likely to lead to unintended results compared to a dom parser. The OP is writing a plugin so I agree with pl71 that onAfterRender() seems a good trigger to call upon. – mickmackusa Jan 2 at 23:12
  • 1
    Your js technique looks to be reliable (so long as your vistors are allowing js -- and all reasonable users will be). joomla.stackexchange.com/q/23989/12352 So long as the javascript is executed after the body tag is rendered, this should work for you. – mickmackusa Jan 3 at 4:17
  • 1
    Yeah but addScriotDeclaration is loaded in the head. So I can't use that. I personally think it's annoying that Joomla! doesn't support before the body out of the box. Actually... If I use defer it probably does. – Eoin Jan 3 at 4:19
0

I thought about this and I came up with a different solution. Assuming your plugin has a field called "class" in the XML.

$app = JFactory::getApplication();
$appParams = $app->getParams();

$class = "";
if ($this->params['class'])
{
    $class = $this->params['class'];
}

$pageClass = $appParams['pageclass_sfx'];
if(!empty($pageClass)) 
{
    $class = " " . $this->params['class'];
}
else 
{
    $class = $this->params['class'];
}
$pageClass = $pageClass . $class;
$app->setParams($pageClass);

As the menu/page class is already added to the body we can just concatenate ur new class

  • 1
    Code Review: 3v4l.org/BSZnB Perhaps I could entice you to refine your offering to use simpler conditional logic, efficient processes, and minimal variable declarations. This way no unnecessary work is done by the script. I'll leave you to decide if you want to adapt your answer or not. Just a suggestion. – mickmackusa Jul 20 at 3:52
  • Thank you, this is much better than my simplistic way of coding. The one thing I am querying about my own code, and you have duplicated the error. I seem to be saying if $pageClass is not empty then prepend a space, but else (it is empty) then call it as it is. Surely if it's empty I don't need to do the else statement at all. But because I'm so noob I'm wondering if there was a reason for me doing that else in that way? Can you think of one? – Eoin Jul 20 at 12:35
  • 1
    In my snippet, if the gotten class is not empty, I am using .= to append space and the new class. If the gotten class is not set or falsey, I am not using concatenation -- I am setting the final class value as the new class. Your snippet is saying if the gotten class is not set or falsey, write the empty string followed by the new class value (no delimiting space required). Hmm, if I haven't explained this well enough, let me know and I'll try to say it differently. – mickmackusa Jul 20 at 13:11
  • I'm not entirely sure why you use .= instead of just =. I get what it does, but I've usually seen it used when multiple variables are assigned in a row. But my main point is in the else statement I seem to be looking up params just to assign an empty class. But we already checked if it was empty earlier, so really no need. Could assign "" or just not assign anything. Is that right? Or have I still gotten confused – Eoin Jul 20 at 13:55
  • In my snippet, using .= (concatenating assignment) streamlines the codes by removing the need to write the variable name on both sides of the assignment. And in the else, only assignment is used for "tinfoil hat" reasons. (For instance, someone writes an invalid class like class="0" -- empty() evaluates 0 as true, and concatenating will create class="0 newClassName") Beyond this wacky fringe case, you could use concatenating assignment in both branches. If you do, you could entertain the idea of writing an inline (ternary) condition statement -- but that will make a long line. – mickmackusa Jul 21 at 21:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.