/** * List all alarms near $date. * * @param integer $date The unix epoch time to check for alarms. * * @return array An array of tasks that have alarms that match. */ public function listAlarms($date) { if (!$this->tasks->count()) { $result = $this->retrieve(0); } $alarms = array(); $this->tasks->reset(); while ($task = $this->tasks->each()) { if ($task->alarm && ($due = $task->getNextDue()) && $due->timestamp() - $task->alarm * 60 <= $date) { $alarms[$task_id] = $task; } } return $alarms; }
public function setTask(Nag_Task $task) { $this->_task = $task; if (!$this->_task->childrenCompleted()) { $this->_completedVar->disable(); } }
/** * Get HTML to display the related tags links. * * @return string */ protected function _getRelatedTags() { $this->_tasks->reset(); $ids = array(); while ($t = $this->_tasks->each()) { $ids[] = $t->uid; } $rtags = $this->_browser->getRelatedTags($ids); if (count($rtags)) { $html = '<div class="nag-tags-related">' . Horde::img('tags.png') . ' <ul class="horde-tags">'; foreach ($rtags as $id => $taginfo) { $html .= '<li>' . $this->_linkAddTag($taginfo['tag_name'])->link() . htmlspecialchars($taginfo['tag_name']) . '</a></li>'; } return $html . '</ul></div>'; } return ''; }
/** * Retrieves sub-tasks from the database. * * @param string $parentId The parent id for the sub-tasks to * retrieve. * @param boolean $include_history Include created/modified info? Not * currently honored. * * @return array List of sub-tasks. * @throws Nag_Exception */ public function getChildren($parentId, $include_history = true) { $task_list = $this->_getData()->getObjects(); if (empty($task_list)) { return array(); } $tasks = array(); foreach ($task_list as $task) { if (Horde_Url::uriB64Encode($task['parent']) != $parentId) { continue; } $t = new Nag_Task($this, $this->_buildTask($task)); $children = $this->getChildren($t->id); $t->mergeChildren($children); $tasks[] = $t; } return $tasks; }
/** * Sends email notifications that a task has been added, edited, or * deleted to users that want such notifications. * * @param string $action The event action. One of "add", "edit", or * "delete". * @param Nag_Task $task The changed task. * @param Nag_Task $old_task The original task if $action is "edit". * * @throws Nag_Exception */ public static function sendNotification($action, $task, $old_task = null) { if (!in_array($action, array('add', 'edit', 'delete'))) { throw new Nag_Exception('Unknown event action: ' . $action); } try { $share = $GLOBALS['nag_shares']->getShare($task->tasklist); } catch (Horde_Share_Exception $e) { Horde::log($e->getMessage(), 'ERR'); throw new Nag_Exception($e); } $groups = $GLOBALS['injector']->getInstance('Horde_Group'); $recipients = array(); $identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create(); $from = $identity->getDefaultFromAddress(true); $owner = $share->get('owner'); if (strlen($owner)) { $recipients[$owner] = self::_notificationPref($owner, 'owner'); } foreach ($share->listUsers(Horde_Perms::READ) as $user) { if (empty($recipients[$user])) { $recipients[$user] = self::_notificationPref($user, 'read', $task->tasklist); } } foreach ($share->listGroups(Horde_Perms::READ) as $group) { try { $group_users = $groups->listUsers($group); } catch (Horde_Group_Exception $e) { Horde::log($e, 'ERR'); continue; } foreach ($group_users as $user) { if (empty($recipients[$user])) { $recipients[$user] = self::_notificationPref($user, 'read', $task->tasklist); } } } $addresses = array(); foreach ($recipients as $user => $vals) { if (!$vals) { continue; } $identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($user); $email = $identity->getValue('from_addr'); if (strpos($email, '@') === false) { continue; } if (!isset($addresses[$vals['lang']][$vals['tf']][$vals['df']])) { $addresses[$vals['lang']][$vals['tf']][$vals['df']] = array(); } $tmp = new Horde_Mail_Rfc822_Address($email); $tmp->personal = $identity->getValue('fullname'); $addresses[$vals['lang']][$vals['tf']][$vals['df']][] = strval($tmp); } if (!$addresses) { return; } $mail = new Horde_Mime_Mail(array('User-Agent' => 'Nag ' . $GLOBALS['registry']->getVersion(), 'Precedence' => 'bulk', 'Auto-Submitted' => 'auto-generated', 'From' => $from)); foreach ($addresses as $lang => $twentyFour) { $GLOBALS['registry']->setLanguageEnvironment($lang); $view_link = Horde::url('view.php', true)->add(array('tasklist' => $task->tasklist, 'task' => $task->id))->setRaw(true); switch ($action) { case 'add': $subject = _("Task added:"); $notification_message = _("You requested to be notified when tasks are added to your task lists.") . "\n\n" . ($task->due ? _("The task \"%s\" has been added to task list \"%s\", with a due date of: %s.") : _("The task \"%s\" has been added to task list \"%s\".")) . "\n" . str_replace('%', '%%', $view_link); break; case 'edit': $subject = _("Task modified:"); $notification_message = _("You requested to be notified when tasks are edited on your task lists.") . "\n\n" . _("The task \"%s\" has been edited on task list \"%s\".") . "\n" . str_replace('%', '%%', $view_link) . "\n\n" . _("Changes made for this task:"); if ($old_task->name != $task->name) { $notification_message .= "\n - " . sprintf(_("Changed name from \"%s\" to \"%s\""), $old_task->name, $task->name); } if ($old_task->tasklist != $task->tasklist) { $old_share = $GLOBALS['nag_shares']->getShare($old_task->tasklist); $notification_message .= "\n - " . sprintf(_("Changed task list from \"%s\" to \"%s\""), Nag::getLabel($old_share), Nag::getLabel($share)); } if ($old_task->parent_id != $task->parent_id) { $old_parent = $old_task->getParent(); try { $parent = $task->getParent(); $notification_message .= "\n - " . sprintf(_("Changed parent task from \"%s\" to \"%s\""), $old_parent ? $old_parent->name : _("no parent"), $parent ? $parent->name : _("no parent")); } catch (Nag_Exception $e) { } } if ($old_task->assignee != $task->assignee) { $identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($old_task->assignee); $old_name = $identity->getValue('fullname'); if (!strlen($old_name)) { $old_name = $old_task->assignee; } $identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($task->assignee); $new_name = $identity->getValue('fullname'); if (!strlen($new_name)) { $new_name = $new_task->assignee; } $notification_message .= "\n - " . sprintf(_("Changed assignee from \"%s\" to \"%s\""), $old_name, $new_name); } if ($old_task->private != $task->private) { $notification_message .= "\n - " . ($task->private ? _("Turned privacy on") : _("Turned privacy off")); } if ($old_task->due != $task->due) { $notification_message .= "\n - " . sprintf(_("Changed due date from %s to %s"), $old_task->due ? self::formatDate($old_task->due) : _("no due date"), $task->due ? self::formatDate($task->due) : _("no due date")); } if ($old_task->start != $task->start) { $notification_message .= "\n - " . sprintf(_("Changed start date from %s to %s"), $old_task->start ? self::formatDate($old_task->start) : _("no start date"), $task->start ? self::formatDate($task->start) : _("no start date")); } if ($old_task->alarm != $task->alarm) { $notification_message .= "\n - " . sprintf(_("Changed alarm from %s to %s"), self::formatAlarm($old_task->alarm), self::formatAlarm($task->alarm)); } if ($old_task->priority != $task->priority) { $notification_message .= "\n - " . sprintf(_("Changed priority from %s to %s"), $old_task->priority, $task->priority); } if ($old_task->estimate != $task->estimate) { $notification_message .= "\n - " . sprintf(_("Changed estimate from %s to %s"), $old_task->estimate, $task->estimate); } if ($old_task->completed != $task->completed) { $notification_message .= "\n - " . sprintf(_("Changed completion from %s to %s"), $old_task->completed ? _("completed") : _("not completed"), $task->completed ? _("completed") : _("not completed")); } if ($old_task->desc != $task->desc) { $notification_message .= "\n - " . _("Changed description"); } break; case 'delete': $subject = _("Task deleted:"); $notification_message = _("You requested to be notified when tasks are deleted from your task lists.") . "\n\n" . _("The task \"%s\" has been deleted from task list \"%s\"."); break; } $mail->addHeader('Subject', $subject . ' ' . $task->name); foreach ($twentyFour as $tf => $dateFormat) { foreach ($dateFormat as $df => $df_recipients) { $message = sprintf($notification_message, $task->name, Nag::getLabel($share), $task->due ? strftime($df, $task->due) . ' ' . date($tf ? 'H:i' : 'h:ia', $task->due) : ''); if (strlen(trim($task->desc))) { $message .= "\n\n" . _("Task description:") . "\n\n" . $task->desc; } $mail->setBody($message); $mail->clearRecipients(); $mail->addRecipients($df_recipients); Horde::log(sprintf('Sending event notifications for %s to %s', $task->name, implode(', ', $df_recipients)), 'INFO'); $mail->send($GLOBALS['injector']->getInstance('Horde_Mail')); } } } }
/** * Helper method for getting only a slice of the total tasks in this list. * * @param integer $page The starting page. * @param integer $perpage The count of tasks per page. * * @return Nag_Task The resulting task list. */ public function getSlice($page = 0, $perpage = null) { $this->reset(); // Position at start task $start = $page * (empty($perpage) ? 0 : $perpage); $count = 0; while ($count < $start) { if (!$this->each()) { return new Nag_Task(); } ++$count; } $count = 0; $results = new Nag_Task(); $max = empty($perpage) ? $this->count() - $start : $perpage; while ($count < $max) { if ($next = $this->each()) { $results->add($next); ++$count; } else { $count = $max; } } $results->process(); return $results; }
/** * Sends out iTip task notification to the assignee. * * Can be used to send task invitations, updates, and cancellations. * * @param Nag_Task $task The task in question. * @param Horde_Notification_Handler $notification * A notification object used to show result status. * @param integer $action * The type of notification to send. One of the Nag::ITIP_* values. * @param Horde_Date $instance * If cancelling a single instance of a recurring task, the date of * this instance. * @param string $range The range parameter if this is a recurring event. * Possible values are self::RANGE_THISANDFUTURE */ public static function sendITipNotifications(Nag_Task $task, Horde_Notification_Handler $notification, $action, Horde_Date $instance = null, $range = null) { global $injector, $registry, $nag_shares; if (!$task->assignee) { return; } $ident = $injector->getInstance('Horde_Core_Factory_Identity')->create($task->creator); if (!$ident->getValue('from_addr')) { $notification->push(sprintf(_("You do not have an email address configured in your Personal Information Preferences. You must set one %shere%s before event notifications can be sent."), $registry->getServiceLink('prefs', 'kronolith')->add(array('app' => 'horde', 'group' => 'identities'))->link(), '</a>'), 'horde.error', array('content.raw')); return; } // Generate image mime part first and only once, because we // need the Content-ID. $image = self::getImagePart('big_invitation.png'); $share = $nag_shares->getShare($task->tasklist); $view = new Horde_View(array('templatePath' => NAG_TEMPLATES . '/itip')); new Horde_View_Helper_Text($view); $view->identity = $ident; $view->task = $task; $view->imageId = $image->getContentId(); $email = Nag::getUserEmail($task->assignee); if (strpos($email, '@') === false) { continue; } /* Determine all notification-specific strings. */ $method = 'REQUEST'; switch ($action) { case self::ITIP_CANCEL: /* Cancellation. */ $method = 'CANCEL'; $filename = 'task-cancellation.ics'; $view->subject = sprintf(_("Cancelled: %s"), $task->name); if (empty($instance)) { $view->header = sprintf(_("%s has cancelled \"%s\"."), $ident->getName(), $task->name); } else { $view->header = sprintf(_("%s has cancelled an instance of the recurring \"%s\"."), $ident->getName(), $task->name); } break; case self::ITIP_UPDATE: if (!empty($task->organizer) && $task->organizer != Nag::getUserEmail($task->creator)) { // Sending a progress update. $method = 'REPLY'; } else { $method = 'UPDATE'; } case self::ITIP_REQUEST: default: if (empty($task->status) || $task->status == self::RESPONSE_NONE) { /* Invitation. */ $filename = 'task-invitation.ics'; $view->subject = $task->name; $view->header = sprintf(_("%s wishes to make you aware of \"%s\"."), $ident->getName(), $task->name); } else { $filename = 'task-update.ics'; $view->subject = sprintf(_("Updated: %s."), $task->name); $view->header = sprintf(_("%s wants to notify you about changes of \"%s\"."), $ident->getName(), $task->name); } break; } $view->attendees = $email; $view->organizer = empty($task->organizer) ? $registry->convertUserName($task->creator, false) : $task->organizer; /* Build the iCalendar data */ $iCal = new Horde_Icalendar(); $iCal->setAttribute('METHOD', $method); $vevent = $task->toiCalendar($iCal); $iCal->addComponent($vevent); /* text/calendar part */ $ics = new Horde_Mime_Part(); $ics->setType('text/calendar'); $ics->setContents($iCal->exportvCalendar()); $ics->setName($filename); $ics->setContentTypeParameter('METHOD', $method); $ics->setCharset('UTF-8'); $ics->setEOL("\r\n"); /* application/ics part */ $ics2 = clone $ics; $ics2->setType('application/ics'); /* multipart/mixed part */ $multipart = new Horde_Mime_Part(); $multipart->setType('multipart/mixed'); $inner = self::buildMimeMessage($view, 'notification', $image); $inner->addPart($ics); $multipart->addPart($inner); $multipart->addPart($ics2); $recipient = $method != 'REPLY' ? new Horde_Mail_Rfc822_Address($email) : new Horde_Mail_Rfc822_Address($task->organizer); $mail = new Horde_Mime_Mail(array('Subject' => $view->subject, 'To' => $recipient, 'From' => $ident->getDefaultFromAddress(true), 'User-Agent' => 'Nag ' . $registry->getVersion())); $mail->setBasePart($multipart); try { $mail->send($injector->getInstance('Horde_Mail')); $notification->push(sprintf(_("The task request notification to %s was successfully sent."), $recipient), 'horde.success'); } catch (Horde_Mime_Exception $e) { $notification->push(sprintf(_("There was an error sending a task request notification to %s: %s"), $recipient, $e->getMessage(), $e->getCode()), 'horde.error'); } }
/** */ public function davPutObject($collection, $object, $data) { $dav = $GLOBALS['injector']->getInstance('Horde_Dav_Storage'); $internal = $dav->getInternalCollectionId($collection, 'tasks') ?: $collection; if (!Nag::hasPermission($internal, Horde_Perms::EDIT)) { throw new Nag_Exception("Task List does not exist or no permission to edit"); } $ical = new Horde_Icalendar(); if (!$ical->parsevCalendar($data)) { throw new Nag_Exception(_("There was an error importing the iCalendar data.")); } $storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')->create($internal); foreach ($ical->getComponents() as $content) { if (!$content instanceof Horde_Icalendar_Vtodo) { continue; } $task = new Nag_Task(); $task->fromiCalendar($content); try { try { $existing_id = $dav->getInternalObjectId($object, $internal) ?: preg_replace('/\\.ics$/', '', $object); } catch (Horde_Dav_Exception $e) { $existing_id = $object; } $existing_task = Nag::getTask($internal, $existing_id); /* Check if our task is newer then the existing - get the * task's history. */ $modified = $this->_modified($internal, $existing_task->uid); try { if (!empty($modified) && $content->getAttribute('LAST-MODIFIED') < $modified) { /* LAST-MODIFIED timestamp of existing entry is newer: * don't replace it. */ continue; } } catch (Horde_Icalendar_Exception $e) { } $task->owner = $existing_task->owner; $storage->modify($existing_task->id, $task->toHash()); } catch (Horde_Exception_NotFound $e) { $hash = $task->toHash(); $newTask = $storage->add($hash); $dav->addObjectMap($newTask[0], $object, $internal); } } }
/** * Replaces the task identified by UID with the content represented in the * specified content type. * * If you want to replace multiple tasks with the UID specified in the * VCALENDAR data, you may use $this->import instead. This automatically does a * replace if existings UIDs are found. * * * @param string $uid Identify the task to replace. * @param string $content The content of the task. * @param string $contentType What format is the data in? Currently supports: * - text/x-vcalendar * - text/calendar * * @return boolean Success or failure. */ public function replace($uid, $content, $contentType) { $factory = $GLOBALS['injector']->getInstance('Nag_Factory_Driver'); $existing = $factory->create('')->getByUID($uid); $taskId = $existing->id; $owner = $existing->owner; if (!Nag::hasPermission($existing->tasklist, Horde_Perms::EDIT)) { throw new Horde_Exception_PermissionDenied(); } switch ($contentType) { case 'text/calendar': case 'text/x-vcalendar': if (!$content instanceof Horde_Icalendar_Vtodo) { $iCal = new Horde_Icalendar(); if (!$iCal->parsevCalendar($content)) { throw new Nag_Exception(_("There was an error importing the iCalendar data.")); } $components = $iCal->getComponents(); $component = null; foreach ($components as $content) { if ($content instanceof Horde_Icalendar_Vtodo) { if ($component !== null) { throw new Nag_Exception(_("Multiple iCalendar components found; only one vTodo is supported.")); } $component = $content; } } if ($component === null) { throw new Nag_Exception(_("No iCalendar data was found.")); } } $task = new Nag_Task(); $task->fromiCalendar($content); $task->owner = $owner; $factory->create($existing->tasklist)->modify($taskId, $task->toHash()); break; case 'activesync': $task = new Nag_Task(); $task->fromASTask($content); $task->owner = $owner; $factory->create($existing->tasklist)->modify($taskId, $task->toHash()); break; default: throw new Nag_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType)); } return $result; }
/** * Perform the search * * @param integer $page The page number * @param integer $perpage The number of results per page. * * @return Nag_Task */ protected function _search($page, $perpage) { global $injector, $prefs; if (!empty($this->_due)) { $parser = Horde_Date_Parser::factory(array('locale' => $GLOBALS['prefs']->getValue('language'))); $date = $parser->parse($this->_due[1]); $date->mday += $this->_due[0]; $date = $date->timestamp(); } else { $date = false; } // Get the full, sorted task list. $tasks = Nag::listTasks(array('tasklists' => $this->_tasklists, 'completed' => $this->_completed, 'include_history' => false)); if (!empty($this->_search)) { $pattern = '/' . preg_quote($this->_search, '/') . '/i'; } $search_results = new Nag_Task(); if (!empty($this->_tags)) { $tagged_tasks = $injector->getInstance('Nag_Tagger')->search($this->_tags, array('list' => $GLOBALS['display_tasklists'])); } $tasks->reset(); while ($task = $tasks->each()) { if (!empty($date)) { if (empty($task->due) || $task->due > $date) { continue; } } // If we have a search string and it doesn't match name|desc continue if (!empty($this->_search) && !($this->_mask & self::MASK_NAME && preg_match($pattern, $task->name)) && !($this->_mask & self::MASK_DESC && preg_match($pattern, $task->desc))) { continue; } // No tags to search? Add it to results. Otherwise, make sure it // has the requested tags. if (empty($this->_tags) || in_array($task->uid, $tagged_tasks)) { $search_results->add($task); } } // Now that we have filtered results, load all tags at once. $search_results->loadTags(); return $search_results; }
if (is_array($next_step)) { /* Create a Nag storage instance. */ $nag_storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')->create($storage->get('target')); $max_tasks = $perms->hasAppPermission('max_tasks'); $num_tasks = Nag::countTasks(); $result = null; foreach ($next_step as $row) { if ($max_tasks !== true && $num_tasks >= $max_tasks) { Horde::permissionDeniedError('nag', 'max_tasks', sprintf(_("You are not allowed to create more than %d tasks."), $perms->hasAppPermission('max_tasks'))); break; } if (!is_array($row)) { if (!is_a($row, 'Horde_Icalendar_Vtodo')) { continue; } $task = new Nag_Task($nag_storage); $task->fromiCalendar($row); $row = $task->toHash(); foreach (array_keys($app_fields) as $field) { if (!isset($row[$field])) { $row[$field] = ''; } } } $row['owner'] = $GLOBALS['registry']->getAuth(); foreach (array('start', 'due', 'completed_date') as $field) { if (!empty($row[$field])) { try { $date = new Horde_Date($row[$field]); $row[$field] = $date->timestamp(); } catch (Horde_Date_Exception $e) {
/** * Lists all alarms near $date. * * @param integer $date The unix epoch time to check for alarms. * * @return array An array of tasks that have alarms that match. * @throws Nag_Exception */ public function listAlarms($date) { $q = 'SELECT * FROM ' . $this->_params['table'] . ' WHERE task_owner = ?' . ' AND task_alarm > 0' . ' AND (task_due - (task_alarm * 60) <= ?)' . ' AND task_completed = 0'; $values = array($this->_tasklist, $date); try { $result = $this->_db->selectAll($q, $values); } catch (Horde_Db_Exception $e) { throw new Nag_Exception($e->getMessage()); } $tasks = array(); foreach ($result as $row) { $task = new Nag_Task($this, $this->_buildTask($row)); if ($task->getNextDue()->before($date + $task->alarm * 60)) { $tasks[$row['task_id']] = $task; } } return $tasks; }
/** * Fetch the matching resources that should appear on the current page * * @param integer $page Start page. * @param integer $perpage Number of tasks per page. * * @return Nag_Task A list of tasks. */ public function getSlice($page = 0, $perpage = null) { // Refresh the search $this->runSearch(); return $this->_tasks->getSlice($page, $perpage); }
/** * Lists all alarms near $date. * * @param integer $date The unix epoch time to check for alarms. * * @return array An array of tasks that have alarms that match. * @throws Nag_Exception */ public function listAlarms($date) { // Check for non-empty alarm AND a non-empty due date. // See Bug: 14214 $q = 'SELECT * FROM nag_tasks' . ' WHERE task_owner = ?' . ' AND task_alarm > 0 AND task_due > 0' . ' AND (task_due - (task_alarm * 60) <= ?)' . ' AND task_completed = 0'; $values = array($this->_tasklist, $date); try { $result = $this->_db->select($q, $values); } catch (Horde_Db_Exception $e) { throw new Nag_Exception($e->getMessage()); } $tasks = array(); foreach ($result as $row) { $task = new Nag_Task($this, $this->_buildTask($row)); if ($task->getNextDue()->before($date + $task->alarm * 60)) { $tasks[$row['task_id']] = $task; } } return $tasks; }