Regular expression to replace broken email links - php

Problem: authors have added email addresses wrongly in a CMS - missing out the 'mailto:' text.
I need a regular expression, if possible, to do a search and replace on the stored MySQL content table.
Cases I need to cope with are:
No 'mailto:'
'mailto:' is already included (correct)
web address not email - no replace
multiple mailto: required (more than one in string)
Sample string would be: (line breaks added for readability)
add1#test.com and
add2#test.com and
real web link
second one to replace add3#test.com
Required output would be:
add1#test.com and
add2#test.com and
real web link
second one to replace add3#test.com
What I tried (in PHP) and issues:
pattern: /href="(.+?)(#)(.+?)(<\/a> )/iU
replacement: href="mailto:$1$2$3$4
This is adding mailto: to the correctly formatted mailto: and acting greedily over the last two links.
Thanks for any help. I have looked about, but am running out of time on this as it was an unexpected content issue.
If you are able to save me time and give the SQL expression, that would be even better.

Try replace
/href="(?!(mailto:|http:\/\/|www\.))/iU
with
href="mailto:
?! loosely means "the next characters aren't these".
Alternative:
Replace
/(href=")(?!mailto:)([^"]+#)/iU
with
$1mailto:$2
[^"]+ means 1 or more characters that aren't ".
You'd probably need a more complex matching pattern for guaranteed correctness.
MySQL REGEX matching:
See this or this.

You need to apply a proper mail pattern first (e.g: Using a regular expression to validate an email address), second search for mailto:before mail or nothing (e.g: (mailto:|)), and last preg_replace_callback suits for this.
This looks like working as you wish (searching only email addresses in double quotes);
$s = 'add1#test.com and
add2#test.com and
real web link
second one to replace add3#test.com';
echo preg_replace_callback(
'~"(mailto:|)([_a-z0-9-]+(\.[_a-z0-9-]+)*#[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4}))"~i',
function($m) {
// print_r($m); #debug
return '"mailto:'. $m[2] .'"';
},
$s
);
Output as you desired;
add1#test.com and
add2#test.com and
real web link
second one to replace add3#test.com

Use the following as pattern:
/(href=")(?!mailto:)(.+?#.+?")/iU
and replace it with
$1mailto:$2
(?!mailto:) is a negative lookahead checking whether a mailto: follows. If there is no such one, remaining part is checked for matching. (.+?#.+?") matches one or more characters followed by a # followed by one or more characters followed by a ". Both + are non-greedy.
The matched pattern is replaced with first capture group (href=") followed by mailto: followed by second capture group (upto closing ").

Related

PHP - Comment System "Replace Http// urls" [duplicate]

This question already has answers here:
How do I replace certain parts of my string?
(5 answers)
Closed 2 years ago.
I'm creating a simple comment system connected by Steam API. Every Steam user connected in my website can automatically post things. But i'm changing some functions to replace things like the URLs.
My question is: When a user post something like,
"Hello I'm nice, have a look at http://www.cute.com"
Automatically replaces the http:// for the link without changing the http:// in the string.
Maybe something like this?
<?php
$str = "helloo im nice, have a look http://www.cute.com";
echo preg_replace("/http:\/\/(.+)\.(.+)\.(.+)/", "<a href='http://$1.$2.$3'>$1.$2.$3</a>", $str);
?>
This will convert any link into an anchor (or an a tag).
Alternative added
Alternatively, it might be a good idea to add support for https as well. In which case the following might be useful.
<?php
$str = "helloo im nice, have a look http://www.cute.com";
echo preg_replace("/http(s?):\/\/(.+)\.(.+)\.(.+)/", "<a href='http$1://$2.$3.$4'>http$1://$2.$3.$4</a>", $str);
?>
This takes advantage of the ? modifier which means "one or more of the preceding character". In this case it is the "s" character since it is "http" and "https" both match.
Explanation
This uses RegEx (or Regular Expressions) to create this.
The first parameter of the preg_replace function takes the RegEx (I like to test mine here: http://regexr.com/).
All RegExs must start and end with a forward slash. The bits inbetween are as follows.
http: is simply selecting a string that starts with "http:"
\/\/ is called "escaping" and that will select two forward slashes. Since forward slashes are special characters used in RegEx (start and end of a statement) they need to be escaped so that PHP doesn't think the RegEx has ended sooner.
(.+) The brackets are also special characters (though not escaped) and they are known as "capture groups". What this is used for is so that I can see what is between the "http://" and the ".com" (or whatever extension is used). The full stop (or period or ".") character selects anything.
\. Further on the escaping. Since full stop is used as a special character, we have to escape this one. What that means so far is that we are selecting "http://" then anything and then stopping at a full stop.
(.+) Last but not least is the final capture group. This, again selects anything from the string so that have our final capture group and RegEx complete.
Modifiers:
? means "one or more of the preceding character". This means that /tests?/ would match test and tests since s is the preceding character and in the first example we have 0 and in the second there is 1
+ means "one of more of the preceding character". In this case we are saying one of more of anything which means we expect at least one character to be provided.
The second parameter is our replace part.
In short, the $1 and $2 sections are to reference the two brackets from the above RegEx.
Some further reading
The PHP function I used
More information on Regular Expressions
RegEx capture groups
$string = 'helloo im nice, have a look http://www.cute.com';
$string = str_replace('http://', '', $string);
echo $string;

How do I extract one group from a URL using regex for use in a redirect?

I've read the Best RegEx Trick Ever and tried to wrap my head around the other answers here on Stack Exchange and just can't seem to get it right. Take these three strings:
http://www.test.com/newyork/class-schedule
http://www.test.com/location/newyork/class-schedule
http://www.test.com/location/newyork/training
I need a regex that will extract the newyork from the first string and save it for a replace later, but will NOT match any part of the other strings. Also, for obscure reasons, I can not include http://www.test.com as a condition for matching (so I can't use anything before the slash that precedes newyork). Note that in this scenario, newyork could easily be chicago, atlanta, or any other city name with no spaces or punctuation.
The only thing I've been able to figure out that isolates only newyork in the first string is the following:
/.*\.com\/(.[^\/]*)\/class-schedule/g
However, this relies on using the URL first which I can't use.
Any ideas on how to achieve this WITHOUT using the URL?
[EDIT]
To clarify what I'm looking for, I'm trying to take the results from the first string and add "location" to it, still using regex. So:
http://www.test.com/newyork/class-schedule
would become
http://www.test.com/location/newyork/class-schedule
using something like
http://www.test.com/location/$1/class-schedule
Try this: ~/(\w+)/[-a-z]+?/?(?:\?.*?)*(:?\s|$)~gm
See it working here: https://regex101.com/r/4VMazZ/3.
So it will use the end of URL instead of the beginning and match only the word between slash 2 and 3 from the end. There can be a query string it will still work.
[EDIT 1]
I exchanged 2 chars doing typo in the end so it was capturing one extra group: /(\w+)/[-a-z]+?/?(?:\?.*?)*(?:\s|$). here: https://regex101.com/r/4VMazZ/4
If you use preg_match($pattern, $string, $matches); the result you want (newyork) will be in $matches[1];, $matches[0] contains everything.
You can see the captures in 'MATCH INFORMATION' panel on regex101 in my example!
[EDIT 2] after your comment.
If you want to replace the whole url you have to match the whole URL, something like this: .*?/(\w+)/[-a-z]+?/?(?:\?.*?)*(?:\s|$) will do in this example. See it working here: https://regex101.com/r/4VMazZ/5
[EDIT 3] Add capturing of last part for replacement.
So as you want to reuse last part you need to add capturing parenthesis: .*?/(\w+)/([-a-z]+?)/?(?:\?.*?)*(?:\s|$).
See it working here: https://regex101.com/r/4VMazZ/6
Could this work? See it here.
(?<=location\/|\.\w{3}\/|\.\w{2}\/)(?!location).*?(?=\/|$)
It matches everything following .xxx/ or .xx/ or location/. I don't know if one letter domain exist, in this case, you can add |\.\w\/ to the lookahead at the start of the regex.
(?<=location\/|\.\w{3}\/|\.\w{2}\/) is a lookahead, so it matches the following pattern only if preceded by location/ or .xxx or .xx
.*? matches every character (lazy)
(?=\/|$) end match if next character is / or on line end
Note: If location is counted as part of the url, I don't think what you are asking is possible in regex, as the city name could be anywhere in string. If so, then you could have a list of cities and check what part of the url matches one of them.
EDIT: You need the multiline m flag so $ also matches end of line

multiple text replace inside a string, while keeping the selected variable

I have a web script that creates a HTML page into a PHP string, then delivers it to the user. All of the pages are generated by index.php, with a unique url.
domain.host.com/index.php?loadpage=/BLAH
The homepage is static HTML, but every other page is dynamically generated into this PHP string. It may seem like im rambling, just trying to give as much info as possible. I have created a javascript code to modify the link url:
BLAH Link
This basically shows the nice neat link in the status bar, but the javascript sends it to the URL i want (I have no need to modify the url bar, as this is in an iframe)
These links are fine on the static page. But on the dynamically generated page thats in the PHP string is a little harder. I need to search through a string for every occurence of:
href="?loadpage=/ [WILDCARD] " title=
and replace it with:
href="http://domain.com/ [WILDCARD] " onclick="location.href='?loadpage=/ [WILDCARD] '; return false;" title=
This seems very complicated to me and I think it could be ereg / preg match / replace, but have no clue with regex.
In a short summary, I need some way of searching through a php string that contains the full page html, and replacing the first string with the second (on every occurance of a link with '?loadpage/'. But each link will have a different [WILDCARD] so i'm presuming, that the script will need to find every occurance, save the [WILDCARD] to a variable, then do the replace function, and insert the word its just saved as a variable from the first url.
EDIT.
Just to clarify what the original link looks like:
<a id="random" href="?loadpage=/BLAH" title="BLAH Title"></a>
this is why i am only searching from the href attribute.
You are right, what you need is a regex. (Your need for a wildcard replace is the clue). This answer is not supposed to be a complete solution, just give you an idea how regexes work. I will leave it to you to integrate this with php (try preg_match_all)
This is the pattern you want to match:
"\?loadpage=\/([^"]*)"
The \ is an escape for characters that have special meaing in regexes
So ignoring the escapes this is
"?loadpage=/ //the start of the string up to the wildcard part
() // capturing parentheses, indicating a part that
// you want to access in the replace string
[^"]* // any number of occurences of any character that is NOT doublequote
// ^ is the negation symbol
// * indicates "zero or more occurrences"
followed by...
" doublequote character
Now you need a replacement string ... for this you just need to know that your (capture parentheses) allow you to recall that part of the match. In most regex flavours your can capture these to a series numbered variables, usually represented as $1, $2, $3.. \1 \2 \3... In your case you only have one capture variable to deal with.
So you replacement string could look like
"http://domain.com/$1/" onclick="location.href='?loadpage=/$1'; return false"
In perl you would put the whole thing together like this:
$string =~ s|"\?loadpage=\/([^"]*)"|"http://domain.com/$1/" onclick=\"location.href='?loadpage=/$1'\; return false"|g;
Note that you don't need to escape your quotemarks. This may differ in php.
As you will see it easily gets very cryptic. regular-expressions.info is a useful online reference.
just so you know what you are looking at (you won't need to do this in php)...
=~ is the perl regex operator (you won't use this in php, take a look at the preg_match documentation)
then you have the form
s|match_pattern|replace_pattern|g;
where s indicates replacement (as opposed to simple matching)
g indicates global matching (otherwise process will stop on first match)
||| are the separators. Usually written /// but then you would have to escape all of your URL //s, which doubles the illegibility.
But this is now too much perl-specifc detail, read the php regex docs!

PHP preg_replace non-greedy trouble

I've been using the following site to test a PHP regex so I don't have to constantly upload:
http://www.spaweditor.com/scripts/regex/index.php
I'm using the following regex:
/(.*?)\.{3}/
on the following string (replacing with nothing):
Non-important data...important data...more important data
and preg_replace is returning:
more important data
yet I expect it to return:
important data...more important data
I thought the ? is the non-greedy modifier. What's going on here?
Your non-greedy modifier is working as expected. But preg_match replaces all occurences of the the (non-greedy) match with the replacement text ("" in your case). If you want only the first one replaced, you could pass 1 as the optional 4th argument (limit) to preg_replace function (PHP docs for preg_replace). On the website you linked, this can be accomplished by typing 1 into the text input between the word "Flags" and the word "limit".
just an actual example of #Asaph solution. In this example ou don't need non-greediness because you can specify a count.
replace just the first occurrence of # in a line with a marker
$line=preg_replace('/#/','zzzzxxxzzz',$line,1);

Need a good regex to convert URLs to links but leave existing links alone

I have a load of user-submitted content. It is HTML, and may contain URLs. Some of them will be <a>'s already (if the user is good) but sometimes users are lazy and just type www.something.com or at best http://www.something.com.
I can't find a decent regex to capture URLs but ignore ones that are immediately to the right of either a double quote or '>'. Anyone got one?
Jan Goyvaerts, creator of RegexBuddy, has written a response to Jeff Atwood's blog that addresses the issues Jeff had and provides a nice solution.
\b(?:(?:https?|ftp|file)://|www\.|ftp\.)[-A-Z0-9+&##/%=~_|$?!:,.]*[A-Z0-9+&##/%=~_|$]
In order to ignore matches that occur right next to a " or >, you could add (?<![">]) to the start of the regex, so you get
(?<![">])\b(?:(?:https?|ftp|file)://|www\.|ftp\.)[-A-Z0-9+&##/%=~_|$?!:,.]*[A-Z0-9+&##/%=~_|$]
This will match full addresses (http://...) and addresses that start with www. or ftp. - you're out of luck with addresses like ars.userfriendly.org...
This thread is old as the hills, but I came across it while working on my own problem: That is, convert any urls into links, but leave alone any that are already within anchor tags. After a while, this is what has popped out:
(?!(?!.*?<a)[^<]*<\/a>)(?:(?:https?|ftp|file)://|www\.|ftp\.)[-A-Z0-9+&#/%=~_|$?!:,.]*[A-Z0-9+&#/%=~_|$]
With the following input:
http://www.google.com
http://google.com
www.google.com
<p>http://www.google.com<p>
this is a normal sentence. let's hope it's ok.
www.google.com
This is the output of a preg_replace:
http://www.google.com
http://google.com
www.google.com
<p>http://www.google.com<p>
this is a normal sentence. let's hope it's ok.
www.google.com
Just wanted to contribute back to save somebody some time.
I made a slight modification to the Regex contained in the original answer:
(?<![.*">])\b(?:(?:https?|ftp|file)://|[a-z]\.)[-A-Z0-9+&#/%=~_|$?!:,.]*[A-Z0-9+&#/%=~_|$]
which allows for more subdomains, and also runs a more full check on tags. To apply this to PHP's preg replace, you can use:
$convertedText = preg_replace( '#(?<![.*">])\b(?:(?:https?|ftp|file)://|[a-z]\.)[-A-Z0-9+&#/%=~_|$?!:,.]*[A-Z0-9+&#/%=~_|$]#i', '\0', $originalText );
Note, I removed # from the regex, in order to use it as a delimiter for preg_replace. It's pretty rare that # would be used in a URL anyway.
Obviously, you can modify the replacement text, and remove target="_blank", or add rel="nofollow" etc.
Hope that helps.
To skip existing ones just use a look-behind - add (?<!href=") to the beginning of your regular expression, so it would look something like this:
/(?<!href=")http://\S*/
Obviously this isn't a complete solution for finding all types of URLs, but this should solve your problem of messing with existing ones.
if (preg_match('/\b(?<!=")(https?|ftp|file):\/\/[-A-Z0-9+&##\/%?=~_|!:,.;]*[A-Z0-9+&##\/%=~_|](?!.*".*>)(?!.*<\/a>)/i', $subject)) {
# Successful match
} else {
# Match attempt failed
}
Shameless plug: You can look here (regular expression replace a word by a link) for inspiration.
The question asked to replace some word with a certain link, unless there already was a link. So the problem you have is more or less the same thing.
All you need is a regex that matches a URL (in place of the word). The simplest assumption would be like this: An URL (optionally) starts with "http://", "ftp://" or "mailto:" and lasts as long as there are no white-space characters, line breaks, tag brackets or quotes).
Beware, long regex ahead. Apply case-insensitively.
(href\s*=\s*['"]?)?((?:http://|ftp://|mailto:)?[^.,<>"'\s\r\n\t]+(?:\.(?![.<>"'\s\r\n])[^.,!<>"'\s\r\n\t]+)+)
Be warned - this will also match URLs that are technically invalid, and it will recognize things.formatted.like.this as an URL. It depends on your data if it is too insensitive. I can fine-tune the regex if you have examples where it returns false positives.
The regex will produce two match groups. Group 2 will contain the matched thing, which is most likely an URL. Group 1 will either contain an empty string or an 'href="'. You can use it as an indicator that this match occurred inside a href parameter of an existing link and you don't have to do touch that one.
Once you confirm that this does the right thing for you most of the time (with user supplied data, you can never be sure), you can do the rest in two steps, as I proposed it in the other question:
Make a link around every URL there is (unless there is something in match group 1!) This will produce double nested <a> tags for things that have a link already.
Scan for incorrectly nested <a> tags, removing the innermost one

Categories