1

I have a channel set up with front end submitted entries. I want a way for users to be able to subscribe themselves to receive an update whenever the entry (or it's related entries) are updated. I was thinking one way to so this so that it can be managed on the front end would be to add a Users field to the entry and let logged in users add themselves to it.

I have a simple notification service that I am using in a custom plugin that is sending emails to entry authors, but I'm not sure how, or what would be the best way to send emails to multiple users as per the above requirement.

(p.s I'm new and limited in my plugin knowledge!)

Any thoughts or suggestions.

Thanks!

  • 1
    There is an easy and a "more complex" way to achieve this, it depends on how many people will subscribe in one entry. You could just hook yourself on the entry.onUpdate method and send a mail to all users. That would be only a few lines (I'll write them) but if you think there could be 20+ people in one entry with frequent updates you have to collect all mails in a table and create a cron job to send them. It's easy but requires much more code (when you use craft 2,with craft 3 it's less). – Robin Schambach Oct 22 '17 at 13:40
  • Ah ok. That makes sense. I think the entries are quite likely to have 20+ users. That was my concern in whether that field starts getting large. It’s essentially a comments section that’s set up, so the notifications would be for when people have commented. – shorn Oct 22 '17 at 13:56
  • It's not about a field getting large, you can relate as many people as you want with an entry. There is no restriction in the database since every user has its own row in the relations table. It's more about the performance. You can't send all mails when one user submits something because this one user has to wait for all mails to be send until he gets a response. So if there are 100+ people the request could be rejected due to server time restrictions. Do you use a custom controller/service to save entries? I'll provide a solution after your answer – Robin Schambach Oct 22 '17 at 14:06
  • Indeed, that’s what I meant, not so much the amount of users, but the effect that has on running something like notification mails. No custom controller/service. Just standard entries. – shorn Oct 22 '17 at 14:52
3

If you have questions or other issues feel free to ask.
Please don't be confused, my Plugins handle/name is "Test" because I only use it for these purposes.. So whenever you see the name "Test" you know you have to rename it

This is your plugins init function

/**
 * Called after the plugin class is instantiated; do any one-time initialization here such as hooks and events:
 * @return mixed
 */
public function init()
{
    parent::init();
    craft()->on('entries.onBeforeSaveEntry', function(Event $event)
    {
        // trigger only when entries are changed not when created
        if(!$event->params['isNewEntry']){
            /** @var EntryModel $entry */
            $entry = $event->params['entry'];
            // create your logic to check if it is the correct entry...
            // you should check for your section here otherwise you'll send a mail for all updates^^
            // I just used the handle pages in my test system
            if($entry->getSection()->handle === "pages"){
                // next: fetch all user related to the entry...
                // since I don't know your entire fields, you have to implement the logic
                // to fetch users from entries that are related to the entry yourself
                // if you need help just ask
                $criteria = craft()->elements->getCriteria(ElementType::User);
                $criteria->relatedTo($entry);

                /** @var UserModel $user */
                foreach( $criteria as $user){
                    // create a new entry in the database
                    $email = new Test_EmailRecord();
                    $email->entryId = $entry->id;
                    $email->email = $user->email;
                    $email->save();
                }
            }
        }

    });
}

And you have to create the following record

namespace Craft;


class Test_EmailRecord extends BaseRecord
{
    public function getTableName()
    {
        return 'cron_mails';
    }

    protected function defineAttributes()
    {
        return array(
            // you could store the user Id here too, but this way
            // you can send the mail to non users too
            'email' => AttributeType::String,
            'entryId' => array(AttributeType::Number),
        );
    }

    // you could - if you want - create relations here..
    // https://craftcms.com/docs/plugins/records#defining-relations
    // but since it's only an example and not needed I skipped that part
}

And you need this in your controller

 /**
 * Cronjob to send emails... you can activate it every X hours
 * or once per day or whatever you want
 * when this function is trigger a specific number of mails will be send
 * their entries in the database will be deleted until there are no rows in the
 * table
 */
public function actionSendMails()
{
    // fetch mails with limit.. insert your limit here.. I would suggest to fetch 20-50
    // entries per trigger depending on how often it should run
    $emails = Test_EmailRecord::model()->findAllBySql('select * from craft_cron_mails LIMIT 1');

    if (is_array($emails) && count($emails)) {
        foreach ($emails as $email) {
            /** @var UserModel $user */
            $user = craft()->users->getUserByEmail($email->email);

            // do your logic if no user is found..
            // of course you could store the user Id rather than the email in the database table
            // but this way you can send the mail to non users too if you like
            if (!$user) {
                // you can just remove that part
                // I just wanted to show you could create an array with defaults here
                // if the user is not registered
                $user = array(/* fill some placeholders for your twig template */);
            }

            // prepare templates, you can address each user personally with a hello Mr {{ user.name }}
            // the entry {{ entry.title }} was edited and such... 
            $message = craft()->templates->render('path/to/template.twig', array(
                'user' => $user,
                'entry' => craft()->elements->getElementById($email->entryId)
            ));
            if ($this->sendMail($message, "The post was edited", $user->email)) {
                //remove the column if the mail was send
                $email->delete();
            }

        }
    }
}

/**
 * This function sends the mail, it usually belongs to a service
 * but to make it easier I'll just leave it here
 *
 * @param $message
 * @param $subject
 * @param null $mail
 * @return bool
 */
public function sendMail($message, $subject, $mail = null)
{
    $errors = false;
    $adminEmail = craft()->systemSettings->getSetting('email', 'emailAddress');

    $email = new EmailModel();

    $email->fromEmail = $adminEmail;
    $email->replyTo = $adminEmail;
    $email->sender = $adminEmail;
    $email->fromName = craft()->getSiteName();
    $email->toEmail = $mail;
    $email->subject = $subject;
    $email->body = $message;

    if (!craft()->email->sendEmail($email)) {
        $errors = true;
    }

    return $errors;
}
  • Wow, this looks super! Going to take me a short while to digest this and test it. Your help is very much appreciated! I’ll respond once I’ve had a play! Thanks – shorn Oct 22 '17 at 15:16
  • Just looking at tackling this now and I think there might be a better way to do this. As I mentioned, this is for a basic commenting system. One way I was thinking of doing it would be to get get a notification only if someone replies directly to you, rather than setting up a users field. So, the user woudl hit reply under another comment and I can pass that users id to the new comment that gets posted and send an email to that user. That way, only 1 additional email is getting generated. Would save setting up and record and cron job. Make sense? – shorn Oct 26 '17 at 19:18
  • It's totally up to you. If you think it might be enough to only notify one user you can do that as well. Some systems do it this way, some system inform everyone. If you only want to inform one user you can just render the email message and send it after your saveEntry from before is successfull – Robin Schambach Oct 26 '17 at 19:25
  • Yea, I think this might be best. I've been in some threads where I get notified of every message and soon wish I hadn't :) – shorn Oct 26 '17 at 19:29
  • Okay, then you can at least use the Sendmail function. Glad I could help you anyway. – Robin Schambach Oct 26 '17 at 19:32

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.