@AlexWright thank you so much for this post! It helped me make the IDENTICAL fURLs I had customized back in the day on 3.4.x to be the same in 4.4!
BTW my default app will be Pages.
What you did not mention that you have to translate is
"topLevel": "forums",
to your language. Thus I was able to translate this toplevel fURL and translated the rest in ACP and in the end I got results such as:
http://website.com/forum/f1/forum-category-name/ http://website.com/forum/t46405/topic-name/
Basically you have to do this for every app you want to translate, go to its folder, find furl.json file and edit it.
Today IPS support told me this can't be done, but there it is. 🙂
Hey team, I had an idea for an application...
I would love for when a member logs in for the first time, or something is launched on your forum, to visually show some cues - literally pointing to important parts of the website.
For example, a member logs in, and most of the screen darkens except for the "Create" button at the top and a message pops up saying "This is how to create a new topic."
Or "This is where to view your profile" or "This is where to leave a comment," that kind of thing. They can click "Got it" and the mini tutorial disappears.
It would be useful for current members when rolling out a new feature, too.
I find that new members are overwhelmed with the amount that can be done. I want to make it very easy and clear for them.
Thoughts?
Great Plugin, first of all 🙂 I'm using the opt out option. Is it possible to add a city search option? From example, I'm from city "A". I see people near me. I would like to see people near another city ("B" , "C", "D" ) without change my location. "People Near Me" Site:
And a second request. To add compatibility with the "Friends" application made by @onlyME. It's about the "People you may know" option. It does not consider "Friends" and shows in the "People you may know" section those Users who are already Friends.
It’s already a feature added in last version: https://invisioncommunity.com/forums/topic/456245-books/?do=findComment&comment=2819792
There’s a Buy This Book button pointing to a Amazon store with the user or site Affiliate ID.
You can see an image here: https://invisioncommunity.com/forums/topic/456245-books/?do=findComment&comment=2819169 linking to a user Affiliate ID.
I don't know if its possible but if you add one more field in book description with a link to amazon store where book can be bought with a referral link would be beneficial to the forum owner 🙂
So here's my code… It comes with a lot of caveats… Think of it as starting point so you or your developer doesn't have to start from scratch. I'm not the best PHP programmer, so my code may not be quite as beautiful as yours, but it's better than nothing. Also, this code calls MailBouncer, so you must have an active license for that. You can put the the file anywhere and name it anything (ending in .php). The code will almost certainly need to be tweaked. For example, my mail server is running SendMail. If your mail server is running something different the bulk of your messages may have different formatting.
Here's what it does… Logs into your email account and scans through all the messages from postmaster@ and MAILER-DAEMON@. You can login with IMAP or POP w/ or w/o SSL. Only IMAP w/o SSL has been tested, but the code is there for the others. After processing the entire mailbox, when you repeat the process you'll probably want to do just examine the most recent period. To do that add ?days=X to the end of the URL where X is the number of days you want to examine. X can be a decimal number.
When it scans it classifies things as 4 types:
Indeterminate
Reputation problem
Hard bounce
Soft bounce
You won't see detailed information about hard and soft bounces, but you will if there's a reputation problem or the outcome was indeterminate.
Reputation problems typically mean that your server got on a blacklist. You'll want to rectify this ASAP. Reputation problems are handled as soft bounces, since they're not the fault of the member.
Indeterminate issues are when you'll probably need to tweak the code to handle the case (typically different error formatting). You can add trigger phrases to $softphrases, $hardphrases and $reputationphrases. If it's not parsing the email address that's bouncing, that's a little trickier. Or you can just manually go into the IP board admin area and do manually what Mail Bouncer does.
It is possible to change up the code to not scan a mailbox and just handle a long list of emails you've manually put together – just replace everything inside
if (!$mbox = imap_open($authhost, $user, $pw)){ } else {}
with something that populates the $hardfails and $softfails arrays.
At the end it does one other thing – it queries the database and finds everyone who has been banned for 8 weeks or longer and processes their email addresses as hard bounces. These people are probably mad at you and don't want your emails, and they're likely to mark your emails as spam just to spite you. So it's best to just treat the email as bad and if/when the member returns they can revalidate their email address. The SQL query is shown. You'll need to rewrite the actual pull from your IP.Board database. I use Fat Free Framework, so that's the syntax you'll see. To turn off handling of long bans permanently just comment out or delete the code. But you can also add lb=off to the end of the URL to not to it on a particular run of the script.
With all of that said, here's the code…
<?php
//I USE FAT FREE FRAMEWORK, SO THERE'S AT LEAST ONE CASE WHERE YOU NEED TO REPLACE F3 CODE WITH YOUR OWN CODE
$user = 'your_mail_username';
$pw = 'your_mail_pw';
$mailserver = 'localhost';
\define('REPORT_EXCEPTIONS', TRUE);
$_SERVER['SCRIPT_FILENAME'] = __FILE__;
require_once '/path/to/init.php'; //PATH TO THE INIT.PHP FILE FOR YOUR IP.BOARD INSTALL
header('Content-Encoding: none');
ob_implicit_flush(true);
ob_end_flush();
set_time_limit(60*180);
echo str_repeat(' ',1024*64.1);
$hardfails = array();
$softfails = array();
//THE FOLLOWING ARRAYS OF PHRASES DETERMINE THE TYPE OF FAILURE, EDIT THEM AS NEEDED
$softphrases = array();
$softphrases[] = '(reason: 552'; //552 = Exceeded storage allocation
$hardphrases[] = '<<< 552';
$softphrases[] = ' over quota';
$softphrases[] = ' inbox is full';
$softphrases[] = ' mailbox is full';
$softphrases[] = ' mailbox full';
$softphrases[] = 'Mail quota exceeded';
$softphrases[] = 'is over quota';
$hardphrases = array();
$hardphrases[] = '550 5.1.1';
$hardphrases[] = '550-5.1.1';
$hardphrases[] = '<<< 550'; //550 = Non-existent email address
$softphrases[] = '(reason: 550';
$hardphrases[] = 'Status code: 550';
$hardphrases[] = ' mailbox unavailable';
$hardphrases[] = ' deactivated mailbox';
$hardphrases[] = ' Bad destination mailbox address';
$hardphrases[] = ' no valid recipients';
$hardphrases[] = 'Not a valid recipient';
$hardphrases[] = 'User unknown';
$hardphrases[] = ' Address does not exist';
$hardphrases[] = ' mailbox is disabled';
$hardphrases[] = ' user doesn\'t have a yahoo';
$hardphrases[] = 'is a deactivated mailbox';
$reputationphrases = array(); //things that indicate they just don't like/trust the email
//this is a problem if you sent the email, not a problem if some spammer sent the email (pretending to be you)
$reputationphrases[] = 'Relay access denied'; //https://serversitters.com/how-to-correct-554-5-7-1-relay-access-denied-email-errors-and-prevent-them-in-the-future.html
$reputationphrases[] = ' blocked using ';
//ONE OF THE FOLLOWING 4 LINES SHOULD BE UNCOMMENTED DEPENDING ON WHAT TYPE OF CONNECTION YOU WANT TO MAKE TO YOUR MAIL SERVER
//$authhost="{".$mailserver.":995/pop3/ssl/novalidate-cert}"; //POP w/o SSL
//$authhost="{".$mailserver.":110/pop3/notls}"; //POP w/ SSL
$authhost="{".$mailserver.":993/imap/ssl/novalidate-cert}"; //IMAP w/o SSL (this is the only one that's been tested)
//$authhost="{".$mailserver.":143/imap/notls}"; //IMAP w/ SSL
echo '<!DOCTYPE html><html lang="en-US" dir="ltr"><head><meta charset="utf-8"></head><body>';
if (!$mbox = imap_open($authhost, $user, $pw)){
echo "<p>Could not connect to mail server.</p>";
} else {
echo "<p>Connected to mail server.</p>";
$totalrows = imap_num_msg($mbox);
echo "<p>Found $totalrows messages</p><ul>";
$result = imap_fetch_overview($mbox, "1:$totalrows", 0);
foreach ($result as $overview) {
$body = imap_fetchbody($mbox, $overview->msgno, "1");
$datetime = strtotime($overview->date);
date_default_timezone_set("UTC");
if (stripos($body, 'https://web.de/email/senderguidelines') !== false && stripos($body, 'cs2172.mojohost.com') !== false) {
//ignore these, problem was resolved
} else if ($_GET['days'] > 0 && $datetime < time() - ($_GET['days'] * 86400)){
//do nothing, it doesn't meet the cutoff
// echo "<li>Skipping: $datetime < ".(time() - ($f3->get('days') * 86400))."</li>";
} else if ((stripos($overview->from, 'Mailer-Daemon@') !== false || stripos($overview->from, 'postmaster@') !== false )
&&
((strpos($body, '----- The following addresses had permanent fatal errors -----') !== false && strpos($body, '(reason: ') !== false) ||
(stripos($body, 'Final-Recipient: ') !== false) ||
(stripos($body, 'message couldn\'t be delivered to ') !== false) ||
(stripos($body, '> is a deactivated mailbox') !== false) ||
(strpos($body, ' to these recipients or groups:') !== false && strpos($body, 'The recipient\'s mailbox is full') !== false))) {
//PARSE THE EMAIL ADDRESS THAT'S HAVING A PROBLEM
$bademail = '';
if (strpos($body, '----- The following addresses had permanent fatal errors -----') !== false) {
$pos1 = strpos($body, '----- The following addresses had permanent fatal errors -----');
$pos1 = $pos1 + strlen('----- The following addresses had permanent fatal errors -----');
$pos2 = strpos($body, '(reason: ', $pos1);
if ($pos2 > 0) {
$bademail = str_replace('<', '', str_replace('>', '', trim(substr($body, $pos1, $pos2 - $pos1))));
}
} else if (stripos($body, 'Final-Recipient: ') !== false){
//The syntax of the field is as follows:
// "Final-Recipient" ":" address-type ";" generic-address
// i.e. Final-Recipient: rfc822; skijanje-zg@net.hr
$pos1 = strpos($body, 'Final-Recipient: ');
$pos1 = $pos1 + strlen('Final-Recipient: ');
$pos2 = strpos($body, "\r", $pos1);
if (strpos($body, "\n", $pos1) < $pos2){
$pos2 = strpos($body, "\n", $pos1);
}
if ($pos2 > 0) {
$bademail = substr($body, $pos1, $pos2 - $pos1);
$pos3 = strpos($bademail, ';');
if ($pos3 !== false){
$bademail = substr($bademail, $pos3 + 1);
}
$bademail = trim($bademail);
}
} else if (strpos($body, ' to these recipients or groups:') !== false && strpos($body, 'The recipient\'s mailbox is full') !== false){
$pos1 = strpos($body, ' to these recipients or groups:');
$pos1 = $pos1 + strlen(' to these recipients or groups:');
$pos2 = strpos($body, 'The recipient\'s mailbox is full', $pos1);
if ($pos2 > 0) {
$bademail = trim(substr($body, $pos1, $pos2 - $pos1));
if (strpos($bademail, '<') !== false && strpos($bademail, '>') !== false){
$bademail = substr($bademail, strpos($bademail, '<') + 1);
$bademail = substr($bademail, 0, strpos($bademail, '>'));
}
}
} else if(stripos($body, '> is a deactivated mailbox') !== false){
$bademail = substr($body, 0, stripos($body, '> is a deactivated mailbox'));
$bademail = substr($bademail, strrpos($bademail, '<') + 1);
} else if(stripos($body, 'message couldn\'t be delivered to ') !== false){
$bademail = substr($body, stripos($body, 'message couldn\'t be delivered to ') + strlen('message couldn\'t be delivered to '));
$bademail = substr($bademail, 0, strpos($bademail, '. '));
}
//CLEAN UP THE EMAIL ADDRESS
if (stripos($bademail, 'mailto:') === 0){
$bademail = substr($bademail, strlen('mailto:'));
}
if (!filter_var($bademail, FILTER_VALIDATE_EMAIL)) {
echo "<li><b>ERROR: PARSED THE EMAIL $bademail BUT IT IS NOT A VALID EMAIL ADDRESS</b></li>";
$bademail = '';
}
//EVALUATE DIFFERENT TYPES OF PROBLEMS
$result = 'Indeterminate';
if ($result == 'Indeterminate'){ //reputation problems
foreach($reputationphrases as $phrase){
if ($result == 'Indeterminate' && stripos($body, $phrase) !== false) {
$result = 'Reputation Problem';
}
}
}
if ($result == 'Indeterminate'){ //hard bounces
foreach($hardphrases as $phrase){
if ($result == 'Indeterminate' && stripos($body, $phrase) !== false) {
$result = 'hard';
}
}
}
if ($result == 'Indeterminate'){ //soft bounces
foreach($softphrases as $phrase){
if ($result == 'Indeterminate' && stripos($body, $phrase) !== false) {
$result = 'soft';
}
}
}
if ($result == 'Indeterminate' || $bademail == '') {
echo "<li>{$overview->msgno}<ul>";
echo "<li>Sent: {$overview->date}</li><li>From: " . htmlspecialchars($overview->from) . "</li><li> Subject: {$overview->subject}</li>";
echo "<li><pre>" . htmlspecialchars(substr($body, 0, 10240)) . "</pre></li>";
echo "<li><b>Bad Email: $bademail</b></li>";
echo "<li>Date/Time: $datetime</li>";
echo "<li><b>Result: $result</b></li>";
echo "</ul></li>";
} elseif ($result == 'Reputation Problem') {
echo "<li>{$overview->msgno}<ul>";
echo "<li>Sent: {$overview->date}</li><li>From: " . htmlspecialchars($overview->from) . "</li><li> Subject: {$overview->subject}</li>";
echo "<li><pre>" . htmlspecialchars(substr($body, 0, 10240)) . "</pre></li>";
echo "<li><b>Bad Email: $bademail</b></li>";
echo "<li>Date/Time: $datetime</li>";
echo "<li><b>Result: Reputation Problem (handled as a soft bounce)</b></li>";
echo "</ul></li>";
$exists = false;
for($i = 0; $i < count($softfails); $i++){
list($temp_email, $temp_dt) = explode(',', $softfails[$i]);
if ($temp_email == $bademail){
$exists = true;
if ($temp_dt < $datetime){
$softfails[$i] = "$bademail,$datetime"; //update date time
}
}
}
if ($exists === false){
$softfails[] = "$bademail,$datetime";
}
} elseif ($result == 'hard' && array_search("$bademail,$datetime", $hardfails) === false) {
$exists = false;
for($i = 0; $i < count($hardfails); $i++){
list($temp_email, $temp_dt) = explode(',', $hardfails[$i]);
if ($temp_email == $bademail){
$exists = true;
if ($temp_dt < $datetime){
$hardfails[$i] = "$bademail,$datetime"; //update date time
}
}
}
if ($exists === false){
$hardfails[] = "$bademail,$datetime";
}
} elseif ($result == 'soft' && array_search("$bademail,$datetime", $softfails) === false) {
$exists = false;
for($i = 0; $i < count($softfails); $i++){
list($temp_email, $temp_dt) = explode(',', $softfails[$i]);
if ($temp_email == $bademail){
$exists = true;
if ($temp_dt < $datetime){
$softfails[$i] = "$bademail,$datetime"; //update date time
}
}
}
if ($exists === false){
$softfails[] = "$bademail,$datetime";
}
}
} else if (stripos($overview->subject, "Mail delivery failed") !== false) {
echo "<li>{$overview->msgno}<ul>";
echo "<li>Sent: {$overview->date}</li><li>From: " . htmlspecialchars($overview->from) . "</li><li> Subject: {$overview->subject}</li>";
echo "<li><pre>" . htmlspecialchars(substr($body, 0, 10240)) . "</pre></li>";
echo "</ul></li>";
} else if (stripos($overview->subject, "Delivery Status Notification (Failure)") !== false) { //hotmale sends ones like this…
echo "<li>{$overview->msgno}<ul>";
echo "<li>Sent: {$overview->date}</li><li>From: " . htmlspecialchars($overview->from) . "</li><li> Subject: {$overview->subject}</li>";
echo "<li><pre>" . htmlspecialchars(substr($body, 0, 10240)) . "</pre></li>";
echo "</ul></li>";
} else if (stripos($overview->subject, "Undeliverable: ") !== false) { //outlook sends ones like this…
echo "<li>{$overview->msgno}<ul>";
echo "<li>Sent: {$overview->date}</li><li>From: " . htmlspecialchars($overview->from) . "</li><li> Subject: {$overview->subject}</li>";
echo "<li><pre>" . htmlspecialchars(substr($body, 0, 10240)) . "</pre></li>";
echo "</ul></li>";
} else if (stripos($overview->from, 'postmaster@') === 0) { //catch all
echo "<li>{$overview->msgno}<ul>";
echo "<li>Sent: {$overview->date}</li><li>From: " . htmlspecialchars($overview->from) . "</li><li> Subject: {$overview->subject}</li>";
echo "<li><pre>" . htmlspecialchars(substr($body, 0, 10240)) . "</pre></li>";
echo "</ul></li>";
} else if (stripos($overview->from, 'Mailer-Daemon@') === 0) { //catch all
echo "<li>{$overview->msgno}<ul>";
echo "<li>Sent: {$overview->date}</li><li>From: " . htmlspecialchars($overview->from) . "</li><li> Subject: {$overview->subject}</li>";
echo "<li><pre>" . htmlspecialchars(substr($body, 0, 10240)) . "</pre></li>";
echo "</ul></li>";
}
} //end foreach
echo "</ul>";
imap_close($mbox);
} //connected
ob_flush();
//NOW HAVE ARRAYS OF HARD AND SOFT FAILS, NEED TO PROCESS THEM…
echo "<p>Please note: 'Email address not found' can mean two things – 1) that you have a problem because a member record could not be found, or 2) that the member has already corrected the problem. If you see just a few messages like that, it usually indicates that the member already corrected the problem.</p>";
echo "</ul><h2>".count($softfails)." Soft Fails</h2><ul>";
asort($softfails);
foreach ($softfails as $fail){
list($email, $datetime) = explode(',', $fail);
echo "<li>$email – ";
ob_flush();
$member = \IPS\Member::load( $email, 'email' );
if( $member->member_id ) {
$member->bouncerSoftBounce( $datetime );
echo "set as soft bounce. ($datetime)";
} else {
echo "<b>EMAIL ADDRESS NOT FOUND</b>";
}
echo "</li>";
ob_flush();
}
echo "</ul>";
echo "<h2>".count($hardfails)." Hard Fails</h2><ul>";
asort($hardfails);
foreach ($hardfails as $fail){
list($email, $datetime) = explode(',', $fail);
echo "<li>$email – ";
ob_flush();
$member = \IPS\Member::load( $email, 'email' );
if( $member->member_id ) {
$member->bouncerHardBounce( $datetime );
echo "set as hard bounce. ($datetime)";
} else {
echo "<b>EMAIL ADDRESS NOT FOUND</b>";
}
echo "</li>";
}
echo "</ul>";
if ($_GET['lb'] != 'off'){
$sql = "SELECT `email` FROM `core_members` WHERE `temp_ban` = -1 OR `temp_ban` > NOW() + INTERVAL 8 WEEK";
$results = $bzDB->exec($sql); //REPLACE THIS WITH A SQL QUERY TO YOUR IP.BOARD DATABASE
echo "<h2>".count($results)." Long Bans (set as hard bounces)</h2><ul>";
foreach($results as $result){
$email = $result['email'];
echo "<li>$email – ";
ob_flush();
$member = \IPS\Member::load( $email, 'email' );
if( $member->member_id ) {
$member->bouncerHardBounce( time() );
echo "set as hard bounce.";
} else {
echo "<b>EMAIL ADDRESS NOT FOUND</b>";
}
echo "</li>";
}
}
echo "</body></html>";
?>
I have went thru the same thing. Here's my brutal way of doing it.
Download all the members list from ACP. Save it as CSV and let it go thru www.neverbounce.com it will mark invalid email boxes. It costs a bit. You can also employ other email checking services. neverbounce isnt the only one.
Then use information in this topic to disable invalid emails in bulk in your system and run queries in phpmyadmin.
I managed to filter out almost 34k worth of emails collected throughout 14 years. Took me 1,5 days of work. Also note that neverbounce has trouble telling yahoo emails if they are valid or not. I noticed that even if it marks address as okay it a lot of them might still bounce.
I completed writing my routine to parse the messages in an email inbox, figure out the type of bounce, and then submit it to Mail Bouncer. I'll post the code in a few days. I want to see if I encounter any issues with it before sharing it with folks.
It will not be a supported code and will come with no warranty. (Use at your own risk!) The error messages from mail hosts are just too unpredictable for me to want to deal with all the weird cases other people will encounter. Rather, think of it as a starting point for people with some knowledge of PHP programming – something you can tweak to suit your own needs, that means you don't have to start from scratch.
And I should mention that it can be modified pretty easily to handle lists you may have of emails that need to be disabled.
Hi
Great results, we could follow the performance with the google search console
and we noticed SEO improvements of 30% (without any other action) on sites with this plugin
>> logical : remove poor content and google will love you !
next step is : update Invision sitemap by removing the noindex topics (so that GSC doenst report errors of noindex urls in sitemap)
the plugin author replied to me about this feature : Option to remove noindex item from the sitemap and also ping the search engines after that would cost 195$.
who is ok to participate to this feature ? i go with $50, who else ?
You can remove that line is it's a CLI based script.
If it's something you're willing to maintain, you could package it in an Invision Community application and list it in the Marketplace.
@rebraf is as close as you will get to a Tier 3 IPS developer in the IPS Marketplace, so you can consider his files to be on par with core development.
I too cannot afford to move to a mail service. I have a newsletter program that processes bounces, so I am all set there. If I send a Bulk Mail through the site (which I only do once a year of so), I get about 500 bounces back each time. That's a huge pain to process those.
Having this built in would be wonderful.
@Charles @Lindy @Matt any chance this is being considered?
No this is not possible now. I think you can translate / rename "condition" with ACP translation tools.
you talk about the front end or the adverts listing in the ACP?
Yes, if you disable transactions buyer and seller will independently manage the transaction without passing through paypal (or the forum)
This is something which is currently not among the development priorities of this app.
For this request I can add a setting in the next release.
That should be it, if you don't send the notification, maybe there's a bug. I'll check and get back to you.
I'll continue to assist you for this issue with a PM.
Working on a fix.
I think I'll be able to release a new version of the app within the week.
From a member
Hello. I am noticing that the new change to modify notifications that are received from a person you follow does not seem to actually save. It seems once you exit the page you are on, it reverts back to following all content. Not sure if others have noticed, but I just thought I would let you know
confirmed. Settings aren’t sticking!
Any ideas?
EDIT: let me try clearing the cache with the support tool.