Added a number of new tests and example XML files for them.
[email-reminder.git] / EmailReminder / EventList.pm
blobd00d11ac172cab7b4077e07086dd73c201f86a1e
1 # This file is part of Email-Reminder.
3 # Email-Reminder is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License as
5 # published by the Free Software Foundation; either version 3 of the
6 # License, or (at your option) any later version.
8 # Email-Reminder is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 # General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with Email-Reminder; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16 # 02110-1301, USA.
18 package EmailReminder::EventList;
20 # Holds all user information and events.
22 # Events are stored in the proper EventStore and they can be accessed
23 # using this class. The main XML parsing and generation happen here.
25 use strict;
26 use warnings;
28 use Email::Valid;
29 use XML::DOM;
31 use EmailReminder::AnniversaryEvent;
32 use EmailReminder::AnniversaryStore;
33 use EmailReminder::BirthdayEvent;
34 use EmailReminder::BirthdayStore;
35 use EmailReminder::MonthlyEvent;
36 use EmailReminder::MonthlyStore;
37 use EmailReminder::Utils;
38 use EmailReminder::WeeklyEvent;
39 use EmailReminder::WeeklyStore;
40 use EmailReminder::YearlyEvent;
41 use EmailReminder::YearlyStore;
43 # XML tags, attributes and values
45 my $EMAIL_TAG = 'email';
46 my $EVENT_TAG = 'event';
47 my $EVENTS_TAG = 'events';
48 my $FIRST_NAME_TAG = 'first_name';
49 my $LAST_NAME_TAG = 'last_name';
50 my $USER_TAG = 'email-reminder_user';
52 my $TYPE_ATTR = 'type';
54 sub new
56 my ($class, $filename, $create) = @_;
58 my $this = { "NEXT_EVENT_ID" => 0,
59 "LOADED_FILENAME" => $filename,
60 "XML_DOC" => undef,
61 "EVENTS_NODE" => undef,
62 "STORES" => {},
65 bless $this, $class;
67 $this->process_file($create);
69 return $this;
72 sub process_file
74 my ($this, $create, $readonly) = @_;
76 # Make sure the config file exists and is readable
77 my $filename = $this->{LOADED_FILENAME};
78 unless (-e $filename)
80 if ($create)
82 open CFG, '>:utf8', "$filename";
83 print CFG '<?xml version="1.0"?><email-reminder_user/>';
84 close CFG;
86 else
88 warn "File '$filename' does not exist and it is impossible to create it.\n";
89 exit(1);
92 unless (-r $filename)
94 warn "File '$filename' exists but is not readable.\n";
95 exit(1);
97 if (!$readonly and !(-w $filename))
99 warn "WARNING: File '$filename' is not writable, your changes will be lost!\n";
102 # Start parsing the XML file
103 my $parser = XML::DOM::Parser->new();
104 my $doc;
105 eval {
106 $doc = $parser->parsefile($filename);
108 unless (defined($doc))
110 warn "File '$filename' is an invalid XML file. Fix it or delete it.\n";
111 exit(1);
113 $this->{XML_DOC} = $doc;
115 # Read user info
116 my $user = $doc->getElementsByTagName($USER_TAG)->item(0);
117 unless (defined($user))
119 warn "File '$filename' is an invalid XML file. Fix it or delete it.\n";
120 exit(1);
122 $this->{USER_NODE} = $user;
124 # Read events
125 my $events = $doc->getElementsByTagName($EVENTS_TAG)->item(0);
126 return unless defined($events);
127 $this->{EVENTS_NODE} = $events;
129 foreach my $event_node ($events->getElementsByTagName($EVENT_TAG)){
130 my $type = $event_node->getAttribute($TYPE_ATTR);
131 my $event = $this->create_event($type, $event_node);
132 next unless defined($event);
134 # Add to proper EventStore
135 my $store = $this->get_model($type);
136 $store->add_event($event);
140 sub create_event
142 my ($this, $type, $event_node) = @_;
144 if (!defined($event_node))
146 $event_node = $this->{XML_DOC}->createElement($EVENT_TAG);
147 $event_node->setAttribute($TYPE_ATTR, $type);
149 my $events = $this->{EVENTS_NODE};
150 unless (defined($events))
152 $events = $this->{XML_DOC}->createElement($EVENTS_TAG);
153 $this->{USER_NODE}->appendChild($events);
154 $this->{EVENTS_NODE} = $events;
156 $events->appendChild($event_node);
159 my $event;
160 my $id = $this->{NEXT_EVENT_ID}++;
162 if ($type eq EmailReminder::BirthdayEvent->get_type()) {
163 $event = EmailReminder::BirthdayEvent->new($event_node, $id);
165 elsif ($type eq EmailReminder::AnniversaryEvent->get_type()) {
166 $event = EmailReminder::AnniversaryEvent->new($event_node, $id);
168 elsif ($type eq EmailReminder::MonthlyEvent->get_type()) {
169 $event = EmailReminder::MonthlyEvent->new($event_node, $id);
171 elsif ($type eq EmailReminder::WeeklyEvent->get_type()) {
172 $event = EmailReminder::WeeklyEvent->new($event_node, $id);
174 elsif ($type eq EmailReminder::YearlyEvent->get_type()) {
175 $event = EmailReminder::YearlyEvent->new($event_node, $id);
178 return $event;
181 sub save
183 my $this = shift;
184 my $verbose = shift;
185 my $filename = shift || $this->{LOADED_FILENAME};
187 my $xml_document = $this->{XML_DOC}->toString();
188 print $xml_document if $verbose;
190 # Overwrite the file
191 if (open OUT, '>:utf8', "$filename")
193 print OUT $xml_document;
194 print "Sucessfully wrote reminders to '$filename'\n" if $verbose;
195 close OUT;
197 else
199 print STDERR "Cannot write to file '$filename', your changes have been lost.\n";
203 sub get_model
205 my ($this, $type) = @_;
207 my $store = $this->{STORES}->{$type};
208 unless (defined($store)) {
209 if ($type eq EmailReminder::AnniversaryEvent->get_type()) {
210 $store = EmailReminder::AnniversaryStore->new();
212 elsif ($type eq EmailReminder::BirthdayEvent->get_type()) {
213 $store = EmailReminder::BirthdayStore->new();
215 elsif ($type eq EmailReminder::MonthlyEvent->get_type()) {
216 $store = EmailReminder::MonthlyStore->new();
218 elsif ($type eq EmailReminder::WeeklyEvent->get_type()) {
219 $store = EmailReminder::WeeklyStore->new();
221 elsif ($type eq EmailReminder::YearlyEvent->get_type()) {
222 $store = EmailReminder::YearlyStore->new();
225 $store->init();
226 $this->{STORES}->{$type} = $store;
229 return $store;
232 sub get_events
234 my $this = shift;
236 my @events = ();
237 foreach my $store (values(%{$this->{STORES}})) {
238 push(@events, @{$store->get_events()});
241 return @events;
244 sub add_event
246 my ($this, $event_type) = @_;
247 my $event = $this->create_event($event_type);
248 return 0 unless defined($event);
250 $event->set_name("<New Event>");
251 $event->set_reminders([0]); # default reminder: same day
253 return $this->{STORES}->{$event_type}->add_event($event);
256 # View/edit user properties
258 sub _get_user_fname {
259 my $this = shift;
260 return EmailReminder::Utils::get_node_value($this->{USER_NODE}, $FIRST_NAME_TAG) || '';
263 sub _get_user_lname {
264 my $this = shift;
265 return EmailReminder::Utils::get_node_value($this->{USER_NODE}, $LAST_NAME_TAG) || '';
268 sub get_user_name
270 my $this = shift;
272 my $fname = $this->_get_user_fname;
273 my $lname = $this->_get_user_lname;
275 if (!$fname and !$lname) {
276 # Get name from UNIX password file
277 my @pwinfo = getpwuid($>);
278 my $fullname = $pwinfo[6];
279 $fullname =~ s/[^0-9A-Za-z_\- ]//g;
280 my @name_parts = split(/ /, $fullname);
282 $fname = $name_parts[0];
283 $lname = $name_parts[-1] if @name_parts > 1;
284 } elsif (!$fname) {
285 $fname = $lname;
286 $lname = '';
289 return ($fname, $lname);
292 sub get_user_email
294 my $this = shift;
295 return EmailReminder::Utils::get_node_value($this->{USER_NODE}, $EMAIL_TAG) || "";
298 sub set_user_fname
300 my ($this, $new_fname) = @_;
301 return EmailReminder::Utils::set_node_value($this->{USER_NODE}, $FIRST_NAME_TAG, $new_fname);
304 sub set_user_lname
306 my ($this, $new_lname) = @_;
307 return EmailReminder::Utils::set_node_value($this->{USER_NODE}, $LAST_NAME_TAG, $new_lname);
310 # Return 0 if the email was ignored (invalid)
311 sub set_user_email
313 my ($this, $new_email) = @_;
314 if (!$new_email or Email::Valid->address($new_email)) {
315 return EmailReminder::Utils::set_node_value($this->{USER_NODE}, $EMAIL_TAG, $new_email);
316 } else {
317 return 0;