650 lines
23 KiB
PHP
650 lines
23 KiB
PHP
<?php
|
|
/**
|
|
* Mnemo external API interface.
|
|
*
|
|
* This file defines Mnemo's external API interface. Other applications can
|
|
* interact with Mnemo through this API.
|
|
*
|
|
* Copyright 2001-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file LICENSE for license information (ASL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/apache.
|
|
*
|
|
* @category Horde
|
|
* @package Mnemo
|
|
*/
|
|
class Mnemo_Api extends Horde_Registry_Api
|
|
{
|
|
/**
|
|
* Links.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_links = array(
|
|
'show' => '%application%/view.php?memo=|memo|&memolist=|memolist|&uid=|uid|'
|
|
);
|
|
|
|
/**
|
|
* Removes user data.
|
|
*
|
|
* @deprecated Use Horde's removeUserData API call instead.
|
|
*
|
|
* @param string $user Name of user to remove data for.
|
|
*
|
|
* @throws Mnemo_Exception
|
|
*/
|
|
public function removeUserData($user)
|
|
{
|
|
try {
|
|
$GLOBALS['registry']->removeUserData($user, 'mnemo');
|
|
} catch (Horde_Exception $e) {
|
|
throw new Mnemo_Exception($e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param boolean $owneronly Only return notepads that this user owns?
|
|
* Defaults to false.
|
|
* @param integer $permission The permission to filter notepads by.
|
|
*
|
|
* @return array The notepads.
|
|
*/
|
|
public function listNotepads($owneronly, $permission)
|
|
{
|
|
return Mnemo::listNotepads($owneronly, $permission);
|
|
}
|
|
|
|
/**
|
|
* Returns an array of UIDs for all notes that the current user is authorized
|
|
* to see.
|
|
*
|
|
* @param array|string $notepads The notepad(s) to list notes from.
|
|
*
|
|
* @return array An array of UIDs for all notes the user can access.
|
|
* @throws Mnemo_Exception
|
|
* @throws Horde_Exception_PermissionDenied
|
|
*/
|
|
public function listUids($notepads = null)
|
|
{
|
|
global $conf;
|
|
|
|
if (!isset($conf['storage']['driver'])) {
|
|
throw new RuntimeException('Not configured');
|
|
}
|
|
|
|
// Make sure we have a valid notepad.
|
|
if (empty($notepads)) {
|
|
$notepads = Mnemo::getSyncNotepads();
|
|
} else {
|
|
if (!is_array($notepads)) {
|
|
$notepads = array($notepads);
|
|
}
|
|
foreach ($notepads as $notepad) {
|
|
if (!Mnemo::hasPermission($notepad, Horde_Perms::READ)) {
|
|
throw new Horde_Exception_PermissionDenied();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set notepad for listMemos.
|
|
$GLOBALS['display_notepads'] = $notepads;
|
|
|
|
$memos = Mnemo::listMemos();
|
|
$uids = array();
|
|
foreach ($memos as $memo) {
|
|
$uids[] = $memo['uid'];
|
|
}
|
|
|
|
return $uids;
|
|
}
|
|
|
|
/**
|
|
* Method for obtaining all server changes between two timestamps. Basically
|
|
* a wrapper around listBy(), but returns an array containing all adds,
|
|
* edits and deletions.
|
|
*
|
|
* @param integer $start The starting timestamp
|
|
* @param integer $end The ending timestamp.
|
|
* @param boolean $isModSeq If true, $timestamp and $end are
|
|
* modification sequences and not
|
|
* timestamps. @since 4.1.1
|
|
* @param string|array $notepads The sources to check. @since 4.2.0
|
|
*
|
|
* @return array An hash with 'add', 'modify' and 'delete' arrays.
|
|
*/
|
|
public function getChanges($start, $end, $isModSeq = false, $notepads = null)
|
|
{
|
|
return array('add' => $this->listBy('add', $start, $notepads, $end, $isModSeq),
|
|
'modify' => $this->listBy('modify', $start, $notepads, $end, $isModSeq),
|
|
'delete' => $this->listBy('delete', $start, $notepads, $end, $isModSeq));
|
|
}
|
|
|
|
/**
|
|
* Return all changes occuring between the specified modification
|
|
* sequences.
|
|
*
|
|
* @param integer $start The starting modseq.
|
|
* @param integer $end The ending modseq.
|
|
* @param string|array $notepads The sources to check. @since 4.2.0
|
|
*
|
|
* @return array The changes @see getChanges()
|
|
* @since 4.1.1
|
|
*/
|
|
public function getChangesByModSeq($start, $end, $notepads = null)
|
|
{
|
|
return $this->getChanges($start, $end, true, $notepads);
|
|
}
|
|
|
|
/**
|
|
* Returns an array of UIDs for notes that have had $action happen since
|
|
* $timestamp.
|
|
*
|
|
* @param string $action The action to check for - add, modify, or delete.
|
|
* @param integer $timestamp The time to start the search.
|
|
* @param string $notepad The notepad to search in.
|
|
* @param integer $end The optional ending timestamp.
|
|
* @param boolean $isModSeq If true, $timestamp and $end are modification
|
|
* sequences and not timestamps. @since 4.1.1
|
|
*
|
|
* @return array An array of UIDs matching the action and time criteria.
|
|
*/
|
|
public function listBy($action, $timestamp, $notepad = null, $end = null, $isModSeq = false)
|
|
{
|
|
/* Make sure we have a valid notepad. */
|
|
if (empty($notepad)) {
|
|
$notepads = Mnemo::getSyncNotepads();
|
|
$results = array();
|
|
foreach ($notepads as $notepad) {
|
|
$results = array_merge($results, $this->listBy($action, $timestamp, $notepad, $end, $isModSeq));
|
|
}
|
|
return $results;
|
|
}
|
|
|
|
$filter = array(array('op' => '=', 'field' => 'action', 'value' => $action));
|
|
if (!empty($end) && !$isModSeq) {
|
|
$filter[] = array('op' => '<', 'field' => 'ts', 'value' => $end);
|
|
}
|
|
$history = $GLOBALS['injector']->getInstance('Horde_History');
|
|
if (!$isModSeq) {
|
|
$histories = $history->getByTimestamp('>', $timestamp, $filter, 'mnemo:' . $notepad);
|
|
} else {
|
|
$histories = $history->getByModSeq($timestamp, $end, $filter, 'mnemo:' . $notepad);
|
|
}
|
|
|
|
// Strip leading mnemo:username:.
|
|
return preg_replace('/^([^:]*:){2}/', '', array_keys($histories));
|
|
}
|
|
|
|
/**
|
|
* Returns the timestamp of an operation for a given uid an action.
|
|
*
|
|
* @param string $uid The uid to look for.
|
|
* @param string $action The action to check for - add, modify, or delete.
|
|
* @param string $notepad The notepad to search in.
|
|
* @param boolean $modSeq Request a modification sequence instead of a
|
|
* timestamp. @since 4.1.1
|
|
*
|
|
* @return integer The timestamp or modseq for this action.
|
|
* @throws Horde_Exception_PermissionDenied
|
|
*/
|
|
public function getActionTimestamp($uid, $action, $notepad = null, $modSeq = false)
|
|
{
|
|
/* Make sure we have a valid notepad. */
|
|
if (empty($notepad)) {
|
|
$notepad = Mnemo::getDefaultNotepad();
|
|
}
|
|
|
|
if (!array_key_exists($notepad, Mnemo::listNotepads(false, Horde_Perms::READ))) {
|
|
throw new Horde_Exception_PermissionDenied();
|
|
}
|
|
|
|
$history = $GLOBALS['injector']->getInstance('Horde_History');
|
|
if (!$modSeq) {
|
|
return $history->getActionTimestamp('mnemo:' . $notepad . ':' . $uid, $action);
|
|
} else {
|
|
return $history->getActionModSeq('mnemo:' . $notepad . ':' . $uid, $action);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the largest modification sequence from the history backend.
|
|
*
|
|
* @param string $id The notepad id to get the MODSEQ for. If null, the
|
|
* highest MODSEQ across all notepads is returned.
|
|
* @since 4.2.0
|
|
*
|
|
* @return integer The modseq.
|
|
* @since 4.1.1
|
|
*/
|
|
public function getHighestModSeq($id = null)
|
|
{
|
|
$parent = 'mnemo';
|
|
if (!empty($id)) {
|
|
$parent .= ':' . $id;
|
|
}
|
|
return $GLOBALS['injector']->getInstance('Horde_History')->getHighestModSeq($parent);
|
|
}
|
|
|
|
/**
|
|
* Import a memo represented in the specified contentType.
|
|
*
|
|
* @param string $content The content of the memo.
|
|
* @param string $contentType What format is the data in? Currently supports:
|
|
* text/plain
|
|
* text/x-vnote
|
|
* activesync
|
|
* @param string $notepad (optional) The notepad to save the memo on.
|
|
*
|
|
* @return string The new UID, or false on failure.
|
|
* @throws Mnemo_Exception
|
|
* @throws Horde_Exception_PermissionDenied
|
|
*/
|
|
public function import($content, $contentType, $notepad = null)
|
|
{
|
|
global $prefs;
|
|
|
|
/* Make sure we have a valid notepad and permissions to edit
|
|
* it. */
|
|
if (empty($notepad)) {
|
|
$notepad = Mnemo::getDefaultNotepad(Horde_Perms::EDIT);
|
|
}
|
|
|
|
if (!array_key_exists($notepad, Mnemo::listNotepads(false, Horde_Perms::EDIT))) {
|
|
throw new Horde_Exception_PermissionDenied();
|
|
}
|
|
|
|
/* Create a Mnemo_Driver instance. */
|
|
$storage = $GLOBALS['injector']->getInstance('Mnemo_Factory_Driver')->create($notepad);
|
|
|
|
switch ($contentType) {
|
|
case 'text/plain':
|
|
$noteId = $storage->add($storage->getMemoDescription($content), $content);
|
|
break;
|
|
|
|
case 'text/x-vnote':
|
|
if (!($content instanceof Horde_Icalendar_Vnote)) {
|
|
$iCal = new Horde_Icalendar();
|
|
if (!$iCal->parsevCalendar($content)) {
|
|
throw new Mnemo_Exception(_("There was an error importing the iCalendar data."));
|
|
}
|
|
|
|
$components = $iCal->getComponents();
|
|
switch (count($components)) {
|
|
case 0:
|
|
throw new Mnemo_Exception(_("No iCalendar data was found."));
|
|
|
|
case 1:
|
|
$content = $components[0];
|
|
break;
|
|
|
|
default:
|
|
$ids = array();
|
|
foreach ($components as $content) {
|
|
if ($content instanceof Horde_Icalendar_Vnote) {
|
|
$note = $storage->fromiCalendar($content);
|
|
$noteId = $storage->add(
|
|
$note['desc'], $note['body'],
|
|
!empty($note['tags']) ? $note['tags'] : '');
|
|
$ids[] = $noteId;
|
|
}
|
|
}
|
|
return $ids;
|
|
}
|
|
}
|
|
|
|
$note = $storage->fromiCalendar($content);
|
|
$noteId = $storage->add(
|
|
$note['desc'], $note['body'],
|
|
!empty($note['tags']) ? $note['tags'] : '');
|
|
break;
|
|
|
|
case 'activesync':
|
|
// We only support plaintext
|
|
if ($content->body->type == Horde_ActiveSync::BODYPREF_TYPE_HTML) {
|
|
$body = Horde_Text_Filter::filter($content->body->data, 'Html2text');
|
|
} else {
|
|
$body = $content->body->data;
|
|
}
|
|
$noteId = $storage->add(Horde_String::substr($content->subject, 0, 255), $body, $content->categories);
|
|
break;
|
|
|
|
default:
|
|
throw new Mnemo_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
|
|
}
|
|
$note = $storage->get($noteId);
|
|
|
|
return $note['uid'];
|
|
}
|
|
|
|
/**
|
|
* Export a memo, identified by UID, in the requested contentType.
|
|
*
|
|
* @param string $uid Identify the memo to export.
|
|
* @param string $contentType What format should the data be in?
|
|
* A string with one of:
|
|
* <pre>
|
|
* 'text/plain'
|
|
* 'text/x-vnote'
|
|
* 'activesync'
|
|
* </pre>
|
|
* @param array $options Any additional options to be passed to the
|
|
* exporter.
|
|
*
|
|
* @return mixed The requested data
|
|
* @throws Mnemo_Exception
|
|
* @throws Horde_Exception_PermissionDenied
|
|
*/
|
|
public function export($uid, $contentType, array $options = array())
|
|
{
|
|
$storage = $GLOBALS['injector']->getInstance('Mnemo_Factory_Driver')->create();
|
|
$memo = $storage->getByUID($uid);
|
|
if (!array_key_exists($memo['memolist_id'], Mnemo::listNotepads(false, Horde_Perms::READ))) {
|
|
throw new Horde_Exception_PermissionDenied();
|
|
}
|
|
|
|
switch ($contentType) {
|
|
case 'text/plain':
|
|
return $memo['body'];
|
|
|
|
case 'text/x-vnote':
|
|
// Create the new iCalendar container.
|
|
$iCal = new Horde_Icalendar('1.1');
|
|
$iCal->setAttribute('VERSION', '1.1');
|
|
$iCal->setAttribute('PRODID', '-//The Horde Project//Mnemo ' . $GLOBALS['registry']->getVersion() . '//EN');
|
|
$iCal->setAttribute('METHOD', 'PUBLISH');
|
|
|
|
// Create a new vNote.
|
|
$vNote = $storage->toiCalendar($memo, $iCal);
|
|
return $vNote->exportvCalendar();
|
|
|
|
case 'activesync':
|
|
return $storage->toASNote($memo, $options);
|
|
}
|
|
|
|
throw new Mnemo_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
|
|
}
|
|
|
|
/**
|
|
* Delete a memo identified by UID.
|
|
*
|
|
* @param string | array $uid Identify the note to delete, either a
|
|
* single UID or an array.
|
|
* @throws Horde_Exception_PermissionDenied
|
|
*/
|
|
public function delete($uid)
|
|
{
|
|
// Handle an arrray of UIDs for convenience of deleting multiple
|
|
// notes at once.
|
|
if (is_array($uid)) {
|
|
foreach ($uid as $u) {
|
|
$result = $this->delete($u);
|
|
}
|
|
return;
|
|
}
|
|
|
|
$storage = $GLOBALS['injector']->getInstance('Mnemo_Factory_Driver')->create();
|
|
$memo = $storage->getByUID($uid);
|
|
if (!$GLOBALS['registry']->isAdmin() &&
|
|
!array_key_exists($memo['memolist_id'], Mnemo::listNotepads(false, Horde_Perms::DELETE))) {
|
|
|
|
throw new Horde_Exception_PermissionDenied();
|
|
}
|
|
|
|
$storage->delete($memo['memo_id']);
|
|
}
|
|
|
|
/**
|
|
* Replace the memo identified by UID with the content represented in
|
|
* the specified contentType.
|
|
*
|
|
* @param string $uid Idenfity the memo to replace.
|
|
* @param string $content The content of the memo.
|
|
* @param string $contentType What format is the data in? Currently supports:
|
|
* text/plain
|
|
* text/x-vnote
|
|
* activesync
|
|
* @throws Mnemo_Exception
|
|
* @throws Horde_Exception_PermissionDenied
|
|
*/
|
|
public function replace($uid, $content, $contentType)
|
|
{
|
|
$storage = $GLOBALS['injector']->getInstance('Mnemo_Factory_Driver')->create();
|
|
$memo = $storage->getByUID($uid);
|
|
if (!array_key_exists($memo['memolist_id'], Mnemo::listNotepads(false, Horde_Perms::EDIT))) {
|
|
throw new Horde_Exception_PermissionDenied();
|
|
}
|
|
|
|
switch ($contentType) {
|
|
case 'text/plain':
|
|
$storage->modify($memo['memo_id'], $storage->getMemoDescription($content), $content, null);
|
|
break;
|
|
case 'text/x-vnote':
|
|
if (!($content instanceof Horde_Icalendar_Vnote)) {
|
|
$iCal = new Horde_Icalendar();
|
|
if (!$iCal->parsevCalendar($content)) {
|
|
throw new Mnemo_Exception(_("There was an error importing the iCalendar data."));
|
|
}
|
|
|
|
$components = $iCal->getComponents();
|
|
switch (count($components)) {
|
|
case 0:
|
|
throw new Mnemo_Exception(_("No iCalendar data was found."));
|
|
|
|
case 1:
|
|
$content = $components[0];
|
|
break;
|
|
|
|
default:
|
|
throw new Mnemo_Exception(_("Multiple iCalendar components found; only one vNote is supported."));
|
|
}
|
|
}
|
|
$note = $storage->fromiCalendar($content);
|
|
$storage->modify($memo['memo_id'],
|
|
$note['desc'],
|
|
$note['body'],
|
|
!empty($note['tags']) ? $note['tags'] : '');
|
|
break;
|
|
|
|
case 'activesync':
|
|
// We only support plaintext
|
|
if ($content->body->type == Horde_ActiveSync::BODYPREF_TYPE_HTML) {
|
|
$body = Horde_Text_Filter::filter($content->body->data, 'Html2text');
|
|
} else {
|
|
$body = $content->body->data;
|
|
}
|
|
$storage->modify($memo['memo_id'], Horde_String::substr($content->subject, 0, 255), $body, $content->categories);
|
|
break;
|
|
|
|
default:
|
|
throw new Mnemo_Exception(sprintf(_("Unsupported Content-Type: %s"),$contentType));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a list of available sources.
|
|
*
|
|
* @param boolean $writeable If true, limits to writeable sources.
|
|
* @param boolean $sync_only Only include synchable notepads.
|
|
*
|
|
* @return array An array of the available sources. Keys are source IDs,
|
|
* values are source titles.
|
|
* @since 4.2.0
|
|
*/
|
|
public function sources($writeable = false, $sync_only = false)
|
|
{
|
|
$out = array();
|
|
|
|
foreach (Mnemo::listNotepads(false, $writeable ? Horde_Perms::EDIT : Horde_Perms::READ) as $key => $val) {
|
|
$out[$key] = $val->get('name');
|
|
}
|
|
|
|
if ($sync_only) {
|
|
$syncable = Mnemo::getSyncNotepads();
|
|
$out = array_intersect_key($out, array_flip($syncable));
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Retrieve the UID for the current user's default notepad.
|
|
*
|
|
* @return string UID.
|
|
* @since 4.2.0
|
|
*/
|
|
public function getDefaultShare()
|
|
{
|
|
return Mnemo::getDefaultNotepad(Horde_Perms::EDIT);
|
|
}
|
|
|
|
/**
|
|
* Create a new notepad.
|
|
*
|
|
* @param string $name The notepad display name.
|
|
* @param array $params Any additional parameters needed.
|
|
* - synchronize: (boolean) If true, add notepad to the list of
|
|
* notepads to syncronize.
|
|
* DEFAULT: false (do not add to the list).
|
|
* @since 4.2.1
|
|
*
|
|
* @return string The new notepad's id.
|
|
* @since 4.2.0
|
|
*/
|
|
public function addNotepad($name, array $params = array())
|
|
{
|
|
if ($GLOBALS['prefs']->isLocked('default_notepad')) {
|
|
throw new Horde_Exception_PermissionDenied();
|
|
}
|
|
|
|
$notepad = $GLOBALS['mnemo_shares']->newShare(
|
|
$GLOBALS['registry']->getAuth(),
|
|
strval(new Horde_Support_Uuid()),
|
|
$name);
|
|
|
|
$name = $notepad->getName();
|
|
if (!empty($params['synchronize'])) {
|
|
$sync = @unserialize($prefs->getValue('sync_notepads'));
|
|
$sync[] = $name;
|
|
$prefs->setValue('sync_notepads', serialize($sync));
|
|
}
|
|
|
|
return $name;
|
|
}
|
|
|
|
/**
|
|
* Delete notepad.
|
|
*
|
|
* @param string $id The notepad id.
|
|
* @since 4.2.0
|
|
*/
|
|
public function deleteNotepad($id)
|
|
{
|
|
// Delete the notepad.
|
|
$storage = $GLOBALS['injector']
|
|
->getInstance('Mnemo_Factory_Driver')
|
|
->create($id);
|
|
$storage->deleteAll();
|
|
$share = $GLOBALS['mnemo_shares']->getShare($id);
|
|
$GLOBALS['mnemo_shares']->removeShare($share);
|
|
}
|
|
|
|
/**
|
|
* Update a notepad's title and/or description.
|
|
*
|
|
* @param string $id The notepad id
|
|
* @param array $info The data to change:
|
|
* - name: The display name.
|
|
* - desc: The description.
|
|
*
|
|
* @since 4.2.0
|
|
*/
|
|
public function updateNotepad($id, array $info)
|
|
{
|
|
$notepad = $GLOBALS['mnemo_shares']->getShare($id);
|
|
if (!empty($info['name'])) {
|
|
$notepad->set('name', $info['name']);
|
|
}
|
|
if (!empty($info['desc'])) {
|
|
$notepad->set('desc', $info['desc']);
|
|
}
|
|
|
|
$notepad->save();
|
|
}
|
|
|
|
/**
|
|
* Retrieve the list of used tag_names, tag_ids and the total number
|
|
* of resources that are linked to that tag.
|
|
*
|
|
* @param array $tags An optional array of tag_ids. If omitted, all tags
|
|
* will be included.
|
|
*
|
|
* @return array An array containing tag_name, and total
|
|
*/
|
|
public function listTagInfo($tags = null, $user = null)
|
|
{
|
|
return $GLOBALS['injector']->getInstance('Mnemo_Tagger')
|
|
->getTagInfo($tags, 500, null, $user);
|
|
}
|
|
|
|
/**
|
|
* SearchTags API:
|
|
* Returns an application-agnostic array (useful for when doing a tag search
|
|
* across multiple applications)
|
|
*
|
|
* The 'raw' results array can be returned instead by setting $raw = true.
|
|
*
|
|
* @param array $names An array of tag_names to search for.
|
|
* @param integer $max The maximum number of resources to return.
|
|
* @param integer $from The number of the resource to start with.
|
|
* @param string $resource_type The resource type [bookmark, '']
|
|
* @param string $user Restrict results to resources owned by $user.
|
|
* @param boolean $raw Return the raw data?
|
|
*
|
|
* @return array An array of results:
|
|
* <pre>
|
|
* 'title' - The title for this resource.
|
|
* 'desc' - A terse description of this resource.
|
|
* 'view_url' - The URL to view this resource.
|
|
* 'app' - The Horde application this resource belongs to.
|
|
* 'icon' - URL to an image.
|
|
* </pre>
|
|
*/
|
|
public function searchTags($names, $max = 10, $from = 0,
|
|
$resource_type = '', $user = null, $raw = false)
|
|
{
|
|
// TODO: $max, $from, $resource_type not honored
|
|
global $injector, $registry;
|
|
|
|
$results = $injector
|
|
->getInstance('Mnemo_Tagger')
|
|
->search(
|
|
$names,
|
|
array('user' => $user));
|
|
|
|
// Check for error or if we requested the raw data array.
|
|
if ($raw) {
|
|
return $results;
|
|
}
|
|
|
|
$return = array();
|
|
$redirectUrl = Horde::url('memo.php');
|
|
foreach ($results as $memo_id) {
|
|
try {
|
|
$memo = $injector->getInstance('Mnemo_Factory_Driver')
|
|
->create(null)
|
|
->getByUID($memo_id);
|
|
$return[] = array(
|
|
'title' => $memo['desc'],
|
|
'desc' => '',
|
|
'view_url' => $redirectUrl->copy()->add(array('memo' => $memo['memo_id'], 'memolist' => $memo['memolist_id'])),
|
|
'app' => 'mnemo'
|
|
);
|
|
} catch (Exception $e) {
|
|
}
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
}
|