Skrypty shell oraz wysyłanie maili w CakePHP

Na praktycznym przykładzie pokażę jak tworzyć w CakePHP skrypty shella oraz pokażę obsługę wysyłania maili. Naszym zadaniem jest stworzenie systemu powiadomień mailowych do zadań (wydarzeń), które są przechowywane w bazie danych w tabeli tasks. Kod sql tworzący tę tabelę poniżej.

CREATE TABLE `tasks` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `title` varchar(255) COLLATE utf8_polish_ci NOT NULL,
 `description` varchar(255) COLLATE utf8_polish_ci DEFAULT NULL,
 `date` date NOT NULL,
 `status` tinyint(4) DEFAULT NULL,
 `remind` text COLLATE utf8_polish_ci,
 `remindemail` varchar(255) COLLATE utf8_polish_ci DEFAULT NULL,
 `created` datetime NOT NULL,
 PRIMARY KEY (`id`)
)
oraz dodający do tabeli kilka wierszy:
INSERT INTO `tasks` (`id`, `title`, `description`, `date`, `status` `remind`, `remindemail`, `user_id`, `created`) VALUES
(2, 'qewtrwet', '', '2014-10-20', 0, NULL, '', 1, '2014-10-06 14:31:37'),
(3, 'qrwterytew', 'sgdsfg ghdfhf', '2014-10-24', 0, NULL, '', 1, '2014-10-07 09:21:22'),
(5, 'Faktura dla firmy ABC', 'Wystawić fakturę', '2014-10-31', 0, '["1"]', '[email protected]', 1, '2014-10-07 14:31:51'),
(6, 'Spotkanie', 'Spotkanie organizacyjne', '2014-10-31', 0, '["1","2"]', '[email protected]', 1, '2014-10-07 14:39:48'),
(7, 'Raport', 'Wygenerować raport', '2014-10-31', 0, '["1","2"]', '[email protected]', 1, '2014-10-07 15:23:47'),
(8, 'Wydrukować dokumenty', 'Wydrukować dokumenty dla firmy ABC', '2014-10-31', 0, NULL, '', 1, '2014-10-07 15:24:54');
Oczywiście musisz zmodyfikować pole date na daty przyszłe oraz pole remindemail na swój adres email. Informacja o tym czy wysyłać przypomnienie dla zadania jest zapisana w polu remind (jako tekst json). NULL oznacza brak przypomnienia. Na przykład jeśli wartość pola to ["1"] oznacza to, że musimy wysłać powiadomienie dzień przed zadaniem, jeśli ["1","2","3"] - dzień, tydzień oraz dwa tygodnie przed zadaniem. Pole status zawiera informację czy zadanie zostało zrealizowane (wartość 1) czy nie (wartość 0). Powiadomienia będziemy wysyłać tylko do zadań niezrealizowanych. W polu remindemail jest zapisany email, na który ma być wysłane powiadomienie. Należy utworzyć (wypiec) odpowiedni model (Task), kontroler (TasksController) i widok dla tej tabeli.

Skrypty shell w CakePHP

Skrypt shella jest idealnym rozwiązaniem naszego zadania. Taki skrypt wywołuje się z konsoli. Można go wykonać również jako zadanie crona (w naszym przypadku tak trzeba będzie zrobić, aby wywoływać skrypt codziennie w celu sprawdzenia czy są zbliżające się wydarzenia i ewentualnie wysłać maila z powiadomieniem). Skrypty shell umieszcza się w katalogu app/Console/Command. Stwórzmy w tym katalogu plik ReminderShell.php o następującej zawartości.
class ReminderShell extends AppShell {
	public function main() {
        $this->out('Witaj');
    }
}
Jest to bardzo prosty skrypt, który wypisuje na ekranie tekst "Witaj". Wywołujemy go z konsoli. Na Windowsie najpierw przechodzimy w konsoli (cmd) do katalogu z php i następnie wywołujemy skrypt
php.exe /sciezka/do/app/Console/cake.php reminder
Na Linuxie wpisanie w terminalu /sciezka/do/app/Console/cake reminder powinno wystarczyć. Była to tylko prosta demonstracja działanie skryptów shella. Teraz napiszemy właściwy "przypominacz". Usuń z klasy ReminderShell metodę main(). Dodaj natomiast co następuje.
class ReminderShell extends AppShell {
	public $uses = array('Task');
	
	public function remind() {
	
	}
}
Pusta metoda remind (tutaj będzie właściwa część skryptu). Zapis public $uses = array('Task') oznacza, że chcemy w naszym shell skrypcie korzystać z modelu Task (robić zapytania). Jeśli chcielibyśmy użyć innych modeli trzeba by je było podać po przecinku. Pełny kod skryptu poniżej.
class ReminderShell extends AppShell {
	public $uses = array('Task');
	
	public function remind() {
		App::uses('CakeEmail', 'Network/Email');
		$Email = new CakeEmail('smtp');
		$all_tasks = $this->Task->find('all', array('conditions' => array('date >= NOW()', 'status' => 0)));
		
		foreach($all_tasks as $task) {
			if(!empty($task['Task']['remind'])) {
				if(!empty($task['Task']['remindemail'])) {
					$remind_email = $task['Task']['remindemail'];
				} else {
					$remind_email = null;
				}
				$task['Task']['remind'] = json_decode($task['Task']['remind']);
				foreach($task['Task']['remind'] as $remind_option) {
					$current_date = mktime(0,0,0,date('n'),date('j'),date('Y'),0);
					if($remind_option==1) {//Dzień przed terminem
						if($current_date+DAY==strtotime($task['Task']['date'])) {
							if(!empty($remind_secondary_email)) {
								$Email->to($remind_email);
							}
							$Email->subject('Przypomnienie o zadaniu: '.$task['Task']['title']);
							$message = 'Przypomnienie o zadaniu na jutro ('.$task['Task']['date'].'): 
										'.$task['Task']['title'].'
										
										'.$task['Task']['description'];
							$Email->send($message);
						}
					}
					if($remind_option==2) {//Tydzień przed terminem
						if($current_date+7*DAY==strtotime($task['Task']['date'])) {
							if(!empty($remind_secondary_email)) {
								$Email->to($remind_email);
							}
							$Email->subject('Przypomnienie o zadaniu: '.$task['Task']['title']);
							$message = 'Przypomnienie o zadaniu za tydzień ('.$task['Task']['date'].'):
										'.$task['Task']['title'].'
							
										'.$task['Task']['description'];
							$Email->send($message);
						}
					}
					if($remind_option==3) {//Dwa tygodnie przed terminem
						if($current_date+14*DAY==strtotime($task['Task']['date'])) {
							if(!empty($remind_secondary_email)) {
								$Email->to($remind_email);
							}
							$Email->subject('Przypomnienie o zadaniu: '.$task['Task']['title']);
							$message = 'Przypomnienie o zadaniu za 2 tygodnie ('.$task['Task']['date'].'):
										'.$task['Task']['title'].'
				
										'.$task['Task']['description'];
							$Email->send($message);
						}
					}
				}
			}
		}
	}
}
Omówię teraz jego działanie. Wszystko dzieje się w metodzie remind. Obsługa maili Dzięki linijce
App::uses('CakeEmail', 'Network/Email'); 
informujemy, że chcemy korzystać z klasy CakeEmail Caka. Następnie wywołujemy
$Email = new CakeEmail('smtp');
Przekazany parametr 'smtp' oznacza, że chcemy wykorzystać konfigurację smtp z pliku konfiguracyjnego maili czyli app/Config/email.php. Jeśli jeszcze nie masz takiego pliku skopiuj plik email.php.default, zmień mu nazwę na email.php i zmień konfigurację na własną. Przykładowa konfiguracja smtp:
public $smtp = array(
	'transport' => 'Smtp',
	'from' => array('[email protected]' => 'Terminarz'),
	'host' => 'costam.pl',
	'port' => 587,
	'username' => '[email protected]',
	'password' => 'haslo'
);
Kolejna linijka w skrypcie shella to
$all_tasks = $this->Task->find('all', array('conditions' => array('date >= NOW()', 'status' => 0)));
Pobiera on wszystkie zadania z datą dzisiejszą i późniejszą oraz ze statusem 0 czyli niezrealizowane. Następnie przechodzimy przez tablicę wyników i sprawdzamy czy kolumna remind jest niepusta. Jeśli jest niepusta to do zmiennej $remind_email zapisujemy email na jaki mamy wysłać powiadomienie. Dekodujemy opcje powiadomień z jsona na tablicę phpową i przechodzimy przez nią sprawdzając wszystkie możliwe opcje (1, 2 i 3). Omówię tylko fragment dotyczący opcji 1 - powiadomienie dzień przed, reszta jest analogiczna.
if($current_date+DAY==strtotime($task['Task']['date'])) {
	if(!empty($remind_secondary_email)) {
		$Email->to($remind_email);
	}
	$Email->subject('Przypomnienie o zadaniu: '.$task['Task']['title']);
	$message = 'Przypomnienie o zadaniu na jutro ('.$task['Task']['date'].'): 
				'.$task['Task']['title'].'
				
				'.$task['Task']['description'];
	$Email->send($message);
}
Porównujemy datę zadania zapisaną w bazie z dzisiejszą datą + 1 dzień i jeśli jest zgodna wysyłamy email z powiadomieniem. Adres email na jaki ma być wysłana wiadomość ustawiamy za pomocą
$Email->to($remind_email);
Temat określa się metodą subject. Natomiast za samo wysłanie odpowiada metoda send. Jako parametr przekazujemy jej treść maila. Skrypt shella wywołujemy pisząc w konsoli: na Windowsie najpierw przechodzimy w konsoli do katalogu z php i następnie wywołujemy skrypt
php.exe /sciezka/do/app/Console/cake.php reminder remind
Na Linuxie:  /sciezka/do/app/Console/cake reminder remind.
Skrypt taki aby spełniał nasze założenia powinien być uruchamiany codziennie za pomocą crona.