Injecting a custom ul into a menu - php

So I am using a cool little function to render a search bar in my menu. Check it out here:
function add_search_to_wp_menu ( $items, $args ) {
if( 'main-menu' === $args -> theme_location ) {
$items .= '<li class="menu-item menu-item-search">';
$items .= '<form method="get" class="menu-search-form" action="' . get_bloginfo('home') . '/"><p><input class="text_input" type="text" value="Enter Text & Click to Search" name="s" id="s" onfocus="if (this.value == \'Enter Text & Click to Search\') {this.value = \'\';}" onblur="if (this.value == \'\') {this.value = \'Enter Text & Click to Search\';}" /><input type="submit" class="my-wp-search" id="searchsubmit" value="search" /></p></form>';
$items .= '</li>';
}
return $items;
}
add_filter('wp_nav_menu_items','add_search_to_wp_menu',10,2);
I didn't make it - but its doing the job.
So the only problem is that it is appearing at the top level:
<ul>
<li>Normal Item</li>
<li>OH LOOOK THE SEATCHBAR APPEARS HERE</li>
</ul>
The problem is that I need it to appear a few more steps down in the menu, ie - here:
<ul>
<li>
Normal Item
<ul>
<li>
I want THE SEARCH to appear here
</li>
<ul>
</li>
</ul>
Not only that, I want it to appear after the last <ul><li><ul><li> in the menu...
IE, if I had a list of 3 normal items with their own sub items, it would look like this once injected:
<ul>
<li>
Normal Item1
</li>
<li>
Normal Item2
</li>
<li>
Normal Item3
</li>
<li>
Normal Item
<ul>
<li>
I want THE SEARCH to appear here
</li>
<ul>
</li>
</ul>
Hopefully that makes sense. Thanks for the help!

A quick and dirty hack. It will (maybe, needs tuning) works but you have to fiddle with the code and the codex (reference on the bottom of the answer) or you will not make it another time.
function add_search_to_wp_menu_item ( $items, $args ) {
$search_form = '<ul><li class="menu-item menu-item-search">'
. '<form method="get......./form>' // FILL IT WITH YOUR HTML
.'</li></ul></li>';
if( 'main-menu' === $args -> theme_location ) {
$items = preg_replace('/<\/li>\s*$/',$search_form,trim($items));
}
return $items;
}
add_filter('wp_nav_menu_items','add_search_to_wp_menu_item',10,2);
It SHOULD work as I don't know the content of the $item parameter.
If I guess right, stated the correctness of the legacy function, it is a string containing a list of li (without enclosing ul) and then I inject an ul into the last one (if the filter is processing the main-menu element of the page).
References:
http://codex.wordpress.org/Function_Reference/add_filter
http://www.php.net/manual/en/function.preg-replace.php
Addendum
What '/<\/li>\s*$/' is?
It is a regular expression (see an easy tutorial on them], it is not limited to php as it is present in several languages.
It is a language per se, to be precise.
preg_replace leverage on regular expression to let you perform some advanced string manipulation. That regex tell to the engine to find the last tag in the string the one just before the string's end it doesn't matter if there are spaces, tabs or newlines (but just this set of chars) before the ending of the string.
I will dissect the code hoping it will shed some light on it.
The first and last chars / are the regex enclosure, the real regex is <\/li>\s*$
If you search for a literal / into your string you need to escape it with a \. The engine will not think that it marks the end of the regex.
If you escape chars you give them special meanings \s is one of them, and represent a character class (a set of different characters) a tab, a space or a new line.
* is a quantity modifier, it change the meanings of the previous character stating that it can be repeated a number of times there are several of them. Ours say 0 or more.
`$' is a boundary. It means the end of the string.
When the engine find in your string a chunk of characters that fit the description you give him then there is a match and that part of the string will be changed with the second parameter given to preg_replace.
I've just scratched the surface of this topic, and it is up to you to choose to go deeper or not.
The tutorial I linked (you need to read all the 3 parts) is a good point of start.

Related

How can I count the amount of lines of an HTML code with PHP?

I have some HTML generated by a WYSIWYG-editor (WordPress).
I'd like to show a preview of this HTML, by only showing up to 3 lines of text (in HTML format).
Example HTML: (always formated with new lines)
<p>Hello, this is some generated HTML.</p>
<ol>
<li>Some list item<li>
<li>Some list item</li>
<li>Some list item</li>
</ol>
I'd like to preview a maximum of 4 lines of text in this formated HTML.
Example preview to display: (numbers represent line numbers, not actual output).
Hello, this is some generated HTML.
Some list item
Some list item
Would this be possible with Regex, or is there any other method that I could use?
I know this would be possible with JavaScript in a 'hacky' way, as questioned and answered on this post.
But I'd like to do this purely on the server-side (with PHP), possibly with SimpleXML?
It's really easy with XPath:
$string = '<p>Hello, this is some generated HTML.</p>
<ol>
<li>Some list item</li>
<li>Some list item</li>
<li>Some list item</li>
</ol>';
// Convert to SimpleXML object
// A root element is required so we can just blindly add this
// or else SimpleXMLElement will complain
$xml = new SimpleXMLElement('<root>'.$string.'</root>');
// Get all the text() nodes
// I believe there is a way to select non-empty nodes here but we'll leave that logic for PHP
$result = $xml->xpath('//text()');
// Loop the nodes and display 4 non-empty text nodes
$i = 0;
foreach( $result as $key => $node )
{
if(trim($node) !== '')
{
echo ++$i.'. '.htmlentities(trim($node)).'<br />'.PHP_EOL;
if($i === 4)
{
break;
}
}
}
Output:
1. Hello, this is some generated HTML.<br />
2. Some list item<br />
3. Some list item<br />
4. Some list item<br />
I have personally coded the following function, which isn't perfect, but works fine for me.
function returnHtmlLines($html, $amountOfLines = 4) {
$lines_arr = array_values(array_filter(preg_split('/\n|\r/', $html)));
$linesToReturn = array_slice($lines_arr, 0, $amountOfLines);
return preg_replace('/\s{2,}/m', '', implode('', $linesToReturn));
}
Which returns the following HTML when using echo:
<p>Hello, this is some generated HTML.</p><ol><li>Some list item<li><li>Some list item</li>
Or formatted:
<p>Hello, this is some generated HTML.</p>
<ol>
<li>Some list item<li>
<li>Some list item</li>
Browsers will automatically close the <ol> tag, so it works fine for my needs.
Here is a Sandbox example

Get last li content Xpath php

I have next html:
<ul class="pages">
<li class="page-1 active"><a data-page="1" href="/sold?page=1">1</a></li>
<li class="page-2"><a data-page="2" href="/sold?page=2">2</a></li>
<li class="page-3"><a data-page="3" href="/sold?page=3">3</a></li>
<li>...</li>
<li class="page-975"><a data-page="975" href="/sold?page=975">975</a></li>
</ul>
I am trying to get the last li's text which contains the number of the last page (in my example it is 975) with help of Xpath.
I've tried something like:
$page_count = $xpath->query(".//ul[#class='pages']/li/a[last()]/text()")->item(0)->textContent;
but it doesn't work.
what would be the correct query to get last li's text?
try this one:
$page_count = $xpath->query(".//ul[#class='pages']/li[last()]/a/text()")->item(0)->nodeValue;
first: you need to grab the last li and then the text of its a-element
Your version basically searches all the li-elements and within them searches for the last a-element (they only have one) and then their text attribute. So you basically got a list of all the texts.
second: try nodeValue instead of textContent

XPath / PHP - Return index of specific tag that matches a regex

I'm trying to get the index of a tag which href matches certain regex, but whatever I try is throwing me a warning that says that the expression is invalid. Here's an example.
$dom = new DOMDocument();
$dom->loadHTML($html);
$url_check = testurl.com
$finder = new DomXPath($dom);
$finder->registerNamespace("php", "http://php.net/xpath");
$finder->registerPhpFunctions('preg_match');
//Updated to fix some errors, still invalid expression
$index = $finder->evaluate("count((/ol[#id='rso']/li[not(#id) and #class = 'g' and h3[#class='r']/a[php:function('preg_match','/^(http://|https://|ftp://)?(www(\d+)?.)?($url_check)\/?$/', string(#href) > 0)]])/preceding-sibling::*)");
$html is a string that stores the html of a webpage, which contains something like this
<ol id="wrap">
<li class="list">
<h3 class="j">
Not the one I'm trying to match
</h3>
</li>
.
.
.
<li class="list">
<h3 class="j">
Click here
</h3>
</li>
</ol>
Any suggestion is appreciated, and if you know a better/faster way to do this feel free to share :)
I found at least three problems in your expression :
preceding-siblings should be singular, not plural
the count() function has no ending parenthesis
$url_check = testurl.com has no quotes (should trigger a syntax error).
fixed code :
$index = $finder->evaluate("count(/ol[#id='wrap']/li[#class = 'list']/h3[#class='j']/a[php:function('preg_match','/^(http://|https://|ftp://)?(www(\d+)?.)?($url_check)\/?$/', string(#href) > 0)]/preceding-sibling::li[#class='list'])");
Moreover, the example html code you give us doesn't provide any result for the expression (each <a> element has no siblings whatsoever). So, even with these fixes, the expression still returns 0 for your test case, which is normal

convert DIV to SPAN using str_replace

I have some data that is provided to me as $data, an example of some of the data is...
<div class="widget_output">
<div id="test1">
Some Content
</div>
<ul>
<li>
<p>
<div>768hh</div>
<div>2308d</div>
<div>237ds</div>
<div>23ljk</div>
</p>
</li>
<div id="temp3">
Some more content
</div>
<li>
<p>
<div>lkgh322</div>
<div>32khhg</div>
<div>987dhgk</div>
<div>23lkjh</div>
</p>
</li>
</div>
I am attempting to change the non valid HTML DIVs inside the paragraphs so i end up with this instead...
<div class="widget_output">
<div id="test1">
Some Content
</div>
<ul>
<li>
<p>
<span>768hh</span>
<span>2308d</span>
<span>237ds</span>
<span>23ljk</span>
</p>
</li>
<div id="temp3">
Some more content
</div>
<li>
<p>
<span>lkgh322</span>
<span>32khhg</span>
<span>987dhgk</span>
<span>23lkjh</span>
</p>
</li>
</div>
I am trying to do this using str_replace with something like...
$data = str_replace('<div>', '<span>', $data);
$data = str_replace('</div>', '</span', $data);
Is there a way I can combine these two statements and also make it so that they only affect the 'This is a random item' and not the other occurences?
$data = str_replace(array('<div>', '</div>'), array('<span>', '</span>'), $data);
As long as you didn't give any other details and only asked:
Is there a way I can combine these two statements and also make it so that they only affect the 'This is a random item' and not the other occurences?
Here you go:
$data = str_replace('<div>This is a random item</div>', '<span>This is a random item</span>', $data);
You'll need to use a regular expression to do what you are looking to do, or to actually parse the string as XML and modify it that way. The XML parsing is almost surely the "safest," since as long as the string is valid XML, it will work in a predictable way. Regexes can at times fall prey to strings not being in exactly the expected format, but if your input is predictable enough, they can be ok. To do what you want with regular expressions, you'd so something like
$parsed_string = preg_replace("~<div>(?=This is a random item)(.*?)</div>~", "<span>$1</span>, $input_string);
What's happening here is the regex is looking for a <div> tag which is followed by (using a lookahead assertion) This is a random item. It then captures any text between that tag and the next </div> tag. Finally, it replaces the match with <span>, followed by the captured text from inside the div tags, followed by </span>. This will work fine on the example you posted, but will have problems if, for example, the <div> tag has a class attribute. If you are expecting things like that, either a more complex regular expression would be needed, or full XML parsing might be the best way to go.
I'm a little surprised by the other answers, I thought someone would post a good one, but that hasn't happened. str_replace is not powerful enough in this case, and regular expressions are hit-and-miss, you need to write a parser.
You don't have to write a full HTML-parser, you can cheat a bit.
$in = '<div class="widget_output">
(..)
</div>';
$lines = explode("\n", $in);
$in_paragraph = false;
foreach ($lines as $nr => $line) {
if (strstr($line, "<p>")) {
$in_paragraph = true;
} else if (strstr($line, "</p>")) {
$in_paragraph = false;
} else {
if ($in_paragraph) {
$lines[$nr] = str_replace(array('<div>', '</div>'), array('<span>', '</span>'), $line);
}
}
}
echo implode("\n", $lines);
The critical part here is detecting whether you're in a paragraph or not. And only when you're in a paragraph, do the string replacement.
Note: I'm splitting on newlines (\n) which is not perfect, but works in this case. You might want to improve this part.

Jquery cycle menu with container

I am trying to link an anchor to cycle to a specific div. This is what I have:
The menu
<div id="mainmenu">
<ul class="sidenav" id="menu">
<li>
<a href="#page2">Serviciile noastre
</a>
</li>
<li>
<a href="#page8">Credite Nevoi personale fara ipoteca
</a>
</li>
<li>
<a href="">Credite Nevoi personale cu ipoteca
</a>
</li>
<li>
<a href="">Credite ipotecare
</a>
</li>
<li>
<a href="">Carduri de credit
</a>
</li>
<li>
<a href="">Credite "Prima Casa"
</a>
</li>
<li>
<a href="">Refinantari credite/carduri
</a>
</li>
<li>
<a href="">Acte necesare
</a>
</li>
<li>
<a href="">Economisire creditare
</a>
</li>
</ul>
</div>
And the JavaScript
var $jts = jQuery.noConflict();
$jts(function() {
// maincontent cycle
$jts('#maincontent').cycle({
fx: 'blindY', // You can choose effect do you like, for reference : http://www.malsup.com/jquery/cycle/browser.html
speed: 'slow',
timeout: 0,
cleartype: true, // true if clearType corrections should be applied (for IE)
cleartypeNoBg: true,
pager: '#menu',
startingSlide : 0,
after:onAfter,
pagerAnchorBuilder: function(idx, slide) {
// return sel string for existing anchor
return '#menu li:eq(' + (idx) + ') a';
}
});
function onAfter(curr, next, opts, fwd){
//get the height of the current slide
var $ht = $jts(this).height();
I am using jquery.cycle.all.min.js.
So if I understand correctly, you wish to have an <a href=... click event to take it to the corresponding slide.
Simplest way to do this for your example slide id of 7:
The link you want to click to view a specific slide:
<!-- support for those strange users
without Javascript - we will fix this later -->
Sample text
Your JS to control the behaviour
//if the user has JS enabled, we will stop the
//link from taking us away from the page
//and only do the sweet animation
$(".go-to").click(function(gt){
gt.preventDefault();
var slide = $(this).attr("rel");
$("#maincontent").cycle(slide);
});
Main things to note:
Use of the rel attribute. Use it, it's great.
Use of the empty selector class go-to - used only to define that particular link to interact with cycle. This can be any name you like (so long as it follows the conventions outlined below), for example foo, bar, coco-the-clown etc.
Finally, the next is a little off-topic, but a useful piece of information none-the-less;
If you want your pages to validate to W3C standards, ID's must be unique, and start with a letter. Direct from the horse's mouth:
ID tokens must begin with a letter ([A-Za-z]) and may be followed by any number of letters, digits ([0-9]), hyphens ("-"), underscores ("_"), colons (":"), and periods (".").
http://www.w3.org/TR/html4/types.html#type-name
in response to your comment
So the basic premise of what we want to do:
Assign an identifier to each link that corresponds with the slide number - which means this will have to be a number.
Find out the slide order.
Using your link you posted in the comment you should change it to reflect which ever slide number you want to see when clicked, like so:
<a href="credite.php" class="go-to" rel="1" >Credite Nevoi personale fara ipoteca</a>
Also, remove the rel="opt" and class="go-to" from your div element.
If you go and try this now (assuming you have more than 1 slide...) and it is what you wanted, you probably won't need to come back and read the rest.
So when you click the link mentioned above, this should take you to slide number 2 - provided you are not on slide number 2. "What?!" I hear you cry. Read on.
So, if you were to look at the rendered source code in your browser, you will see that inside of your cycle element, maincontent, each of the div elements that are direct descendants of maincontent will be the slides. These are in order (for a computer); 0 is the starting slide and 1 is the second slide, 2 is the third slide ad infinitum.
So - if you are using PHP & MySQL to show your content, it will be super-easy to assign "IDs" to your anchor's rel attributes. Alternatively, you can hard-code these.
Let me know how you did!

Categories