Changeset View
Changeset View
Standalone View
Standalone View
plugins/calendar/lib/calendar_itip.php
Show All 22 Lines | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
* GNU Affero General Public License for more details. | * GNU Affero General Public License for more details. | ||||
* | * | ||||
* You should have received a copy of the GNU Affero General Public License | * You should have received a copy of the GNU Affero General Public License | ||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | */ | ||||
class calendar_itip extends libcalendaring_itip | class calendar_itip extends libcalendaring_itip | ||||
{ | { | ||||
/** | /** | ||||
* Constructor to set text domain to calendar | * Constructor to set text domain to calendar | ||||
*/ | */ | ||||
function __construct($plugin, $domain = 'calendar') | function __construct($plugin, $domain = 'calendar') | ||||
{ | { | ||||
parent::__construct($plugin, $domain); | parent::__construct($plugin, $domain); | ||||
$this->db_itipinvitations = $this->rc->db->table_name('itipinvitations', true); | $this->db_itipinvitations = $this->rc->db->table_name('itipinvitations', true); | ||||
} | } | ||||
/** | /** | ||||
* Handler for calendar/itip-status requests | * Handler for calendar/itip-status requests | ||||
*/ | */ | ||||
public function get_itip_status($event, $existing = null) | public function get_itip_status($event, $existing = null) | ||||
{ | { | ||||
$status = parent::get_itip_status($event, $existing); | $status = parent::get_itip_status($event, $existing); | ||||
// don't ask for deleting events when declining | // don't ask for deleting events when declining | ||||
if ($this->rc->config->get('kolab_invitation_calendars')) | if ($this->rc->config->get('kolab_invitation_calendars')) { | ||||
$status['saved'] = false; | $status['saved'] = false; | ||||
} | |||||
return $status; | return $status; | ||||
} | } | ||||
/** | /** | ||||
* Find invitation record by token | * Find invitation record by token | ||||
* | * | ||||
* @param string Invitation token | * @param string $token Invitation token | ||||
* | |||||
* @return mixed Invitation record as hash array or False if not found | * @return mixed Invitation record as hash array or False if not found | ||||
*/ | */ | ||||
public function get_invitation($token) | public function get_invitation($token) | ||||
{ | { | ||||
if ($parts = $this->decode_token($token)) { | if ($parts = $this->decode_token($token)) { | ||||
$result = $this->rc->db->query("SELECT * FROM $this->db_itipinvitations WHERE `token` = ?", $parts['base']); | $result = $this->rc->db->query("SELECT * FROM $this->db_itipinvitations WHERE `token` = ?", $parts['base']); | ||||
if ($result && ($rec = $this->rc->db->fetch_assoc($result))) { | if ($result && ($rec = $this->rc->db->fetch_assoc($result))) { | ||||
$rec['event'] = unserialize($rec['event']); | $rec['event'] = unserialize($rec['event']); | ||||
$rec['attendee'] = $parts['attendee']; | $rec['attendee'] = $parts['attendee']; | ||||
return $rec; | return $rec; | ||||
} | } | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
/** | /** | ||||
* Update the attendee status of the given invitation record | * Update the attendee status of the given invitation record | ||||
* | * | ||||
* @param array Invitation record as fetched with calendar_itip::get_invitation() | * @param array $invitation Invitation record as fetched with calendar_itip::get_invitation() | ||||
* @param string Attendee email address | * @param string $email Attendee email address | ||||
* @param string New attendee status | * @param string $newstatus New attendee status | ||||
*/ | */ | ||||
public function update_invitation($invitation, $email, $newstatus) | public function update_invitation($invitation, $email, $newstatus) | ||||
{ | { | ||||
if (is_string($invitation)) | if (is_string($invitation)) { | ||||
$invitation = $this->get_invitation($invitation); | $invitation = $this->get_invitation($invitation); | ||||
} | |||||
if ($invitation['token'] && $invitation['event']) { | if (!empty($invitation['token']) && !empty($invitation['event'])) { | ||||
// update attendee record in event data | // update attendee record in event data | ||||
foreach ($invitation['event']['attendees'] as $i => $attendee) { | foreach ($invitation['event']['attendees'] as $i => $attendee) { | ||||
if ($attendee['role'] == 'ORGANIZER') { | if ($attendee['role'] == 'ORGANIZER') { | ||||
$organizer = $attendee; | $organizer = $attendee; | ||||
} | } | ||||
else if ($attendee['email'] == $email) { | else if ($attendee['email'] == $email) { | ||||
// nothing to be done here | // nothing to be done here | ||||
if ($attendee['status'] == $newstatus) | if ($attendee['status'] == $newstatus) { | ||||
return true; | return true; | ||||
} | |||||
$invitation['event']['attendees'][$i]['status'] = $newstatus; | $invitation['event']['attendees'][$i]['status'] = $newstatus; | ||||
$this->sender = $attendee; | $this->sender = $attendee; | ||||
} | } | ||||
} | } | ||||
$invitation['event']['changed'] = new DateTime(); | $invitation['event']['changed'] = new DateTime(); | ||||
// send iTIP REPLY message to organizer | // send iTIP REPLY message to organizer | ||||
if ($organizer) { | if (!empty($organizer)) { | ||||
$status = strtolower($newstatus); | $status = strtolower($newstatus); | ||||
if ($this->send_itip_message($invitation['event'], 'REPLY', $organizer, 'itipsubject' . $status, 'itipmailbody' . $status)) | if ($this->send_itip_message($invitation['event'], 'REPLY', $organizer, 'itipsubject' . $status, 'itipmailbody' . $status)) { | ||||
$this->rc->output->command('display_message', $this->plugin->gettext(array('name' => 'sentresponseto', 'vars' => array('mailto' => $organizer['name'] ? $organizer['name'] : $organizer['email']))), 'confirmation'); | $mailto = !empty($organizer['name']) ? $organizer['name'] : $organizer['email']; | ||||
else | $message = $this->plugin->gettext([ | ||||
'name' => 'sentresponseto', | |||||
'vars' => ['mailto' => $mailto] | |||||
]); | |||||
$this->rc->output->command('display_message', $message, 'confirmation'); | |||||
} | |||||
else { | |||||
$this->rc->output->command('display_message', $this->plugin->gettext('itipresponseerror'), 'error'); | $this->rc->output->command('display_message', $this->plugin->gettext('itipresponseerror'), 'error'); | ||||
} | } | ||||
} | |||||
// update record in DB | // update record in DB | ||||
$query = $this->rc->db->query( | $query = $this->rc->db->query( | ||||
"UPDATE $this->db_itipinvitations | "UPDATE $this->db_itipinvitations SET `event` = ? WHERE `token` = ?", | ||||
SET `event` = ? | |||||
WHERE `token` = ?", | |||||
self::serialize_event($invitation['event']), | self::serialize_event($invitation['event']), | ||||
$invitation['token'] | $invitation['token'] | ||||
); | ); | ||||
if ($this->rc->db->affected_rows($query)) | if ($this->rc->db->affected_rows($query)) { | ||||
return true; | return true; | ||||
} | } | ||||
} | |||||
return false; | return false; | ||||
} | } | ||||
/** | /** | ||||
* Create iTIP invitation token for later replies via URL | * Create iTIP invitation token for later replies via URL | ||||
* | * | ||||
* @param array Hash array with event properties | * @param array $event Hash array with event properties | ||||
* @param string Attendee email address | * @param string $attendee Attendee email address | ||||
* | |||||
* @return string Invitation token | * @return string Invitation token | ||||
*/ | */ | ||||
public function store_invitation($event, $attendee) | public function store_invitation($event, $attendee) | ||||
{ | { | ||||
static $stored = array(); | static $stored = []; | ||||
if (!$event['uid'] || !$attendee) | if (empty($event['uid']) || !$attendee) { | ||||
return false; | return false; | ||||
} | |||||
// generate token for this invitation | // generate token for this invitation | ||||
$token = $this->generate_token($event, $attendee); | $token = $this->generate_token($event, $attendee); | ||||
$base = substr($token, 0, 40); | $base = substr($token, 0, 40); | ||||
// already stored this | // already stored this | ||||
if ($stored[$base]) | if (!empty($stored[$base])) { | ||||
return $token; | return $token; | ||||
} | |||||
// delete old entry | // delete old entry | ||||
$this->rc->db->query("DELETE FROM $this->db_itipinvitations WHERE `token` = ?", $base); | $this->rc->db->query("DELETE FROM $this->db_itipinvitations WHERE `token` = ?", $base); | ||||
$event_uid = $event['uid'] . ($event['_instance'] ? '-' . $event['_instance'] : ''); | $event_uid = $event['uid'] . (!empty($event['_instance']) ? '-' . $event['_instance'] : ''); | ||||
$query = $this->rc->db->query( | $query = $this->rc->db->query( | ||||
"INSERT INTO $this->db_itipinvitations | "INSERT INTO $this->db_itipinvitations" | ||||
(`token`, `event_uid`, `user_id`, `event`, `expires`) | . " (`token`, `event_uid`, `user_id`, `event`, `expires`)" | ||||
VALUES(?, ?, ?, ?, ?)", | . " VALUES(?, ?, ?, ?, ?)", | ||||
$base, | $base, | ||||
$event_uid, | $event_uid, | ||||
$this->rc->user->ID, | $this->rc->user->ID, | ||||
self::serialize_event($event), | self::serialize_event($event), | ||||
date('Y-m-d H:i:s', $event['end']->format('U') + 86400 * 2) | date('Y-m-d H:i:s', $event['end']->format('U') + 86400 * 2) | ||||
); | ); | ||||
if ($this->rc->db->affected_rows($query)) { | if ($this->rc->db->affected_rows($query)) { | ||||
$stored[$base] = 1; | $stored[$base] = 1; | ||||
return $token; | return $token; | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
/** | /** | ||||
* Mark invitations for the given event as cancelled | * Mark invitations for the given event as cancelled | ||||
* | * | ||||
* @param array Hash array with event properties | * @param array $event Hash array with event properties | ||||
*/ | */ | ||||
public function cancel_itip_invitation($event) | public function cancel_itip_invitation($event) | ||||
{ | { | ||||
$event_uid = $event['uid'] . ($event['_instance'] ? '-' . $event['_instance'] : ''); | $event_uid = $event['uid'] . (!empty($event['_instance']) ? '-' . $event['_instance'] : ''); | ||||
// flag invitation record as cancelled | // flag invitation record as cancelled | ||||
$this->rc->db->query( | $this->rc->db->query( | ||||
"UPDATE $this->db_itipinvitations | "UPDATE $this->db_itipinvitations SET `cancelled` = 1" | ||||
SET `cancelled` = 1 | . " WHERE `event_uid` = ? AND `user_id` = ?", | ||||
WHERE `event_uid` = ? AND `user_id` = ?", | |||||
$event_uid, | $event_uid, | ||||
$this->rc->user->ID | $this->rc->user->ID | ||||
); | ); | ||||
} | } | ||||
/** | /** | ||||
* Generate an invitation request token for the given event and attendee | * Generate an invitation request token for the given event and attendee | ||||
* | * | ||||
* @param array Event hash array | * @param array $event Event hash array | ||||
* @param string Attendee email address | * @param string $attendee Attendee email address | ||||
*/ | */ | ||||
public function generate_token($event, $attendee) | public function generate_token($event, $attendee) | ||||
{ | { | ||||
$event_uid = $event['uid'] . ($event['_instance'] ? '-' . $event['_instance'] : ''); | $event_uid = $event['uid'] . (!empty($event['_instance']) ? '-' . $event['_instance'] : ''); | ||||
$base = sha1($event_uid . ';' . $this->rc->user->ID); | $base = sha1($event_uid . ';' . $this->rc->user->ID); | ||||
$mail = base64_encode($attendee); | $mail = base64_encode($attendee); | ||||
$hash = substr(md5($base . $mail . $this->rc->config->get('des_key')), 0, 6); | $hash = substr(md5($base . $mail . $this->rc->config->get('des_key')), 0, 6); | ||||
return "$base.$mail.$hash"; | return "$base.$mail.$hash"; | ||||
} | } | ||||
/** | /** | ||||
* Decode the given iTIP request token and return its parts | * Decode the given iTIP request token and return its parts | ||||
* | * | ||||
* @param string Request token to decode | * @param string $token Request token to decode | ||||
* | |||||
* @return mixed Hash array with parts or False if invalid | * @return mixed Hash array with parts or False if invalid | ||||
*/ | */ | ||||
public function decode_token($token) | public function decode_token($token) | ||||
{ | { | ||||
list($base, $mail, $hash) = explode('.', $token); | list($base, $mail, $hash) = explode('.', $token); | ||||
// validate and return parts | // validate and return parts | ||||
if ($mail && $hash && $hash == substr(md5($base . $mail . $this->rc->config->get('des_key')), 0, 6)) { | if ($mail && $hash && $hash == substr(md5($base . $mail . $this->rc->config->get('des_key')), 0, 6)) { | ||||
return array('base' => $base, 'attendee' => base64_decode($mail)); | return ['base' => $base, 'attendee' => base64_decode($mail)]; | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
/** | /** | ||||
* Helper method to serialize the given event for storing in invitations table | * Helper method to serialize the given event for storing in invitations table | ||||
*/ | */ | ||||
private static function serialize_event($event) | private static function serialize_event($event) | ||||
{ | { | ||||
$ev = $event; | $ev = $event; | ||||
if (!empty($ev['description'])) { | |||||
$ev['description'] = abbreviate_string($ev['description'], 100); | $ev['description'] = abbreviate_string($ev['description'], 100); | ||||
} | |||||
unset($ev['attachments']); | unset($ev['attachments']); | ||||
return serialize($ev); | return serialize($ev); | ||||
} | } | ||||
} | } |