diff options
author | Georg Ehrke <dev@georgswebsite.de> | 2012-04-10 20:10:07 -0400 |
---|---|---|
committer | Georg Ehrke <dev@georgswebsite.de> | 2012-04-10 20:10:07 -0400 |
commit | ac2d14101cc15d62d238c1299eb02e341c2e0c07 (patch) | |
tree | 1b6890fe756637df1183930ab9ae8f5f2c0c9070 | |
parent | 64d3301523dce1032f7e20f03452573c125f8b17 (diff) | |
parent | 9f547a1b3987838fda8db238a8999c9f474bb542 (diff) | |
download | nextcloud-server-ac2d14101cc15d62d238c1299eb02e341c2e0c07.tar.gz nextcloud-server-ac2d14101cc15d62d238c1299eb02e341c2e0c07.zip |
Merge branch 'master' into calendar_sharing
104 files changed, 5525 insertions, 614 deletions
diff --git a/3rdparty/granite/git/blob.php b/3rdparty/granite/git/blob.php new file mode 100644 index 00000000000..781a697d560 --- /dev/null +++ b/3rdparty/granite/git/blob.php @@ -0,0 +1,162 @@ +<?php +/** + * Blob - provides a Git blob object + * + * PHP version 5.3 + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; +use \Granite\Git\Object\Raw as Raw; +use \InvalidArgumentException as InvalidArgumentException; +use \finfo as finfo; +/** + * **Granite\Git\Blob** represents the raw content of an object in a Git repository, + * typically a **file**. This class provides methods related to the handling of + * blob content, mimetypes, sizes and write support. + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Blob +{ + + /** + * Stores the SHA-1 id of the object requested; accessed through the `sha()` + * method where it is recalculated based on the blob content. + */ + private $sha = null; + /** + * The raw binary string of the file contents. + */ + private $content = ""; + /** + * The path to the repository location. + */ + private $path; + + /** + * Fetches a raw Git object and parses the result. Throws an + * InvalidArgumentException if the object is not of the correct type, + * or cannot be found. + * + * @param string $path The path to the repository root. + * @param string $sha The SHA-1 id of the requested object, or `null` if + * creating a new blob object. + * + * @throws InvalidArgumentException If the SHA-1 id provided is not a blob. + */ + public function __construct($path, $sha = NULL) + { + $this->path = $path; + if ($sha !== NULL) { + $this->sha = $sha; + $object = Raw::factory($path, $sha); + + if ($object->type() !== Raw::OBJ_BLOB) { + throw new InvalidArgumentException( + "The object $sha is not a blob, type is {$object->type()}" + ); + } + + $this->content = $object->content(); + unset($object); + } + } + + /** + * Sets or returns the raw file content, depending whether the parameter is + * provided. + * + * @param string $content The object content to set, or `null` if requesting the + * current content. + * + * @return string The raw binary string of the file contents. + */ + public function content($content = NULL) + { + if ($content == NULL) { + return $this->content; + } + $this->content = $content; + } + + /** + * Returns the size of the file content in bytes, equivalent to + * `strlen($blob->content())`. + * + * @return int The size of the object in bytes. + */ + public function size() + { + return strlen($this->content); + } + + /** + * Updates and returns the SHA-1 id of the object, based on it's contents. + * + * @return int The SHA-1 id of the object. + */ + public function sha() + { + $sha = hash_init('sha1'); + $header = 'blob ' . strlen($this->content) . "\0"; + hash_update($sha, $header); + hash_update($sha, $this->content); + $this->sha = hash_final($sha); + return $this->sha; + } + + /** + * Returns the mimetype of the object, using `finfo()` to determine the mimetype + * of the string. + * + * @return string The object mimetype. + * @see http://php.net/manual/en/function.finfo-open.php + */ + public function mimetype() + { + $finfo = new finfo(FILEINFO_MIME); + return $finfo->buffer($this->content); + } + + /** + * Encode and compress the object content, saving it to a 'loose' file. + * + * @return boolean True on success, false on failure. + */ + public function write() + { + $sha = $this->sha(TRUE); + $path = $this->path + . 'objects' + . DIRECTORY_SEPARATOR + . substr($sha, 0, 2) + . DIRECTORY_SEPARATOR + . substr($sha, 2); + // FIXME: currently writes loose objects only + if (file_exists($path)) { + return FALSE; + } + + if (!is_dir(dirname($path))) { + mkdir(dirname($path), 0777, TRUE); + } + + $loose = fopen($path, 'wb'); + $data = 'blob ' . strlen($this->content) . "\0" . $this->content; + $write = fwrite($loose, gzcompress($data)); + fclose($loose); + + return ($write !== FALSE); + } + +} diff --git a/3rdparty/granite/git/commit.php b/3rdparty/granite/git/commit.php new file mode 100644 index 00000000000..51077e89f3c --- /dev/null +++ b/3rdparty/granite/git/commit.php @@ -0,0 +1,232 @@ +<?php +/** + * Commit - provides a 'commit' object + * + * PHP version 5.3 + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; +use \Granite\Git\Object\Raw as Raw; +use \InvalidArgumentException as InvalidArgumentException; + +/** + * Commit represents a full commit object + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Commit +{ + + /** + * The path to the repository root + */ + private $path; + /** + * The SHA-1 id of the requested commit + */ + private $sha; + /** + * The size of the commit in bytes + */ + private $size; + /** + * The commit message + */ + private $message; + /** + * The full committer string + */ + private $committer; + /** + * The full author string + */ + private $author; + /** + * The SHA-1 ids of the parent commits + */ + private $parents = array(); + + /** + * Fetches a raw Git object and parses the result. Throws an + * InvalidArgumentException if the object is not of the correct type, + * or cannot be found. + * + * @param string $path The path to the repository root + * @param string $sha The SHA-1 id of the requested object + * + * @throws InvalidArgumentException + */ + public function __construct($path, $sha = NULL) + { + $this->path = $path; + if ($sha !== NULL) { + $this->sha = $sha; + $object = Raw::factory($path, $sha); + $this->size = $object->size(); + + if ($object->type() !== Raw::OBJ_COMMIT) { + throw new InvalidArgumentException( + "The object $sha is not a commit, type is " . $object->type() + ); + } + + // Parse headers and commit message (delimited with "\n\n") + list($headers, $this->message) = explode("\n\n", $object->content(), 2); + $headers = explode("\n", $headers); + + foreach ($headers as $header) { + list($header, $value) = explode(' ', $header, 2); + if ($header == 'parent') { + $this->parents[] = $value; + } else { + $this->$header = $value; + } + } + + $this->tree = new Tree($this->path, $this->tree); + } + } + + /** + * Returns the message stored in the commit + * + * @return string The commit message + */ + public function message($message = NULL) + { + if ($message !== NULL) { + $this->message = $message; + return $this; + } + return $this->message; + } + + /** + * Returns the commiter string + * + * @return string The committer string + */ + public function committer($committer = NULL) + { + if ($committer !== NULL) { + $this->committer = $committer; + return $this; + } + return $this->committer; + } + + /** + * Returns the author string + * + * @return string The author string + */ + public function author($author = NULL) + { + if ($author !== NULL) { + $this->author = $author; + return $this; + } + return $this->author; + } + + /** + * Returns the parents of the commit, or an empty array if none + * + * @return array The parents of the commit + */ + public function parents($parents = NULL) + { + if ($parents !== NULL) { + $this->parents = $parents; + return $this; + } + return $this->parents; + } + + /** + * Returns a tree object associated with the commit + * + * @return Tree + */ + public function tree(Tree $tree = NULL) + { + if ($tree !== NULL) { + $this->tree = $tree; + return $this; + } + return $this->tree; + } + + /** + * Returns the size of the commit in bytes (Git header + data) + * + * @return int + */ + public function size() + { + return $this->size; + } + + /** + * Returns the size of the commit in bytes (Git header + data) + * + * @return int + */ + public function sha() + { + $this->sha = hash('sha1', $this->_raw()); + return $this->sha; + } + + public function write() + { + $sha = $this->sha(); + $path = $this->path + . 'objects' + . DIRECTORY_SEPARATOR + . substr($sha, 0, 2) + . DIRECTORY_SEPARATOR + . substr($sha, 2); + // FIXME: currently writes loose objects only + if (file_exists($path)) { + return FALSE; + } + + if (!is_dir(dirname($path))) { + mkdir(dirname($path), 0777, TRUE); + } + + $loose = fopen($path, 'wb'); + $data = $this->_raw(); + $write = fwrite($loose, gzcompress($data)); + fclose($loose); + + return ($write !== FALSE); + } + + public function _raw() + { + $data = 'tree ' . $this->tree->sha() . "\n"; + foreach ($this->parents as $parent) + { + $data .= "parent $parent\n"; + } + $data .= 'author ' . $this->author . "\n"; + $data .= 'committer ' . $this->committer . "\n\n"; + $data .= $this->message; + + $data = 'commit ' . strlen($data) . "\0" . $data; + return $data; + } + +} diff --git a/3rdparty/granite/git/object/index.php b/3rdparty/granite/git/object/index.php new file mode 100644 index 00000000000..239706d4efd --- /dev/null +++ b/3rdparty/granite/git/object/index.php @@ -0,0 +1,210 @@ +<?php +/** + * Index - provides an 'index' object for packfile indexes + * + * PHP version 5.3 + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Object; +use \UnexpectedValueException as UnexpectedValueException; + +/** + * Index represents a packfile index + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @link http://craig0990.github.com/Granite/ + */ +class Index +{ + const INDEX_MAGIC = "\377tOc"; + + /** + * The full path to the packfile index + */ + private $path; + /** + * The offset at which the fanout begins, version 2+ indexes have a 2-byte header + */ + private $offset = 8; + /** + * The size of the SHA-1 entries, version 1 stores 4-byte offsets alongside to + * total 24 bytes, version 2+ stores offsets separately + */ + private $size = 20; + /** + * The version of the index file format, versions 1 and 2 are in use and + * currently supported + */ + private $version; + + /** + * Fetches a raw Git object and parses the result + * + * @param string $path The path to the repository root + * @param string $packname The name of the packfile index to read + */ + public function __construct($path, $packname) + { + $this->path = $path + . 'objects' + . DIRECTORY_SEPARATOR + . 'pack' + . DIRECTORY_SEPARATOR + . 'pack-' . $packname . '.idx'; + + $this->version = $this->_readVersion(); + if ($this->version !== 1 && $this->version !== 2) { + throw new UnexpectedValueException( + "Unsupported index version (version $version)" + ); + } + + if ($this->version == 1) { + $this->offset = 0; // Version 1 index has no header/version + $this->size = 24; // Offsets + SHA-1 ids are stored together + } + } + + /** + * Returns the offset of the object stored in the index + * + * @param string $sha The SHA-1 id of the object being requested + * + * @return int The offset of the object in the packfile + */ + public function find($sha) + { + $index = fopen($this->path, 'rb'); + $offset = false; // Offset for object in packfile not found by default + + // Read the fanout to skip to the start char in the sorted SHA-1 list + list($start, $after) = $this->_readFanout($index, $sha); + + if ($start == $after) { + fclose($index); + return false; // Object is apparently located in a 0-length section + } + + // Seek $offset + 255 4-byte fanout entries and read 256th entry + fseek($index, $this->offset + 4 * 255); + $totalObjects = $this->_uint32($index); + + // Look up the SHA-1 id of the object + // TODO: Binary search + fseek($index, $this->offset + 1024 + $this->size * $start); + for ($i = $start; $i < $after; $i++) { + if ($this->version == 1) { + $offset = $this->_uint32($index); + } + + $name = fread($index, 20); + if ($name == pack('H40', $sha)) { + break; // Found it + } + } + + if ($i == $after) { + fclose($index); + return false; // Scanned entire section, couldn't find it + } + + if ($this->version == 2) { + // Jump to the offset location and read it + fseek($index, 1032 + 24 * $totalObjects + 4 * $i); + $offset = $this->_uint32($index); + if ($offset & 0x80000000) { + // Offset is a 64-bit integer; packfile is larger than 2GB + fclose($index); + throw new UnexpectedValueException( + "Packfile larger than 2GB, currently unsupported" + ); + } + } + + fclose($index); + return $offset; + } + + /** + * Converts a binary string into a 32-bit unsigned integer + * + * @param handle $file Binary string to convert + * + * @return int Integer value + */ + private function _uint32($file) + { + $val = unpack('Nx', fread($file, 4)); + return $val['x']; + } + + /** + * Reads the fanout for a particular SHA-1 id + * + * Largely modified from Glip, with some reference to Grit - largely because I + * can't see how to re-implement this in PHP + * + * @param handle $file File handle to the index file + * @param string $sha The SHA-1 id to search for + * @param int $offset The offset at which the fanout begins + * + * @return array Array containing integer 'start' and + * 'past-the-end' locations + */ + private function _readFanout($file, $sha) + { + $sha = pack('H40', $sha); + fseek($file, $this->offset); + if ($sha{0} == "\00") { + /** + * First character is 0, read first fanout entry to provide + * 'past-the-end' location (since first fanout entry provides start + * point for '1'-prefixed SHA-1 ids) + */ + $start = 0; + fseek($file, $this->offset); // Jump to start of fanout, $offset bytes in + $after = $this->_uint32($file); + } else { + /** + * Take ASCII value of first character, minus one to get the fanout + * position of the offset (minus one because the fanout does not + * contain an entry for "\00"), multiplied by four bytes per entry + */ + fseek($file, $this->offset + (ord($sha{0}) - 1) * 4); + $start = $this->_uint32($file); + $after = $this->_uint32($file); + } + + return array($start, $after); + } + + /** + * Returns the version number of the index file, or 1 if there is no version + * information + * + * @return int + */ + private function _readVersion() + { + $file = fopen($this->path, 'rb'); + $magic = fread($file, 4); + $version = $this->_uint32($file); + + if ($magic !== self::INDEX_MAGIC) { + $version = 1; + } + + fclose($file); + return $version; + } + +} diff --git a/3rdparty/granite/git/object/loose.php b/3rdparty/granite/git/object/loose.php new file mode 100644 index 00000000000..32f894845b9 --- /dev/null +++ b/3rdparty/granite/git/object/loose.php @@ -0,0 +1,81 @@ +<?php +/** + * Loose - provides a 'loose object' object + * + * PHP version 5.3 + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Object; +use \UnexpectedValueException as UnexpectedValueException; + +/** + * Loose represents a loose object in the Git repository + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Loose extends Raw +{ + + /** + * Reads an object from a loose object file based on the SHA-1 id + * + * @param string $path The path to the repository root + * @param string $sha The SHA-1 id of the requested object + * + * @throws UnexpectedValueException If the type is not 'commit', 'tree', + * 'tag' or 'blob' + */ + public function __construct($path, $sha) + { + $this->sha = $sha; + + $loose_path = $path + . 'objects/' + . substr($sha, 0, 2) + . '/' + . substr($sha, 2); + + if (!file_exists($loose_path)) { + throw new InvalidArgumentException("Cannot open loose object file for $sha"); + } + + $raw = gzuncompress(file_get_contents($loose_path)); + $data = explode("\0", $raw, 2); + + $header = $data[0]; + $this->content = $data[1]; + + list($this->type, $this->size) = explode(' ', $header); + + switch ($this->type) { + case 'commit': + $this->type = Raw::OBJ_COMMIT; + break; + case 'tree': + $this->type = Raw::OBJ_TREE; + break; + case 'blob': + $this->type = Raw::OBJ_BLOB; + break; + case 'tag': + $this->type = Raw::OBJ_TAG; + break; + default: + throw new UnexpectedValueException( + "Unexpected type '{$this->type}'" + ); + break; + } + } + +} diff --git a/3rdparty/granite/git/object/packed.php b/3rdparty/granite/git/object/packed.php new file mode 100644 index 00000000000..7e8d663b32e --- /dev/null +++ b/3rdparty/granite/git/object/packed.php @@ -0,0 +1,304 @@ +<?php +/** + * Packed - provides a 'packed object' object + * + * PHP version 5.3 + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Object; +use \UnexpectedValueException as UnexpectedValueException; + +/** + * Packed represents a packed object in the Git repository + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Packed extends Raw +{ + + /** + * The name of the packfile being read + */ + private $_packfile; + + /** + * Added to the object size to make a 'best-guess' effort at how much compressed + * data to read - should be reimplemented, ideally with streams. + */ + const OBJ_PADDING = 512; + + /** + * Reads the object data from the compressed data at $offset in $packfile + * + * @param string $packfile The path to the packfile + * @param int $offset The offset of the object data + */ + public function __construct($packfile, $offset) + { + $this->_packfile = $packfile; + + list($this->type, $this->size, $this->content) + = $this->_readPackedObject($offset); + } + + /** + * Reads the object data at $this->_offset + * + * @param int $offset Offset of the object header + * + * @return array Containing the type, size and object data + */ + private function _readPackedObject($offset) + { + $file = fopen($this->_packfile, 'rb'); + fseek($file, $offset); + // Read the type and uncompressed size from the object header + list($type, $size) = $this->_readHeader($file, $offset); + $object_offset = ftell($file); + + if ($type == self::OBJ_OFS_DELTA || $type == self::OBJ_REF_DELTA) { + return $this->_unpackDeltified( + $file, $offset, $object_offset, $type, $size + ); + } + + $content = gzuncompress(fread($file, $size + self::OBJ_PADDING), $size); + + return array($type, $size, $content); + } + + /** + * Reads a packed object header, returning the type and the size. For more + * detailed information, refer to the @see tag. + * + * From the @see tag: "Each byte is really 7 bits of data, with the first bit + * being used to say if that hunk is the last one or not before the data starts. + * If the first bit is a 1, you will read another byte, otherwise the data starts + * next. The first 3 bits in the first byte specifies the type of data..." + * + * @param handle $file File handle to read + * @param int $offset Offset of the object header + * + * @return array Containing the type and the size + * @see http://book.git-scm.com/7_the_packfile.html + */ + private function _readHeader($file, $offset) + { + // Read the object header byte-by-byte + fseek($file, $offset); + $byte = ord(fgetc($file)); + /** + * Bit-shift right by four, then ignore the first bit with a bitwise AND + * This gives us the object type in binary: + * 001 commit self::OBJ_COMMIT + * 010 tree self::OBJ_TREE + * 011 blob self::OBJ_BLOB + * 100 tag self::OBJ_TAG + * 110 offset delta self::OBJ_OFS_DELTA + * 111 ref delta self::OBJ_REF_DELTA + * + * (000 is undefined, 101 is not currently in use) + * See http://book.git-scm.com/7_the_packfile.html for details + */ + $type = ($byte >> 4) & 0x07; + + // Read the last four bits of the first byte, used to find the size + $size = $byte & 0x0F; + + /** + * $shift initially set to four, since we use the last four bits of the first + * byte + * + * $byte & 0x80 checks the initial bit is set to 1 (i.e. keep reading data) + * + * Finally, $shift is incremented by seven for each consecutive byte (because + * we ignore the initial bit) + */ + for ($shift = 4; $byte & 0x80; $shift += 7) { + $byte = ord(fgetc($file)); + /** + * The size is ANDed against 0x7F to strip the initial bit, then + * bitshifted by left $shift (4 or 7, depending on whether it's the + * initial byte) and ORed against the existing binary $size. This + * continuously increments the $size variable. + */ + $size |= (($byte & 0x7F) << $shift); + } + + return array($type, $size); + } + + /** + * Unpacks a deltified object located at $offset in $file + * + * @param handle $file File handle to read + * @param int $offset Offset of the object data + * @param int $object_offset Offset of the object data, past the header + * @param int $type The object type, either OBJ_REF_DELTA + or OBJ_OFS_DELTA + * @param int $size The expected size of the uncompressed data + * + * @return array Containing the type, size and object data + */ + private function _unpackDeltified($file, $offset, $object_offset, $type, $size) + { + fseek($file, $object_offset); + + if ($type == self::OBJ_REF_DELTA) { + + $base_sha = bin2hex(fread($file, 20)); + + $path = substr($this->_packfile, 0, strpos($this->_packfile, '.git')+5); + $base = Raw::factory($path, $base_sha); + $type = $base->type(); + $base = $base->content(); + + $delta = gzuncompress( + fread($file, $size + self::OBJ_PADDING), $size + ); + + $content = $this->_applyDelta($base, $delta); + + } elseif ($type == self::OBJ_OFS_DELTA) { + + // 20 = maximum varint size according to Glip + $data = fread($file, $size + self::OBJ_PADDING + 20); + + list($base_offset, $length) = $this->_bigEndianNumber($data); + + $delta = gzuncompress(substr($data, $length), $size); + unset($data); + + $base_offset = $offset - $base_offset; + list($type, $size, $base) = $this->_readPackedObject($base_offset); + + $content = $this->_applyDelta($base, $delta); + + } else { + throw new UnexpectedValueException( + "Unknown type $type for deltified object" + ); + } + + return array($type, strlen($content), $content); + } + + /** + * Applies the $delta byte-sequence to $base and returns the + * resultant binary string. + * + * This code is modified from Grit (see below), the Ruby + * implementation used for GitHub under an MIT license. + * + * @param string $base The base string for the delta to be applied to + * @param string $delta The delta string to apply + * + * @return string The patched binary string + * @see + * https://github.com/mojombo/grit/blob/master/lib/grit/git-ruby/internal/pack.rb + */ + private function _applyDelta($base, $delta) + { + $pos = 0; + $src_size = $this->_varint($delta, $pos); + $dst_size = $this->_varint($delta, $pos); + + if ($src_size !== strlen($base)) { + throw new UnexpectedValueException( + 'Expected base delta size ' . strlen($base) . ' does not match the expected ' + . "value $src_size" + ); + } + + $dest = ""; + while ($pos < strlen($delta)) { + $byte = ord($delta{$pos++}); + + if ($byte & 0x80) { + /* copy a part of $base */ + $offset = 0; + if ($byte & 0x01) $offset = ord($delta{$pos++}); + if ($byte & 0x02) $offset |= ord($delta{$pos++}) << 8; + if ($byte & 0x04) $offset |= ord($delta{$pos++}) << 16; + if ($byte & 0x08) $offset |= ord($delta{$pos++}) << 24; + $length = 0; + if ($byte & 0x10) $length = ord($delta{$pos++}); + if ($byte & 0x20) $length |= ord($delta{$pos++}) << 8; + if ($byte & 0x40) $length |= ord($delta{$pos++}) << 16; + if ($length == 0) $length = 0x10000; + $dest .= substr($base, $offset, $length); + } else { + /* take the next $byte bytes as they are */ + $dest .= substr($delta, $pos, $byte); + $pos += $byte; + } + } + + if (strlen($dest) !== $dst_size) { + throw new UnexpectedValueException( + "Deltified string expected to be $dst_size bytes, but actually " + . strlen($dest) . ' bytes' + ); + } + + return $dest; + } + + /** + * Parse a Git varint (variable-length integer). Used in the `_applyDelta()` + * method to read the delta header. + * + * @param string $string The string to parse + * @param int &$pos The position in the string to read from + * + * @return int The integer value + */ + private function _varint($string, &$pos = 0) + { + $varint = 0; + $bitmask = 0x80; + for ($i = 0; $bitmask & 0x80; $i += 7) { + $bitmask = ord($string{$pos++}); + $varint |= (($bitmask & 0x7F) << $i); + } + return $varint; + } + + /** + * Decodes a big endian modified base 128 number (refer to @see tag); this only + * appears to be used in one place, the offset delta in packfiles. The offset + * is the number of bytes to seek back from the start of the delta object to find + * the base object. + * + * This code has been implemented using the C code given in the @see tag below. + * + * @param string &$data The data to read from and decode the number + * + * @return Array Containing the base offset (number of bytes to seek back) and + * the length to use when reading the delta + * @see http://git.rsbx.net/Documents/Git_Data_Formats.txt + */ + private function _bigEndianNumber(&$data) + { + $i = 0; + $byte = ord($data{$i++}); + $number = $byte & 0x7F; + while ($byte & 0x80) { + $byte = ord($data{$i++}); + $number = (($number + 1) << 7) | ($byte & 0x7F); + } + + return array($number, $i); + } + +} diff --git a/3rdparty/granite/git/object/raw.php b/3rdparty/granite/git/object/raw.php new file mode 100644 index 00000000000..56f363c37b2 --- /dev/null +++ b/3rdparty/granite/git/object/raw.php @@ -0,0 +1,153 @@ +<?php +/** + * Raw - provides a raw Git object + * + * PHP version 5.3 + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Object; +use \InvalidArgumentException as InvalidArgumentException; + +/** + * Raw represents a raw Git object, using Index to locate + * packed objects. + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Raw +{ + /** + * Integer values for Git objects + * @see http://book.git-scm.com/7_the_packfile.html + */ + const OBJ_COMMIT = 1; + const OBJ_TREE = 2; + const OBJ_BLOB = 3; + const OBJ_TAG = 4; + const OBJ_OFS_DELTA = 6; + const OBJ_REF_DELTA = 7; + + /** + * The SHA-1 id of the requested object + */ + protected $sha; + /** + * The type of the requested object (see class constants) + */ + protected $type; + /** + * The binary string content of the requested object + */ + protected $content; + + /** + * Returns an instance of a raw Git object + * + * @param string $path The path to the repository root + * @param string $sha The SHA-1 id of the requested object + * + * @return Packed|Loose + */ + public static function factory($path, $sha) + { + $loose_path = $path + . 'objects/' + . substr($sha, 0, 2) + . '/' + . substr($sha, 2); + if (file_exists($loose_path)) { + return new Loose($path, $sha); + } else { + return self::_findPackedObject($path, $sha); + } + } + + /** + * Returns the raw content of the Git object requested + * + * @return string Raw object content + */ + public function content() + { + return $this->content; + } + + /** + * Returns the size of the Git object + * + * @return int The size of the object in bytes + */ + public function size() + { + return strlen($this->content); + } + + /** + * Returns the type of the object as either commit, tag, blob or tree + * + * @return string The object type + */ + public function type() + { + return $this->type; + } + + /** + * Searches a packfile for the SHA id and reads the object from the packfile + * + * @param string $path The path to the repository + * @param string $sha The SHA-1 id of the object being requested + * + * @throws \InvalidArgumentException + * @return array An array containing the type, size and object data + */ + private static function _findPackedObject($path, $sha) + { + $packfiles = glob( + $path + . 'objects' + . DIRECTORY_SEPARATOR + . 'pack' + . DIRECTORY_SEPARATOR + . 'pack-*.pack' + ); + + $offset = false; + foreach ($packfiles as $packfile) { + $packname = substr(basename($packfile, '.pack'), 5); + $idx = new Index($path, $packname); + $offset = $idx->find($sha); + + if ($offset !== false) { + break; // Found it + } + } + + if ($offset == false) { + throw new InvalidArgumentException("Could not find packed object $sha"); + } + + $packname = $path + . 'objects' + . DIRECTORY_SEPARATOR + . 'pack' + . DIRECTORY_SEPARATOR + . 'pack-' . $packname . '.pack'; + $object = new Packed($packname, $offset); + + return $object; + } + +} + +?> diff --git a/3rdparty/granite/git/repository.php b/3rdparty/granite/git/repository.php new file mode 100644 index 00000000000..30b58a39f5a --- /dev/null +++ b/3rdparty/granite/git/repository.php @@ -0,0 +1,293 @@ +<?php +/** + * Repository - provides a 'repository' object with a set of helper methods + * + * PHP version 5.3 + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; +use \InvalidArgumentException as InvalidArgumentException; +use \UnexpectedValueException as UnexpectedValueException; + +/** + * Repository represents a Git repository, providing a variety of methods for + * fetching objects from SHA-1 ids or the tip of a branch with `head()` + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Repository +{ + + /** + * The path to the repository root + */ + private $_path; + /** + * The indexed version of a commit, ready to write with `commit()` + */ + private $idx_commit; + /** + * The indexed version of a tree, modified to with `add()` and `remove()` + */ + private $idx_tree; + + /** + * Sets the repository path + * + * @param string $path The path to the repository root (i.e. /repo/.git/) + */ + public function __construct($path) + { + if (!is_dir($path)) { + throw new InvalidArgumentException("Unable to find directory $path"); + } elseif (!is_readable($path)) { + throw new InvalidArgumentException("Unable to read directory $path"); + } elseif (!is_dir($path . DIRECTORY_SEPARATOR . 'objects') + || !is_dir($path . DIRECTORY_SEPARATOR . 'refs') + ) { + throw new UnexpectedValueException( + "Invalid directory, could not find 'objects' or 'refs' in $path" + ); + } + + $this->_path = $path; + $this->idx_commit = $this->factory('commit'); + $this->idx_tree = $this->factory('tree'); + } + + /** + * Returns an object from the Repository of the given type, with the given + * SHA-1 id, or false if it cannot be found + * + * @param string $type The type (blob, commit, tag or tree) of object being + * requested + * @param string $sha The SHA-1 id of the object (or the name of a tag) + * + * @return Blob|Commit|Tag|Tree + */ + public function factory($type, $sha = null) + { + if (!in_array($type, array('blob', 'commit', 'tag', 'tree'))) { + throw new InvalidArgumentException("Invalid type: $type"); + } + + if ($type == 'tag') { + $sha = $this->_ref('tags' . DIRECTORY_SEPARATOR . $sha); + } + $type = 'Granite\\Git\\' . ucwords($type); + + return new $type($this->_path, $sha); + } + + /** + * Returns a Commit object representing the HEAD commit + * + * @param string $branch The branch name to lookup, defaults to 'master' + * + * @return Commit An object representing the HEAD commit + */ + public function head($branch = 'master', $value = NULL) + { + if ($value == NULL) + return $this->factory( + 'commit', $this->_ref('heads' . DIRECTORY_SEPARATOR . $branch) + ); + + file_put_contents( + $this->_path . DIRECTORY_SEPARATOR + . 'refs' . DIRECTORY_SEPARATOR + . 'heads' . DIRECTORY_SEPARATOR . 'master', + $value + ); + } + + /** + * Returns a string representing the repository's location, which may or may + * not be initialised + * + * @return string A string representing the repository's location + */ + public function path() + { + return $this->_path; + } + + /** + * Returns an array of the local branches under `refs/heads` + * + * @return array + */ + public function tags() + { + return $this->_refs('tags'); + } + + /** + * Returns an array of the local tags under `refs/tags` + * + * @return array + */ + public function branches() + { + return $this->_refs('heads'); + } + + private function _refs($type) + { + $dir = $this->_path . 'refs' . DIRECTORY_SEPARATOR . $type; + $refs = glob($dir . DIRECTORY_SEPARATOR . '*'); + foreach ($refs as &$ref) { + $ref = basename($ref); + } + return $refs; + } + + /** + * Initialises a Git repository + * + * @return boolean Returns true on success, false on error + */ + public static function init($path) + { + $path .= '/'; + if (!is_dir($path)) { + mkdir($path); + } elseif (is_dir($path . 'objects')) { + return false; + } + + mkdir($path . 'objects'); + mkdir($path . 'objects/info'); + mkdir($path . 'objects/pack'); + mkdir($path . 'refs'); + mkdir($path . 'refs/heads'); + mkdir($path . 'refs/tags'); + + file_put_contents($path . 'HEAD', 'ref: refs/heads/master'); + + return true; + } + + /** + * Writes the indexed commit to disk, with blobs added/removed via `add()` and + * `rm()` + * + * @param string $message The commit message + * @param string $author The author name + * + * @return boolean True on success, or false on failure + */ + public function commit($message, $author) + { + $user_string = $username . ' ' . time() . ' +0000'; + + try { + $parents = array($this->repo->head()->sha()); + } catch (InvalidArgumentException $e) { + $parents = array(); + } + + $this->idx_commit->message($message); + $this->idx_commit->author($user_string); + $this->idx_commit->committer($user_string); + $this->idx_commit->tree($this->idx_tree); + $commit->parents($parents); + + $this->idx_tree->write(); + $this->idx_commit->write(); + + $this->repo->head('master', $this->idx_commit->sha()); + + $this->idx_commit = $this->factory('commit'); + $this->idx_tree = $this->factory('tree'); + } + + /** + * Adds a file to the indexed commit, to be written to disk with `commit()` + * + * @param string $filename The filename to save it under + * @param Granite\Git\Blob $blob The raw blob object to add to the tree + */ + public function add($filename, Granite\Git\Blob $blob) + { + $blob->write(); + $nodes = $this->idx_tree->nodes(); + $nodes[$filename] = new Granite\Git\Tree\Node($filename, '100644', $blob->sha()); + $this->idx_tree->nodes($nodes); + } + + /** + * Removes a file from the indexed commit + */ + public function rm($filename) + { + $nodes = $this->idx_tree->nodes(); + unset($nodes[$filename]); + $this->idx_tree->nodes($nodes); + } + + /** + * Returns an SHA-1 id of the ref resource + * + * @param string $ref The ref name to lookup + * + * @return string An SHA-1 id of the ref resource + */ + private function _ref($ref) + { + // All refs are stored in `.git/refs` + $file = $this->_path . 'refs' . DIRECTORY_SEPARATOR . $ref; + + if (file_exists($file)) { + return trim(file_get_contents($file)); + } + + $sha = $this->_packedRef($ref); + + if ($sha == false) { + throw new InvalidArgumentException("The ref $ref could not be found"); + } + + return $sha; + } + + /** + * Returns an SHA-1 id of the ref resource, or false if it cannot be found + * + * @param string $ref The ref name to lookup + * + * @return string An SHA-1 id of the ref resource + */ + private function _packedRef($ref) + { + $sha = false; + if (file_exists($this->_path . 'packed-refs')) { + $file = fopen($this->_path . 'packed-refs', 'r'); + + while (($line = fgets($file)) !== false) { + $info = explode(' ', $line); + if (count($info) == 2 + && trim($info[1]) == 'refs' . DIRECTORY_SEPARATOR . $ref + ) { + $sha = trim($info[0]); + break; + } + } + + fclose($file); + } + + return $sha; + } + +} diff --git a/3rdparty/granite/git/tag.php b/3rdparty/granite/git/tag.php new file mode 100644 index 00000000000..e26ddaffa6d --- /dev/null +++ b/3rdparty/granite/git/tag.php @@ -0,0 +1,38 @@ +<?php +/** + * Tag - provides a 'tag' object + * + * PHP version 5.3 + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; + +/** + * Tag represents a full tag object + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Tag +{ + + public function __construct($path, $sha) + { + $this->sha = $sha; + } + + public function sha() + { + return $this->sha; + } + +} diff --git a/3rdparty/granite/git/tree.php b/3rdparty/granite/git/tree.php new file mode 100644 index 00000000000..2de72274532 --- /dev/null +++ b/3rdparty/granite/git/tree.php @@ -0,0 +1,198 @@ +<?php +/** + * Tree - provides a 'tree' object + * + * PHP version 5.3 + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git; +use \Granite\Git\Tree\Node as Node; + +/** + * Tree represents a full tree object, with nodes pointing to other tree objects + * and file blobs + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Tree +{ + + /** + * The SHA-1 id of the requested tree + */ + private $sha; + /** + * The nodes/entries for the requested tree + */ + private $nodes = array(); + /** + * The path to the repository + */ + private $path; + + /** + * Reads a tree object by fetching the raw object + * + * @param string $path The path to the repository root + * @param string $sha The SHA-1 id of the requested object + */ + public function __construct($path, $sha = NULL, $dbg = FALSE) + { + $this->path = $path; + if ($sha !== NULL) { + $object = Object\Raw::factory($path, $sha); + $this->sha = $sha; + + if ($object->type() !== Object\Raw::OBJ_TREE) { + throw new \InvalidArgumentException( + "The object $sha is not a tree, type is " . $object->type() + ); + } + + $content = $object->content(); + file_put_contents('/tmp/tree_from_real_repo'.time(), $content); + $nodes = array(); + + for ($i = 0; $i < strlen($content); $i = $data_start + 21) { + $data_start = strpos($content, "\0", $i); + $info = substr($content, $i, $data_start-$i); + list($mode, $name) = explode(' ', $info, 2); + // Read the object SHA-1 id + $sha = bin2hex(substr($content, $data_start + 1, 20)); + + $this->nodes[$name] = new Node($name, $mode, $sha); + } + } + } + + /** + * Returns an array of Tree and Granite\Git\Blob objects, + * representing subdirectories and files + * + * @return array Array of Tree and Granite\Git\Blob objects + */ + public function nodes($nodes = null) + { + if ($nodes == null) { + return $this->nodes; + } + $this->nodes = $nodes; + } + + /** + * Adds a blob or a tree to the list of nodes + * + * @param string $name The basename (filename) of the blob or tree + * @param string $mode The mode of the blob or tree (see above) + * @param string $sha The SHA-1 id of the blob or tree to add + */ + public function add($name, $mode, $sha) + { + $this->nodes[$name] = new Node($name, $mode, $sha); + uasort($this->nodes, array($this, '_sort')); + } + + public function write() + { + $sha = $this->sha(); + $path = $this->path + . 'objects' + . DIRECTORY_SEPARATOR + . substr($sha, 0, 2) + . DIRECTORY_SEPARATOR + . substr($sha, 2); + // FIXME: currently writes loose objects only + if (file_exists($path)) { + return FALSE; + } + + if (!is_dir(dirname($path))) { + mkdir(dirname($path), 0777, TRUE); + } + + $loose = fopen($path, 'wb'); + $data = $this->_raw(); + $data = 'tree ' . strlen($data) . "\0" . $data; + $write = fwrite($loose, gzcompress($data)); + fclose($loose); + + return ($write !== FALSE); + } + + /** + * Returns the SHA-1 id of the Tree + * + * @return string SHA-1 id of the Tree + */ + public function sha() + { + $data = $this->_raw(); + $raw = 'tree ' . strlen($data) . "\0" . $data; + $this->sha = hash('sha1', $raw); + return $this->sha; + } + + /** + * Generates the raw object content to be saved to disk + */ + public function _raw() + { + uasort($this->nodes, array($this, '_sort')); + $data = ''; + foreach ($this->nodes as $node) + { + $data .= base_convert($node->mode(), 10, 8) . ' ' . $node->name() . "\0"; + $data .= pack('H40', $node->sha()); + } + file_put_contents('/tmp/tree_made'.time(), $data); + return $data; + } + + /** + * Sorts the node entries in a tree, general sort method adapted from original + * Git C code (see @see tag below). + * + * @return 1, 0 or -1 if the first entry is greater than, the same as, or less + * than the second, respectively. + * @see https://github.com/gitster/git/blob/master/read-cache.c Around line 352, + * the `base_name_compare` function + */ + public function _sort(&$a, &$b) + { + $length = strlen($a->name()) < strlen($b->name()) ? strlen($a->name()) : strlen($b->name()); + + $cmp = strncmp($a->name(), $b->name(), $length); + if ($cmp) { + return $cmp; + } + + $suffix1 = $a->name(); + $suffix1 = (strlen($suffix1) > $length) ? $suffix1{$length} : FALSE; + $suffix2 = $b->name(); + $suffix2 = (strlen($suffix2) > $length) ? $suffix2{$length} : FALSE; + if (!$suffix1 && $a->isDirectory()) { + $suffix1 = '/'; + } + if (!$suffix2 && $b->isDirectory()) { + $suffix2 = '/'; + } + if ($suffix1 < $suffix2) { + return -1; + } elseif ($suffix1 > $suffix2) { + return 1; + } + + return 0; + } + +} diff --git a/3rdparty/granite/git/tree/node.php b/3rdparty/granite/git/tree/node.php new file mode 100644 index 00000000000..f99eb1ae281 --- /dev/null +++ b/3rdparty/granite/git/tree/node.php @@ -0,0 +1,126 @@ +<?php +/** + * Node - provides a tree node object for tree entries + * + * PHP version 5.3 + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ + +namespace Granite\Git\Tree; + +/** + * Node represents an entry in a Tree + * + * @category Git + * @package Granite + * @author Craig Roberts <craig0990@googlemail.com> + * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License + * @link http://craig0990.github.com/Granite/ + */ +class Node +{ + + /** + * Name of the file, directory or submodule + */ + private $_name; + /** + * Mode of the object, in octal + */ + private $_mode; + /** + * SHA-1 id of the tree + */ + private $_sha; + /** + * Boolean value for whether the entry represents a directory + */ + private $_is_dir; + /** + * Boolean value for whether the entry represents a submodule + */ + private $_is_submodule; + + /** + * Sets up a Node class with properties corresponding to the $mode parameter + * + * @param string $name The name of the object (file, directory or submodule name) + * @param int $mode The mode of the object, retrieved from the repository + * @param string $sha The SHA-1 id of the object + */ + public function __construct($name, $mode, $sha) + { + $this->_name = $name; + $this->_mode = intval($mode, 8); + $this->_sha = $sha; + + $this->_is_dir = (bool) ($this->_mode & 0x4000); + $this->_is_submodule = ($this->_mode == 0xE000); + } + + /** + * Returns a boolean value indicating whether the node is a directory + * + * @return boolean + */ + public function isDirectory() + { + return $this->_is_dir; + } + + /** + * Returns a boolean value indicating whether the node is a submodule + * + * @return boolean + */ + public function isSubmodule() + { + return $this->_is_submodule; + } + + /** + * Returns the object name + * + * @return string + */ + public function name() + { + return $this->_name; + } + + /** + * Returns the object's SHA-1 id + * + * @return string + */ + public function sha() + { + return $this->_sha; + } + + /** + * Returns the octal value of the file mode + * + * @return int + */ + public function mode() + { + return $this->_mode; + } + + public function type() + { + if ($this->isDirectory()) { + return 'tree'; + } elseif ($this->isSubmodule()) { + return 'commit'; + } else { + return 'blob'; + } + } +} @@ -3,10 +3,11 @@ A personal cloud which runs on your own server. http://ownCloud.org -Installation instructions: http://owncloud.org/support/setup-and-installation/ -Source code: http://gitorious.org/owncloud +Installation instructions: http://owncloud.org/support +Source code: http://gitorious.org/owncloud Mailing list: http://mail.kde.org/mailman/listinfo/owncloud IRC channel: http://webchat.freenode.net/?channels=owncloud Diaspora: https://joindiaspora.com/u/owncloud Identi.ca: http://identi.ca/owncloud + diff --git a/apps/admin_export/appinfo/info.xml b/apps/admin_export/appinfo/info.xml deleted file mode 100644 index df8a07c2f5b..00000000000 --- a/apps/admin_export/appinfo/info.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0"?> -<info> - <id>admin_export</id> - <name>Import/Export</name> - <description>Import/Export your owncloud data</description> - <version>0.1</version> - <licence>AGPL</licence> - <author>Thomas Schmidt</author> - <require>2</require> - <default_enable/> -</info> diff --git a/apps/admin_export/settings.php b/apps/admin_export/settings.php deleted file mode 100644 index a33c872ccf4..00000000000 --- a/apps/admin_export/settings.php +++ /dev/null @@ -1,96 +0,0 @@ -<?php - -/** - * ownCloud - admin export - * - * @author Thomas Schmidt - * @copyright 2011 Thomas Schmidt tom@opensuse.org - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU AFFERO GENERAL PUBLIC LICENSE for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with this library. If not, see <http://www.gnu.org/licenses/>. - * - */ -OC_Util::checkAdminUser(); -OC_Util::checkAppEnabled('admin_export'); -if (isset($_POST['admin_export'])) { - $root = OC::$SERVERROOT . "/"; - $zip = new ZipArchive(); - $filename = get_temp_dir() . "/owncloud_export_" . date("y-m-d_H-i-s") . ".zip"; - OC_Log::write('admin_export',"Creating export file at: " . $filename,OC_Log::INFO); - if ($zip->open($filename, ZIPARCHIVE::CREATE) !== TRUE) { - exit("Cannot open <$filename>\n"); - } - - if (isset($_POST['owncloud_system'])) { - // adding owncloud system files - OC_Log::write('admin_export',"Adding owncloud system files to export",OC_Log::INFO); - zipAddDir($root, $zip, false); - foreach (array(".git", "3rdparty", "apps", "core", "files", "l10n", "lib", "ocs", "search", "settings", "tests") as $dirname) { - zipAddDir($root . $dirname, $zip, true, basename($root) . "/"); - } - } - - if (isset($_POST['owncloud_config'])) { - // adding owncloud config - // todo: add database export - OC_Log::write('admin_export',"Adding owncloud config to export",OC_Log::INFO); - zipAddDir($root . "config/", $zip, true, basename($root) . "/"); - $zip->addFile($root . '/data/.htaccess', basename($root) . "/data/owncloud.db"); - } - - if (isset($_POST['user_files'])) { - // adding user files - $zip->addFile($root . '/data/.htaccess', basename($root) . "/data/.htaccess"); - $zip->addFile($root . '/data/index.html', basename($root) . "/data/index.html"); - foreach (OC_User::getUsers() as $i) { - OC_Log::write('admin_export',"Adding owncloud user files of $i to export",OC_Log::INFO); - zipAddDir($root . "data/" . $i, $zip, true, basename($root) . "/data/"); - } - } - - $zip->close(); - - header("Content-Type: application/zip"); - header("Content-Disposition: attachment; filename=" . basename($filename)); - header("Content-Length: " . filesize($filename)); - @ob_end_clean(); - readfile($filename); - unlink($filename); -} else { -// fill template - $tmpl = new OC_Template('admin_export', 'settings'); - return $tmpl->fetchPage(); -} - -function zipAddDir($dir, $zip, $recursive=true, $internalDir='') { - $dirname = basename($dir); - $zip->addEmptyDir($internalDir . $dirname); - $internalDir.=$dirname.='/'; - - if ($dirhandle = opendir($dir)) { - while (false !== ( $file = readdir($dirhandle))) { - - if (( $file != '.' ) && ( $file != '..' )) { - - if (is_dir($dir . '/' . $file) && $recursive) { - zipAddDir($dir . '/' . $file, $zip, $recursive, $internalDir); - } elseif (is_file($dir . '/' . $file)) { - $zip->addFile($dir . '/' . $file, $internalDir . $file); - } - } - } - closedir($dirhandle); - } else { - OC_Log::write('admin_export',"Was not able to open directory: " . $dir,OC_Log::ERROR); - } -} diff --git a/apps/admin_export/templates/settings.php b/apps/admin_export/templates/settings.php deleted file mode 100644 index 47689facbbc..00000000000 --- a/apps/admin_export/templates/settings.php +++ /dev/null @@ -1,13 +0,0 @@ -<form id="export" action="#" method="post"> - <fieldset class="personalblock"> - <legend><strong><?php echo $l->t('Export this ownCloud instance');?></strong></legend> - <p><?php echo $l->t('This will create a compressed file that contains the data of this owncloud instance. - Please choose which components should be included:');?> - </p> - <p><input type="checkbox" id="user_files" name="user_files" value="true"><label for="user_files"><?php echo $l->t('User files');?></label><br/> - <input type="checkbox" id="owncloud_system" name="owncloud_system" value="true"><label for="owncloud_system"><?php echo $l->t('ownCloud system files');?></label><br/> - <input type="checkbox" id="owncloud_config" name="owncloud_config" value="true"><label for="owncloud_config"><?php echo $l->t('ownCloud configuration');?></label> - </p> - <input type="submit" name="admin_export" value="Export" /> - </fieldset> -</form> diff --git a/apps/admin_export/appinfo/app.php b/apps/admin_migrate/appinfo/app.php index beebb4864e9..e45d3f6a529 100644 --- a/apps/admin_export/appinfo/app.php +++ b/apps/admin_migrate/appinfo/app.php @@ -1,10 +1,10 @@ <?php /** -* ownCloud - user_ldap +* ownCloud - admin_migrate * -* @author Dominik Schmidt -* @copyright 2011 Dominik Schmidt dev@dominik-schmidt.de +* @author Tom Needham +* @copyright 2012 Tom Needham tom@owncloud.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE @@ -22,12 +22,12 @@ */ -OC_APP::registerAdmin('admin_export','settings'); +OC_APP::registerAdmin('admin_migrate','settings'); // add settings page to navigation $entry = array( - 'id' => "admin_export_settings", + 'id' => "admin_migrate_settings", 'order'=>1, - 'href' => OC_Helper::linkTo( "admin_export", "settings.php" ), + 'href' => OC_Helper::linkTo( "admin_migrate", "settings.php" ), 'name' => 'Export' ); diff --git a/apps/admin_migrate/appinfo/info.xml b/apps/admin_migrate/appinfo/info.xml new file mode 100644 index 00000000000..67fc3f9c5a0 --- /dev/null +++ b/apps/admin_migrate/appinfo/info.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> +<info> + <id>admin_migrate</id> + <name>ownCloud Instance Migration</name> + <description>Import/Export your owncloud instance</description> + <version>0.1</version> + <licence>AGPL</licence> + <author>Thomas Schmidt and Tom Needham</author> + <require>2</require> + <default_enable/> +</info> diff --git a/apps/admin_migrate/settings.php b/apps/admin_migrate/settings.php new file mode 100644 index 00000000000..94bf6052a6b --- /dev/null +++ b/apps/admin_migrate/settings.php @@ -0,0 +1,57 @@ +<?php + +/** + * ownCloud - admin_migrate + * + * @author Thomas Schmidt + * @copyright 2011 Thomas Schmidt tom@opensuse.org + * @author Tom Needham + * @copyright 2012 Tom Needham tom@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ +OC_Util::checkAdminUser(); +OC_Util::checkAppEnabled('admin_migrate'); + +// Export? +if (isset($_POST['admin_export'])) { + // Create the export zip + $response = json_decode( OC_Migrate::export( null, $_POST['export_type'] ) ); + if( !$response->success ){ + // Error + die('error'); + } else { + $path = $response->data; + // Download it + header("Content-Type: application/zip"); + header("Content-Disposition: attachment; filename=" . basename($path)); + header("Content-Length: " . filesize($path)); + @ob_end_clean(); + readfile( $path ); + unlink( $path ); + } +// Import? +} else if( isset($_POST['admin_import']) ){ + $from = $_FILES['owncloud_import']['tmp_name']; + + if( !OC_Migrate::import( $from, 'instance' ) ){ + die('failed'); + } + +} else { +// fill template + $tmpl = new OC_Template('admin_migrate', 'settings'); + return $tmpl->fetchPage(); +}
\ No newline at end of file diff --git a/apps/admin_migrate/templates/settings.php b/apps/admin_migrate/templates/settings.php new file mode 100644 index 00000000000..91e305074e4 --- /dev/null +++ b/apps/admin_migrate/templates/settings.php @@ -0,0 +1,31 @@ +<form id="export" action="#" method="post"> + <fieldset class="personalblock"> + <legend><strong><?php echo $l->t('Export this ownCloud instance');?></strong></legend> + <p><?php echo $l->t('This will create a compressed file that contains the data of this owncloud instance. + Please choose the export type:');?> + </p> + <h3>What would you like to export?</h3> + <p> + <input type="radio" name="export_type" value="instance" /> ownCloud instance (suitable for import )<br /> + <input type="radio" name="export_type" value="system" /> ownCloud system files<br /> + <input type="radio" name="export_type" value="userfiles" /> Just user files<br /> + <input type="submit" name="admin_export" value="<?php echo $l->t('Export'); ?>" /> + </fieldset> +</form> +<?php +/* + * EXPERIMENTAL +?> +<form id="import" action="#" method="post" enctype="multipart/form-data"> + <fieldset class="personalblock"> + <legend><strong><?php echo $l->t('Import an ownCloud instance. THIS WILL DELETE ALL CURRENT OWNCLOUD DATA');?></strong></legend> + <p><?php echo $l->t('All current ownCloud data will be replaced by the ownCloud instance that is uploaded.');?> + </p> + <p><input type="file" id="owncloud_import" name="owncloud_import"><label for="owncloud_import"><?php echo $l->t('ownCloud Export Zip File');?></label> + </p> + <input type="submit" name="admin_import" value="<?php echo $l->t('Import'); ?>" /> + </fieldset> +</form> +<?php +*/ +?> diff --git a/apps/bookmarks/appinfo/app.php b/apps/bookmarks/appinfo/app.php index 09d7b5df525..b9c308ca053 100644 --- a/apps/bookmarks/appinfo/app.php +++ b/apps/bookmarks/appinfo/app.php @@ -17,4 +17,5 @@ OC_App::addNavigationEntry( array( 'id' => 'bookmarks_index', 'order' => 70, 'hr OC_App::registerPersonal('bookmarks', 'settings'); OC_Util::addScript('bookmarks','bookmarksearch'); + OC_Search::registerProvider('OC_Search_Provider_Bookmarks'); diff --git a/apps/bookmarks/appinfo/migrate.php b/apps/bookmarks/appinfo/migrate.php new file mode 100644 index 00000000000..c1251e816ec --- /dev/null +++ b/apps/bookmarks/appinfo/migrate.php @@ -0,0 +1,68 @@ +<?php +class OC_Migration_Provider_Bookmarks extends OC_Migration_Provider{ + + // Create the xml for the user supplied + function export( ){ + $options = array( + 'table'=>'bookmarks', + 'matchcol'=>'user_id', + 'matchval'=>$this->uid, + 'idcol'=>'id' + ); + $ids = $this->content->copyRows( $options ); + + $options = array( + 'table'=>'bookmarks_tags', + 'matchcol'=>'bookmark_id', + 'matchval'=>$ids + ); + + // Export tags + $ids2 = $this->content->copyRows( $options ); + + // If both returned some ids then they worked + if( is_array( $ids ) && is_array( $ids2 ) ) + { + return true; + } else { + return false; + } + + } + + // Import function for bookmarks + function import( ){ + switch( $this->appinfo->version ){ + default: + // All versions of the app have had the same db structure, so all can use the same import function + $query = $this->content->prepare( "SELECT * FROM bookmarks WHERE user_id LIKE ?" ); + $results = $query->execute( array( $this->olduid ) ); + $idmap = array(); + while( $row = $results->fetchRow() ){ + // Import each bookmark, saving its id into the map + $query = OC_DB::prepare( "INSERT INTO *PREFIX*bookmarks(url, title, user_id, public, added, lastmodified) VALUES (?, ?, ?, ?, ?, ?)" ); + $query->execute( array( $row['url'], $row['title'], $this->uid, $row['public'], $row['added'], $row['lastmodified'] ) ); + // Map the id + $idmap[$row['id']] = OC_DB::insertid(); + } + // Now tags + foreach($idmap as $oldid => $newid){ + $query = $this->content->prepare( "SELECT * FROM bookmarks_tags WHERE bookmark_id LIKE ?" ); + $results = $query->execute( array( $oldid ) ); + while( $row = $results->fetchRow() ){ + // Import the tags for this bookmark, using the new bookmark id + $query = OC_DB::prepare( "INSERT INTO *PREFIX*bookmarks_tags(bookmark_id, tag) VALUES (?, ?)" ); + $query->execute( array( $newid, $row['tag'] ) ); + } + } + // All done! + break; + } + + return true; + } + +} + +// Load the provider +new OC_Migration_Provider_Bookmarks( 'bookmarks' );
\ No newline at end of file diff --git a/apps/contacts/ajax/addcontact.php b/apps/contacts/ajax/addcontact.php index 839a3919981..68da54655ae 100644 --- a/apps/contacts/ajax/addcontact.php +++ b/apps/contacts/ajax/addcontact.php @@ -39,13 +39,14 @@ foreach ($_POST as $key=>$element) { debug('_POST: '.$key.'=>'.$element); } -$aid = $_POST['aid']; +$aid = isset($_POST['aid'])?$_POST['aid']:null; +if(!$aid) { + $aid = min(OC_Contacts_Addressbook::activeIds()); // first active addressbook. +} OC_Contacts_App::getAddressbook( $aid ); // is owner access check $fn = trim($_POST['fn']); $n = trim($_POST['n']); -debug('N: '.$n); -debug('FN: '.$fn); $vcard = new OC_VObject('VCARD'); $vcard->setUID(); diff --git a/apps/contacts/ajax/saveproperty.php b/apps/contacts/ajax/saveproperty.php index 924d873652c..99d55e7927a 100644 --- a/apps/contacts/ajax/saveproperty.php +++ b/apps/contacts/ajax/saveproperty.php @@ -96,40 +96,40 @@ switch($element) { //$value = getOtherValue(); } break; - case 'CATEGORIES': - /* multi autocomplete triggers an save with empty value */ + //case 'CATEGORIES': + /* multi autocomplete triggers an save with empty value if (!$value) { $value = $vcard->getAsString('CATEGORIES'); } - break; + break;*/ case 'EMAIL': $value = strtolower($value); break; } if(!$value) { - bailOut(OC_Contacts_App::$l10n->t('Cannot save empty value.')); -} - -/* setting value */ -switch($element) { - case 'BDAY': - case 'FN': - case 'N': - case 'ORG': - case 'NOTE': - case 'NICKNAME': - case 'CATEGORIES': - debug('Setting string:'.$name.' '.$value); - $vcard->setString($name, $value); - break; - case 'EMAIL': - case 'TEL': - case 'ADR': // should I delete the property if empty or throw an error? - debug('Setting element: (EMAIL/TEL/ADR)'.$element); - if(!$value) { - unset($vcard->children[$line]); // Should never happen... - } else { + unset($vcard->children[$line]); + $checksum = ''; +} else { + /* setting value */ + switch($element) { + case 'BDAY': + case 'FN': + case 'N': + case 'ORG': + case 'NOTE': + case 'NICKNAME': + debug('Setting string:'.$name.' '.$value); + $vcard->setString($name, $value); + break; + case 'CATEGORIES': + debug('Setting string:'.$name.' '.$value); + $vcard->children[$line]->setValue($value); + break; + case 'EMAIL': + case 'TEL': + case 'ADR': // should I delete the property if empty or throw an error? + debug('Setting element: (EMAIL/TEL/ADR)'.$element); $vcard->children[$line]->setValue($value); $vcard->children[$line]->parameters = array(); if(!is_null($parameters)) { @@ -142,12 +142,12 @@ switch($element) { } } } - } - break; + break; + } + // Do checksum and be happy + $checksum = md5($vcard->children[$line]->serialize()); } -// Do checksum and be happy -$checksum = md5($vcard->children[$line]->serialize()); -debug('New checksum: '.$checksum); +//debug('New checksum: '.$checksum); if(!OC_Contacts_VCard::edit($id,$vcard)) { bailOut(OC_Contacts_App::$l10n->t('Error updating contact property.')); diff --git a/apps/contacts/appinfo/migrate.php b/apps/contacts/appinfo/migrate.php new file mode 100644 index 00000000000..a6c6bc20fa4 --- /dev/null +++ b/apps/contacts/appinfo/migrate.php @@ -0,0 +1,68 @@ +<?php +class OC_Migration_Provider_Contacts extends OC_Migration_Provider{ + + // Create the xml for the user supplied + function export( ){ + $options = array( + 'table'=>'contacts_addressbooks', + 'matchcol'=>'userid', + 'matchval'=>$this->uid, + 'idcol'=>'id' + ); + $ids = $this->content->copyRows( $options ); + + $options = array( + 'table'=>'contacts_cards', + 'matchcol'=>'addressbookid', + 'matchval'=>$ids + ); + + // Export tags + $ids2 = $this->content->copyRows( $options ); + + // If both returned some ids then they worked + if( is_array( $ids ) && is_array( $ids2 ) ) + { + return true; + } else { + return false; + } + + } + + // Import function for bookmarks + function import( ){ + switch( $this->appinfo->version ){ + default: + // All versions of the app have had the same db structure, so all can use the same import function + $query = $this->content->prepare( "SELECT * FROM contacts_addressbooks WHERE userid LIKE ?" ); + $results = $query->execute( array( $this->olduid ) ); + $idmap = array(); + while( $row = $results->fetchRow() ){ + // Import each bookmark, saving its id into the map + $query = OC_DB::prepare( "INSERT INTO *PREFIX*contacts_addressbooks (`userid`, `displayname`, `uri`, `description`, `ctag`) VALUES (?, ?, ?, ?, ?)" ); + $query->execute( array( $this->uid, $row['displayname'], $row['uri'], $row['description'], $row['ctag'] ) ); + // Map the id + $idmap[$row['id']] = OC_DB::insertid(); + } + // Now tags + foreach($idmap as $oldid => $newid){ + $query = $this->content->prepare( "SELECT * FROM contacts_cards WHERE addressbookid LIKE ?" ); + $results = $query->execute( array( $oldid ) ); + while( $row = $results->fetchRow() ){ + // Import the tags for this bookmark, using the new bookmark id + $query = OC_DB::prepare( "INSERT INTO *PREFIX*contacts_cards (`addressbookid`, `fullname`, `carddata`, `uri`, `lastmodified`) VALUES (?, ?, ?, ?, ?)" ); + $query->execute( array( $newid, $row['fullname'], $row['carddata'], $row['uri'], $row['lastmodified'] ) ); + } + } + // All done! + break; + } + + return true; + } + +} + +// Load the provider +new OC_Migration_Provider_Contacts( 'contacts' );
\ No newline at end of file diff --git a/apps/contacts/css/contacts.css b/apps/contacts/css/contacts.css index 76b5972ba3c..2d207943841 100644 --- a/apps/contacts/css/contacts.css +++ b/apps/contacts/css/contacts.css @@ -13,22 +13,26 @@ #contacts_propertymenu li a { padding: 3px; display: block } #contacts_propertymenu li:hover { background-color: #1d2d44; } #contacts_propertymenu li a:hover { color: #fff } -#actionbar { height: 30px; width: 200px; position: fixed; right: 0px; top: 75px; margin: 0 0 0 0; padding: 0 0 0 0;} -#card { /*max-width: 70em; border: thin solid lightgray; display: block;*/ } +#actionbar { height: 30px; width: 200px; position: fixed; right: 0px; top: 75px; margin: 0 0 0 0; padding: 0 0 0 0; z-index: 1000; } +#card { width: auto;/*max-width: 70em; border: thin solid lightgray; display: block;*/ } #firstrun { width: 100%; position: absolute; top: 5em; left: 0; text-align: center; font-weight:bold; font-size:1.5em; color:#777; } #firstrun #selections { font-size:0.8em; margin: 2em auto auto auto; clear: both; } -#card input[type="text"].contacts_property,input[type="email"].contacts_property { width: 14em; } +#card input[type="text"].contacts_property,input[type="email"].contacts_property { width: 14em; float: left; } .categories { float: left; width: 16em; } -#card input[type="text"],input[type="email"],input[type="tel"],input[type="date"], select { background-color: #f8f8f8; border: 0 !important; -webkit-appearance:none !important; -moz-appearance:none !important; -webkit-box-sizing:none !important; -moz-box-sizing:none !important; box-sizing:none !important; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; float: left; } -#card input[type="text"]:hover, input[type="text"]:focus, input[type="text"]:active,input[type="email"]:hover,input[type="tel"]:hover,input[type="date"]:hover,input[type="date"],input[type="date"]:hover,input[type="date"]:active,input[type="date"]:active,input[type="date"]:active,input[type="email"]:active,input[type="tel"]:active, select:hover, select:focus, select:active { border: 0 !important; -webkit-appearance:textfield; -moz-appearance:textfield; -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; background:#fff; color:#333; border:1px solid #ddd; -moz-box-shadow:0 1px 1px #fff, 0 2px 0 #bbb inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; outline:none; float: left; } -input[type="text"]:invalid,input[type="email"]:invalid,input[type="tel"]:invalid,input[type="date"]:invalid { background-color: #ffc0c0 !important; } +#card input[type="text"],input[type="email"],input[type="tel"],input[type="date"], select, textarea { background-color: #fefefe; border: 0 !important; -webkit-appearance:none !important; -moz-appearance:none !important; -webkit-box-sizing:none !important; -moz-box-sizing:none !important; box-sizing:none !important; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; float: left; } +#card input[type="text"]:hover, input[type="text"]:focus, input[type="text"]:active,input[type="email"]:hover,input[type="tel"]:hover,input[type="date"]:hover,input[type="date"],input[type="date"]:hover,input[type="date"]:active,input[type="date"]:active,input[type="date"]:active,input[type="email"]:active,input[type="tel"]:active, select:hover, select:focus, select:active, textarea:focus, textarea:hover { border: 0 !important; -webkit-appearance:textfield; -moz-appearance:textfield; -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; background:#fff; color:#333; border:1px solid #ddd; -moz-box-shadow:0 1px 1px #fff, 0 2px 0 #bbb inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; outline:none; float: left; } +input[type="text"]:invalid,input[type="email"]:invalid,input[type="tel"]:invalid,input[type="date"]:invalid, textarea:invalid { color: #bbb !important; } +textarea { min-height: 4em; } dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; } -.form dt { display: table-cell; clear: left; float: left; width: 7em; margin: 0; padding: 0.8em 0.5em 0 0; font-weight: bold; text-align:right; text-overflow:ellipsis; o-text-overflow: ellipsis; vertical-align: text-bottom;/* white-space: pre-wrap; white-space: -moz-pre-wrap !important; white-space: -pre-wrap; white-space: -o-pre-wrap;*/ } +.form dt { display: table-cell; clear: left; float: left; width: 7em; margin: 0; padding: 0.8em 0.5em 0 0; text-align:right; text-overflow:ellipsis; o-text-overflow: ellipsis; vertical-align: text-bottom; color: #bbb;/* white-space: pre-wrap; white-space: -moz-pre-wrap !important; white-space: -pre-wrap; white-space: -o-pre-wrap;*/ } .form dd { display: table-cell; clear: right; float: left; margin: 0; padding: 0px; white-space: nowrap; vertical-align: text-bottom; } +#address.form dt { min-width: 5em; } +#address.form dl { min-width: 10em; } .loading { background: url('../../../core/img/loading.gif') no-repeat center !important; /*cursor: progress; */ cursor: wait; } - +.ui-autocomplete-loading { background: url('../../../core/img/loading.gif') right center no-repeat; } +.float { float: left; } .listactions { height: 1em; width:60px; float: left; clear: right; } .add,.edit,.delete,.mail, .globe { cursor: pointer; width: 20px; height: 20px; margin: 0; float: left; position:relative; display: none; } .add { background:url('../../../core/img/actions/add.svg') no-repeat center; clear: both; } @@ -43,18 +47,18 @@ dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; } #edit_address_dialog { /*width: 30em;*/ } #edit_address_dialog > input { width: 15em; } #edit_photo_dialog_img { display: block; width: 150; height: 200; border: thin solid black; } -#fn { float: left; } -/** - * Create classes form, floateven and floatodd which flows left and right respectively. - */ -.contactsection { float: left; min-width: 30em; max-width: 40em; margin: 0.5em; border: thin solid lightgray; -webkit-border-radius: 0.5em; -moz-border-radius: 0.5em; border-radius: 0.5em; background-color: #f8f8f8; } +#fn { float: left !important; width: 18em !important; } +#name { /*position: absolute; top: 0px; left: 0px;*/ min-width: 25em; height: 2em; clear: right; display: block; } +#identityprops { /*position: absolute; top: 2.5em; left: 0px;*/ } +/*#contact_photo { max-width: 250px; }*/ +#contact_identity { min-width: 30em; } +.contactsection { position: relative; float: left; /*max-width: 40em;*/ padding: 0.5em; height: auto: border: thin solid lightgray;/* -webkit-border-radius: 0.5em; -moz-border-radius: 0.5em; border-radius: 0.5em; background-color: #f8f8f8;*/ } .contactpart legend { width:auto; padding:.3em; border:1px solid #ddd; font-weight:bold; cursor:pointer; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; } #cropbox { margin: auto; } - -#contacts_details_photo { border-radius: 0.5em; border: thin solid #bbb; padding: 0.5em; margin: 1em 1em 1em 7em; cursor: pointer; background: url(../../../core/img/loading.gif) no-repeat center center; clear: right; } +#contacts_details_photo { border-radius: 0.5em; border: thin solid #bbb; margin: 0.3em; cursor: pointer; background: url(../../../core/img/loading.gif) no-repeat center center; display: block; /* clear: right;*/ } #contacts_details_photo:hover { background: #fff; } -#contacts_details_photo_progress { margin: 0.3em 0.3em 0.3em 7em; clear: left; } +/*#contacts_details_photo_progress { margin: 0.3em 0.3em 0.3em 7em; clear: left; }*/ /* Address editor */ #addressdisplay { padding: 0.5em; } dl.addresscard { background-color: #fff; float: left; width: 45%; margin: 0 0.3em 0.3em 0.3em; padding: 0; border: thin solid lightgray; } @@ -72,8 +76,10 @@ dl.addresscard dd > ul { margin: 0.3em; padding: 0.3em; } #file_upload_target, #crop_target { display:none; } -#file_upload_start { opacity:0; filter:alpha(opacity=0); z-index:1; position:absolute; left:0; top:0; cursor:pointer; width:0; height:0;} +#file_upload_start { opacity:0; filter:alpha(opacity=0); z-index:1; /*position:absolute; left:0; top:0;*/ cursor:pointer; width:0; height:0;} input[type="checkbox"] { width: 20px; height: 20px; vertical-align: bottom; } +.big { font-weight:bold; font-size:1.2em; } +.huge { font-weight:bold; font-size:1.5em; } .propertycontainer dd { float: left; width: 25em; } .propertylist { clear: none; max-width: 28em; } .propertylist li { /*background-color: cyan; */ min-width: 25em; /*max-width: 30em;*/ display: block; clear: right; } diff --git a/apps/contacts/img/person_large.png b/apps/contacts/img/person_large.png Binary files differindex f57511e1000..4edba0c5489 100644 --- a/apps/contacts/img/person_large.png +++ b/apps/contacts/img/person_large.png diff --git a/apps/contacts/index.php b/apps/contacts/index.php index 04f6c65a145..776c57ca605 100644 --- a/apps/contacts/index.php +++ b/apps/contacts/index.php @@ -34,7 +34,26 @@ if(!is_null($id)) { } $property_types = OC_Contacts_App::getAddPropertyOptions(); $phone_types = OC_Contacts_App::getTypesOfProperty('TEL'); -$categories = OC_Contacts_App::$categories->categories(); +$categories = OC_Contacts_App::getCategories(); +if(count($categories) == 0) { + $vcaddressbooks = OC_Contacts_Addressbook::all(OC_User::getUser()); + if(count($vcaddressbooks) > 0) { + $vcaddressbookids = array(); + foreach($vcaddressbooks as $vcaddressbook) { + $vcaddressbookids[] = $vcaddressbook['id']; + } + $vccontacts = OC_Contacts_VCard::all($vcaddressbookids); + if(count($vccontacts) > 0) { + $cards = array(); + foreach($vccontacts as $vccontact) { + $cards[] = $vccontact['carddata']; + } + + OC_Contacts_App::$categories->rescan($cards); + $categories = OC_Contacts_App::$categories->categories(); + } + } +} $upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize')); $post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size')); @@ -61,7 +80,6 @@ $tmpl = new OC_Template( "contacts", "index", "user" ); $tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); $tmpl->assign('uploadMaxHumanFilesize', OC_Helper::humanFileSize($maxUploadFilesize)); $tmpl->assign('property_types',$property_types); -$tmpl->assign('categories',OC_Contacts_App::getCategories()); $tmpl->assign('phone_types',$phone_types); $tmpl->assign('categories',$categories); $tmpl->assign('addressbooks', $addressbooks); diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index d314878cc0a..6f34a42a73f 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -65,7 +65,7 @@ Contacts={ propertyTypeFor:function(obj) { return $(obj).parents('.propertycontainer').first().data('element'); }, - showHideContactInfo:function() { + /*showHideContactInfo:function() { var show = ($('#emaillist li.propertycontainer').length > 0 || $('#phonelist li.propertycontainer').length > 0 || $('#addressdisplay dl.propertycontainer').length > 0); console.log('showHideContactInfo: ' + show); if(show) { @@ -73,8 +73,8 @@ Contacts={ } else { $('#contact_communication').hide(); } - }, - checkListFor:function(obj) { + },*/ + /*checkListFor:function(obj) { var type = $(obj).parents('.propertycontainer').first().data('element'); console.log('checkListFor: ' + type); switch (type) { @@ -101,7 +101,7 @@ Contacts={ case 'BDAY': break; } - }, + },*/ loading:function(obj, state) { if(state) { $(obj).addClass('loading'); @@ -116,7 +116,7 @@ Contacts={ }, loadListHandlers:function() { //$('.add,.delete').hide(); - $('.globe,.mail,.delete,.edit').tipsy(); + $('.globe,.mail,.delete,.edit,.tip').tipsy(); $('.addresscard,.propertylist li,.propertycontainer').hover( function () { $(this).find('.globe,.mail,.delete,.edit').fadeIn(100); @@ -137,18 +137,14 @@ Contacts={ $(this).find('.add').fadeOut(500); } );*/ - $('.button,.action').tipsy(); - $('#contacts_deletecard').tipsy({gravity: 'ne'}); - $('#contacts_downloadcard').tipsy({gravity: 'ne'}); //$('#fn').jec(); $('#fn_select').combobox({ 'id': 'fn', 'name': 'value', - 'classes': ['contacts_property'], + 'classes': ['contacts_property', 'huge', 'tip', 'float'], + 'attributes': {'placeholder': t('contacts', 'Enter name')}, 'title': t('contacts', 'Format custom, Short name, Full name, Reverse or Reverse with comma')}); //$('.jecEditableOption').attr('title', t('contacts','Custom')); - $('#fn').tipsy(); - $('#contacts_details_photo_wrapper').tipsy(); $('#bday').datepicker({ dateFormat : 'dd-mm-yy' }); @@ -175,10 +171,6 @@ Contacts={ // Contacts.UI.Card.editAddress(); // return false; // }); - $('#n').click(function(){ - Contacts.UI.Card.editName(); - //return false; - }); $('#edit_name').click(function(){ Contacts.UI.Card.editName(); return false; @@ -200,6 +192,9 @@ Contacts={ } ] ); $('#categories').multiple_autocomplete({source: categories}); + $('.button,.action,.tip').tipsy(); + $('#contacts_deletecard').tipsy({gravity: 'ne'}); + $('#contacts_downloadcard').tipsy({gravity: 'ne'}); Contacts.UI.loadListHandlers(); }, Card:{ @@ -259,16 +254,28 @@ Contacts={ }); } }, - export:function() { + doExport:function() { document.location.href = OC.linkTo('contacts', 'export.php') + '?contactid=' + this.id; //$.get(OC.linkTo('contacts', 'export.php'),{'contactid':this.id},function(jsondata){ //}); }, - import:function(){ + doImport:function(){ Contacts.UI.notImplemented(); }, - add:function(n, fn, aid){ // add a new contact + add:function(n, fn, aid, isnew){ // add a new contact console.log('Add contact: ' + n + ', ' + fn + ' ' + aid); + var card = $('#card')[0]; + if(!card) { + console.log('Loading proper card DOM'); + $.getJSON(OC.filePath('contacts', 'ajax', 'loadcard.php'),{},function(jsondata){ + if(jsondata.status == 'success'){ + $('#rightcontent').html(jsondata.data.page); + Contacts.UI.loadHandlers(); + } else{ + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + } + }); + } $.post(OC.filePath('contacts', 'ajax', 'addcontact.php'), { n: n, fn: fn, aid: aid }, function(jsondata) { if (jsondata.status == 'success'){ @@ -291,7 +298,15 @@ Contacts={ if(!added) { $('#leftcontent ul').append(item); } - + if(isnew) { + Contacts.UI.Card.addProperty('EMAIL'); + Contacts.UI.Card.addProperty('TEL'); + Contacts.UI.Card.addProperty('NICKNAME'); + Contacts.UI.Card.addProperty('ORG'); + Contacts.UI.Card.addProperty('CATEGORIES'); + $('#fn').focus(); + $('#fn').select(); + } } else{ OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); @@ -308,7 +323,7 @@ Contacts={ } }); }, - delete:function() { + doDelete:function() { $('#contacts_deletecard').tipsy('hide'); OC.dialogs.confirm(t('contacts', 'Are you sure you want to delete this contact?'), t('contacts', 'Warning'), function(answer) { if(answer == true) { @@ -356,7 +371,7 @@ Contacts={ return false; }, loadContact:function(jsondata){ - $('#contact_communication').hide(); + //$('#contact_communication').hide(); this.data = jsondata; this.id = this.data.id; $('#rightcontent').data('id',this.id); @@ -368,7 +383,6 @@ Contacts={ this.loadPhones(); this.loadAddresses(); this.loadSingleProperties(); - // TODO: load NOTE ;-) if(this.data.NOTE) { $('#note').data('checksum', this.data.NOTE[0]['checksum']); $('#note').find('textarea').val(this.data.NOTE[0]['value']); @@ -376,7 +390,7 @@ Contacts={ } else { $('#note').data('checksum', ''); $('#note').find('textarea').val(''); - $('#note').hide(); + //$('#note').hide(); } }, loadSingleProperties:function() { @@ -521,17 +535,18 @@ Contacts={ },*/ editNew:function(){ // add a new contact this.id = ''; this.fn = ''; this.fullname = ''; this.givname = ''; this.famname = ''; this.addname = ''; this.honpre = ''; this.honsuf = ''; - $.getJSON(OC.filePath('contacts', 'ajax', 'newcontact.php'),{},function(jsondata){ + Contacts.UI.Card.add(';;;;', '', '', true); + /*$.getJSON(OC.filePath('contacts', 'ajax', 'newcontact.php'),{},function(jsondata){ if(jsondata.status == 'success'){ id = ''; $('#rightcontent').data('id',''); $('#rightcontent').html(jsondata.data.page); - Contacts.UI.Card.editName(); + //Contacts.UI.Card.editName(); } else { OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); //alert(jsondata.data.message); } - }); + });*/ }, savePropertyInternal:function(name, fields, oldchecksum, checksum){ // TODO: Add functionality for new fields. @@ -627,8 +642,8 @@ Contacts={ },'json'); } }, - addProperty:function(obj){ - var type = $(obj).data('type'); + addProperty:function(type){ + //var type = $(obj).data('type'); console.log('addProperty:' + type); switch (type) { case 'PHOTO': @@ -647,21 +662,21 @@ Contacts={ $('#emails').show(); } Contacts.UI.Card.addMail(); - Contacts.UI.showHideContactInfo(); + //Contacts.UI.showHideContactInfo(); break; case 'TEL': if($('#phonelist>li').length == 1) { $('#phones').show(); } Contacts.UI.Card.addPhone(); - Contacts.UI.showHideContactInfo(); + //Contacts.UI.showHideContactInfo(); break; case 'ADR': if($('#addressdisplay>dl').length == 1) { $('#addresses').show(); } Contacts.UI.Card.editAddress('new', true); - Contacts.UI.showHideContactInfo(); + //Contacts.UI.showHideContactInfo(); break; case 'NICKNAME': case 'ORG': @@ -682,8 +697,8 @@ Contacts={ if(jsondata.status == 'success'){ if(type == 'list') { Contacts.UI.propertyContainerFor(obj).remove(); - Contacts.UI.showHideContactInfo(); - Contacts.UI.checkListFor(obj); + //Contacts.UI.showHideContactInfo(); + //Contacts.UI.checkListFor(obj); } else if(type == 'single') { var proptype = Contacts.UI.propertyTypeFor(obj); console.log('deleteProperty, hiding: ' + proptype); @@ -718,8 +733,8 @@ Contacts={ } else { // Property hasn't been saved so there's nothing to delete. if(type == 'list') { Contacts.UI.propertyContainerFor(obj).remove(); - Contacts.UI.showHideContactInfo(); - Contacts.UI.checkListFor(obj); + //Contacts.UI.showHideContactInfo(); + //Contacts.UI.checkListFor(obj); } else if(type == 'single') { var proptype = Contacts.UI.propertyTypeFor(obj); console.log('deleteProperty, hiding: ' + proptype); @@ -891,7 +906,7 @@ Contacts={ if(isnew) { container.remove(); } - Contacts.UI.showHideContactInfo(); + //Contacts.UI.showHideContactInfo(); } }, close : function(event, ui) { @@ -900,11 +915,95 @@ Contacts={ if(isnew) { container.remove(); } - Contacts.UI.showHideContactInfo(); - }/*, + //Contacts.UI.showHideContactInfo(); + }, open : function(event, ui) { - // load 'ADR' property - maybe :-P - }*/ + $( "#adr_city" ).autocomplete({ + source: function( request, response ) { + $.ajax({ + url: "http://ws.geonames.org/searchJSON", + dataType: "jsonp", + data: { + featureClass: "P", + style: "full", + maxRows: 12, + lang: lang, + name_startsWith: request.term + }, + success: function( data ) { + response( $.map( data.geonames, function( item ) { + /*for(var key in item) { + console.log(key + ': ' + item[key]); + }*/ + return { + label: item.name + (item.adminName1 ? ", " + item.adminName1 : "") + ", " + item.countryName, + value: item.name, + country: item.countryName + } + })); + } + }); + }, + minLength: 2, + select: function( event, ui ) { + if(ui.item && $('#adr_country').val().trim().length == 0) { + $('#adr_country').val(ui.item.country); + } + /*log( ui.item ? + "Selected: " + ui.item.label : + "Nothing selected, input was " + this.value);*/ + }, + open: function() { + $( this ).removeClass( "ui-corner-all" ).addClass( "ui-corner-top" ); + }, + close: function() { + $( this ).removeClass( "ui-corner-top" ).addClass( "ui-corner-all" ); + } + }); + $( "#adr_country" ).autocomplete({ + source: function( request, response ) { + $.ajax({ + url: "http://ws.geonames.org/searchJSON", + dataType: "jsonp", + data: { + /*featureClass: "A",*/ + featureCode: "PCLI", + /*countryBias: "true",*/ + /*style: "full",*/ + lang: lang, + maxRows: 12, + name_startsWith: request.term + }, + success: function( data ) { + response( $.map( data.geonames, function( item ) { + for(var key in item) { + console.log(key + ': ' + item[key]); + } + return { + label: item.name, + value: item.name + } + })); + } + }); + }, + minLength: 2, + select: function( event, ui ) { + /*if(ui.item) { + $('#adr_country').val(ui.item.country); + } + log( ui.item ? + "Selected: " + ui.item.label : + "Nothing selected, input was " + this.value);*/ + }, + open: function() { + $( this ).removeClass( "ui-corner-all" ).addClass( "ui-corner-top" ); + }, + close: function() { + $( this ).removeClass( "ui-corner-top" ).addClass( "ui-corner-all" ); + } + }); + } }); } else { alert(jsondata.data.message); @@ -973,7 +1072,7 @@ Contacts={ } }, loadPhoto:function(force){ - if(this.data.PHOTO||force==true) { + //if(this.data.PHOTO||force==true) { $.getJSON('ajax/loadphoto.php',{'id':this.id},function(jsondata){ if(jsondata.status == 'success'){ //alert(jsondata.data.page); @@ -986,11 +1085,11 @@ Contacts={ }); $('#file_upload_form').show(); $('#contacts_propertymenu a[data-type="PHOTO"]').parent().hide(); - } else { + /*} else { $('#contacts_details_photo_wrapper').empty(); $('#file_upload_form').hide(); $('#contacts_propertymenu a[data-type="PHOTO"]').parent().show(); - } + }*/ }, editPhoto:function(id, tmp_path){ //alert('editPhoto: ' + tmp_path); @@ -1165,7 +1264,7 @@ Contacts={ }); } }, - import:function(){ + doImport:function(){ Contacts.UI.notImplemented(); }, submit:function(button, bookid){ @@ -1198,9 +1297,7 @@ Contacts={ } }, Contacts:{ - /** - * Reload the contacts list. - */ + // Reload the contacts list. update:function(){ console.log('Contacts.update, start'); $.getJSON('ajax/contacts.php',{},function(jsondata){ @@ -1215,9 +1312,7 @@ Contacts={ }); setTimeout(Contacts.UI.Contacts.lazyupdate, 500); }, - /** - * Add thumbnails to the contact list as they become visible in the viewport. - */ + // Add thumbnails to the contact list as they become visible in the viewport. lazyupdate:function(){ $('#contacts li').live('inview', function(){ if (!$(this).find('a').attr('style')) { @@ -1237,9 +1332,6 @@ $(document).ready(function(){ OCCategories.changed = Contacts.UI.Card.categoriesChanged; OCCategories.app = 'contacts'; - /** - * Show the Addressbook chooser - */ $('#chooseaddressbook').click(function(){ Contacts.UI.Addressbooks.overview(); return false; @@ -1272,7 +1364,7 @@ $(document).ready(function(){ }); $('#contacts_deletecard').live('click',function(){ - Contacts.UI.Card.delete(); + Contacts.UI.Card.doDelete(); }); $('#contacts li').bind('inview', function(event, isInView, visiblePartX, visiblePartY) { @@ -1430,7 +1522,8 @@ $(document).ready(function(){ } }); $('#contacts_propertymenu a').live('click',function(){ - Contacts.UI.Card.addProperty(this); + var type = $(this).data('type'); + Contacts.UI.Card.addProperty(type); $('#contacts_propertymenu').hide(); }); }); diff --git a/apps/contacts/js/jquery.combobox.js b/apps/contacts/js/jquery.combobox.js index 6da4ecb5147..f46d7c14c18 100644 --- a/apps/contacts/js/jquery.combobox.js +++ b/apps/contacts/js/jquery.combobox.js @@ -72,17 +72,10 @@ .appendTo( ul ); }; - this.button = $( "<button type='button'> </button>" ) + /*this.button = $( "<button type='button'> </button>" ) .attr( "tabIndex", -1 ) .attr( "title", "Show All Items" ) .insertAfter( input ) - /*.button({ - icons: { - primary: "ui-icon-triangle-1-s" - }, - text: false - }) - .removeClass( "ui-corner-all" )*/ .addClass('svg') .addClass('action') .addClass('combo-button') @@ -99,7 +92,7 @@ // pass empty string as value to search for, displaying all results input.autocomplete( "search", "" ); input.focus(); - }); + });*/ $.each(this.options, function(key, value) { self._setOption(key, value); }); @@ -123,17 +116,23 @@ case "id": this.options['id'] = value; this.input.attr('id', value); - break; + break; case "name": this.options['name'] = value; this.input.attr('name', value); - break; + break; + case "attributes": + var input = this.input; + $.each(this.options['attributes'], function(key, value) { + input.attr(key, value); + }); + break; case "classes": var input = this.input; $.each(this.options['classes'], function(key, value) { input.addClass(value); }); - break; + break; } // In jQuery UI 1.8, you have to manually invoke the _setOption method from the base widget $.Widget.prototype._setOption.apply( this, arguments ); diff --git a/apps/contacts/js/jquery.multi-autocomplete.js b/apps/contacts/js/jquery.multi-autocomplete.js index 7607de3f918..e1c5d63dc5f 100644 --- a/apps/contacts/js/jquery.multi-autocomplete.js +++ b/apps/contacts/js/jquery.multi-autocomplete.js @@ -62,7 +62,7 @@ return false; } }); - this.button = $( "<button type='button'> </button>" ) + /*this.button = $( "<button type='button'> </button>" ) .attr( "tabIndex", -1 ) .attr( "title", "Show All Items" ) .insertAfter( this.element ) @@ -86,7 +86,7 @@ // pass empty string as value to search for, displaying all results self.element.autocomplete( "search", "" ); self.element.focus(); - }); + });*/ }, }); })( jQuery ); diff --git a/apps/contacts/lib/addressbook.php b/apps/contacts/lib/addressbook.php index 052c19e55fe..9061fa19140 100644 --- a/apps/contacts/lib/addressbook.php +++ b/apps/contacts/lib/addressbook.php @@ -169,7 +169,7 @@ class OC_Contacts_Addressbook{ $uid = OC_User::getUser(); } $prefbooks = OC_Preferences::getValue($uid,'contacts','openaddressbooks',null); - if(is_null($prefbooks)){ + if(!$prefbooks){ $addressbooks = OC_Contacts_Addressbook::all($uid); if(count($addressbooks) == 0){ OC_Contacts_Addressbook::add($uid,'default','Default Address Book'); diff --git a/apps/contacts/templates/index.php b/apps/contacts/templates/index.php index af159ce9c60..d68dd68f605 100644 --- a/apps/contacts/templates/index.php +++ b/apps/contacts/templates/index.php @@ -1,6 +1,7 @@ <script type='text/javascript'> var totalurl = '<?php echo OC_Helper::linkToAbsolute('contacts', 'carddav.php'); ?>/addressbooks'; var categories = <?php sort($_['categories']); echo json_encode($_['categories']); ?>; + var lang = '<?php echo OC_Preferences::getValue(OC_User::getUser(), 'core', 'lang', 'en'); ?>'; </script> <div id="controls"> <form> diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php index d243c2b5e14..ff1f081c4d4 100644 --- a/apps/contacts/templates/part.contact.php +++ b/apps/contacts/templates/part.contact.php @@ -17,16 +17,16 @@ $id = isset($_['id']) ? $_['id'] : ''; <li><a data-type="CATEGORIES"><?php echo $l->t('Categories'); ?></a></li> </ul> </div> - <img onclick="Contacts.UI.Card.export();" class="svg action" id="contacts_downloadcard" src="<?php echo image_path('', 'actions/download.svg'); ?>" title="<?php echo $l->t('Download contact');?>" /> + <img onclick="Contacts.UI.Card.doExport();" class="svg action" id="contacts_downloadcard" src="<?php echo image_path('', 'actions/download.svg'); ?>" title="<?php echo $l->t('Download contact');?>" /> <img class="svg action" id="contacts_deletecard" src="<?php echo image_path('', 'actions/delete.svg'); ?>" title="<?php echo $l->t('Delete contact');?>" /> </div> - <div class="contactsection"> + <div id="contact_photo" class="contactsection"> - <form style="display:none;" id="file_upload_form" action="ajax/uploadphoto.php" method="post" enctype="multipart/form-data" target="file_upload_target" class="propertycontainer" data-element="PHOTO"> - <fieldset id="photo" class="formfloat"> + <form class="float" id="file_upload_form" action="ajax/uploadphoto.php" method="post" enctype="multipart/form-data" target="file_upload_target" class="propertycontainer" data-element="PHOTO"> + <fieldset id="photo"> <a class="action delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a> - <div id="contacts_details_photo_wrapper" title="<?php echo $l->t('Click or drop to upload picture'); ?> (max <?php echo $_['uploadMaxHumanFilesize']; ?>)"> + <div class="tip" id="contacts_details_photo_wrapper" title="<?php echo $l->t('Click or drop to upload picture'); ?> (max <?php echo $_['uploadMaxHumanFilesize']; ?>)"> <!-- img style="padding: 1em;" id="contacts_details_photo" alt="Profile picture" src="photo.php?id=<?php echo $_['id']; ?>" / --> <progress id="contacts_details_photo_progress" style="display:none;" value="0" max="100">0 %</progress> </div> @@ -37,58 +37,55 @@ $id = isset($_['id']) ? $_['id'] : ''; <iframe name="file_upload_target" id='file_upload_target' src=""></iframe> </fieldset> </form> - <form id="contact_identity" method="post" <?php echo ($_['id']==''||!isset($_['id'])?'style="display:none;"':''); ?>> + </div> <!-- contact_photo --> + + <div id="contact_identity" class="contactsection"> + <form method="post"> <input type="hidden" name="id" value="<?php echo $_['id'] ?>"> - <fieldset class="propertycontainer" data-element="N"><input type="hidden" id="n" class="contacts_property" name="value" value="" /></fieldset> - <fieldset id="ident" class="formfloat"> + <fieldset id="ident" class="contactpart"> <!-- legend>Name</legend --> - <dl class="form"> - <!-- dt><label for="n"><?php echo $l->t('Name'); ?></label></dt> - <dd style="padding-top: 0.8em;vertical-align: text-bottom;"><span id="n" type="text"></span></dd --> - <dt><label for="fn"><?php echo $l->t('Display name'); ?></label></dt> - <dd class="propertycontainer" data-element="FN"> - <select id="fn_select" title="<?php echo $l->t('Format custom, Short name, Full name, Reverse or Reverse with comma'); ?>" style="width:16em;"> - </select><a id="edit_name" class="action edit" title="<?php echo $l->t('Edit name details'); ?>"></a> - </dd> + <span class="propertycontainer" data-element="N"><input type="hidden" id="n" class="contacts_property" name="value" value="" /></span> + <span id="name" class="propertycontainer" data-element="FN"> + <select class="float" id="fn_select" title="<?php echo $l->t('Format custom, Short name, Full name, Reverse or Reverse with comma'); ?>" style="width:16em;"> + </select><a id="edit_name" class="action edit" title="<?php echo $l->t('Edit name details'); ?>"></a> + </span> + <dl id="identityprops" class="form"> <dt style="display:none;" id="org_label" data-element="ORG"><label for="org"><?php echo $l->t('Organization'); ?></label></dt> - <dd style="display:none;" class="propertycontainer" id="org_value" data-element="ORG"><input id="org" required="required" name="value[ORG]" type="text" class="contacts_property" style="width:16em;" name="value" value="" placeholder="<?php echo $l->t('Organization'); ?>" /><a class="action delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a></dd> + <dd style="display:none;" class="propertycontainer" id="org_value" data-element="ORG"><input id="org" required="required" name="value[ORG]" type="text" class="contacts_property big" name="value" value="" placeholder="<?php echo $l->t('Organization'); ?>" /><a class="action delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a></dd> <dt style="display:none;" id="nickname_label" data-element="NICKNAME"><label for="nickname"><?php echo $l->t('Nickname'); ?></label></dt> - <dd style="display:none;" class="propertycontainer" id="nickname_value" data-element="NICKNAME"><input id="nickname" required="required" name="value[NICKNAME]" type="text" class="contacts_property" style="width:16em;" name="value" value="" placeholder="<?php echo $l->t('Enter nickname'); ?>" /><a class="action delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a></dd> + <dd style="display:none;" class="propertycontainer" id="nickname_value" data-element="NICKNAME"><input id="nickname" required="required" name="value[NICKNAME]" type="text" class="contacts_property big" name="value" value="" placeholder="<?php echo $l->t('Enter nickname'); ?>" /><a class="action delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a></dd> <dt style="display:none;" id="bday_label" data-element="BDAY"><label for="bday"><?php echo $l->t('Birthday'); ?></label></dt> - <dd style="display:none;" class="propertycontainer" id="bday_value" data-element="BDAY"><input id="bday" required="required" name="value" type="text" class="contacts_property" value="" placeholder="<?php echo $l->t('dd-mm-yyyy'); ?>" /><a class="action delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a></dd> - <dt style="display:none;" id="categories_label" data-element="CATEGORIES"><label for="categories"><?php echo $l->t('Categories'); ?></label></dt> - <dd style="display:none;" class="propertycontainer" id="categories_value" data-element="CATEGORIES"><input id="categories" required="required" name="value[CATEGORIES]" type="text" class="contacts_property" style="width:16em;" name="value" value="" placeholder="<?php echo $l->t('Categories'); ?>" /><a class="action delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a><a class="action edit" onclick="$(this).tipsy('hide');OCCategories.edit();" title="<?php echo $l->t('Edit categories'); ?>"></a></dd> + <dd style="display:none;" class="propertycontainer" id="bday_value" data-element="BDAY"><input id="bday" required="required" name="value" type="text" class="contacts_property big" value="" placeholder="<?php echo $l->t('dd-mm-yyyy'); ?>" /><a class="action delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a></dd> + <dt style="display:none;" id="categories_label" data-element="CATEGORIES"><label for="categories"><?php echo $l->t('Groups'); ?></label></dt> + <dd style="display:none;" class="propertycontainer" id="categories_value" data-element="CATEGORIES"><input id="categories" required="required" name="value[CATEGORIES]" type="text" class="contacts_property bold" name="value" value="" placeholder=" +<?php echo $l->t('Separate groups with commas'); ?>" /><a class="action delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a><a class="action edit" onclick="$(this).tipsy('hide');OCCategories.edit();" title="<?php echo $l->t('Edit categories'); ?>"></a></dd> </dl> </fieldset> - <fieldset id="note" class="formfloat propertycontainer contactpart" style="display:none;" data-element="NOTE"> - <legend><?php echo $l->t('Note'); ?><a class="action delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'single');" title="<?php echo $l->t('Delete'); ?>"></a></legend> - <textarea class="contacts_property note" name="value" cols="60" rows="10"></textarea> - </fieldset> </form> - </div> + </div> <!-- contact_identity --> <!-- div class="delimiter"></div --> - <form id="contact_communication" method="post" style="display: none;"> - <div class="contactsection"> + <div id="contact_communication" class="contactsection"> + <form method="post"> <!-- email addresses --> - <div id="emails" style="display:none;"> + <div id="emails"> <fieldset class="contactpart"> - <legend><?php echo $l->t('Email'); ?></legend> + <!-- legend><?php echo $l->t('Email'); ?></legend --> <ul id="emaillist" class="propertylist"> <li class="template" style="white-space: nowrap; display: none;" data-element="EMAIL"> - <input type="checkbox" class="contacts_property" name="parameters[TYPE][]" value="PREF" title="<?php echo $l->t('Preferred'); ?>" /> + <input type="checkbox" class="contacts_property tip" name="parameters[TYPE][]" value="PREF" title="<?php echo $l->t('Preferred'); ?>" /> <input type="email" required="required" class="nonempty contacts_property" style="width:15em;" name="value" value="" x-moz-errormessage="<?php echo $l->t('Please specify a valid email address.'); ?>" placeholder="<?php echo $l->t('Enter email address'); ?>" /><span class="listactions"><a onclick="Contacts.UI.mailTo(this)" class="action mail" title="<?php echo $l->t('Mail to address'); ?>"></a> <a class="action delete" onclick="$(this).tipsy('hide');Contacts.UI.Card.deleteProperty(this, 'list');" title="<?php echo $l->t('Delete email address'); ?>"></a></span></li> </ul><!-- a id="add_email" class="add" title="<?php echo $l->t('Add email address'); ?>"></a --> </div> <!-- email addresses--> <!-- Phone numbers --> - <div id="phones" style="display:none;"> + <div id="phones"> <fieldset class="contactpart"> - <legend><?php echo $l->t('Phone'); ?></legend> + <!-- legend><?php echo $l->t('Phone'); ?></legend --> <ul id="phonelist" class="propertylist"> <li class="template" style="white-space: nowrap; display: none;" data-element="TEL"> - <input type="checkbox" class="contacts_property" name="parameters[TYPE][]" value="PREF" title="<?php echo $l->t('Preferred'); ?>" /> + <input type="checkbox" class="contacts_property tip" name="parameters[TYPE][]" value="PREF" title="<?php echo $l->t('Preferred'); ?>" /> <input type="text" required="required" class="nonempty contacts_property" style="width:10em; border: 0px;" name="value" value="" placeholder="<?php echo $l->t('Enter phone number'); ?>" /> <select multiple="multiple" name="parameters[TYPE][]"> <?php echo html_select_options($_['phone_types'], array()) ?> @@ -100,7 +97,7 @@ $id = isset($_['id']) ? $_['id'] : ''; <!-- Addresses --> <div id="addresses" style="display:none;"> <fieldset class="contactpart"> - <legend><?php echo $l->t('Address'); ?></legend> + <!-- legend><?php echo $l->t('Address'); ?></legend --> <div id="addressdisplay"> <dl class="addresscard template" style="display: none;" data-element="ADR"><dt> <input class="adr contacts_property" name="value" type="hidden" value="" /> @@ -109,13 +106,18 @@ $id = isset($_['id']) ? $_['id'] : ''; </dt><dd><ul class="addresslist"></ul></dd></dl> </fieldset> - </div> + </div> <!-- addressdisplay --> </div> <!-- Addresses --> - </div> - <!-- a id="add_address" onclick="Contacts.UI.Card.editAddress('new', true)" class="add" title="<?php echo $l->t('Add address'); ?>"></a --> - </div> </form> -</div> + </div> <!-- contact_communication --> + <div id="contact_note" class="contactsection"> + <form class="float" method="post"> + <fieldset id="note" class="formfloat propertycontainer contactpart" data-element="NOTE"> + <textarea class="contacts_property note" name="value" cols="40" rows="10" required="required" placeholder="<?php echo $l->t('Add notes here.'); ?>"></textarea> + </fieldset> + </form> + </div> <!-- contact_note --> +</div> <!-- card --> <div id="edit_photo_dialog" title="Edit photo"> <div id="edit_photo_dialog_img"></div> </div> @@ -128,7 +130,7 @@ $(document).ready(function(){ Contacts.UI.Card.loadContact(jsondata.data); } else{ - Contacts.UI.messageBox(t('contacts', 'Error'), jsondata.data.message); + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); } }); } diff --git a/apps/contacts/templates/part.edit_address_dialog.php b/apps/contacts/templates/part.edit_address_dialog.php index 0ecdc4e1915..507a3acaa0c 100644 --- a/apps/contacts/templates/part.edit_address_dialog.php +++ b/apps/contacts/templates/part.edit_address_dialog.php @@ -22,44 +22,44 @@ foreach(isset($adr['parameters']['TYPE'])?array($adr['parameters']['TYPE']):arra <label class="label" for="adr_pobox"><?php echo $l->t('PO Box'); ?></label> </dt> <dd> - <input type="text" id="adr_pobox" name="value[ADR][0]" value="<?php echo isset($adr['value'][0])?$adr['value'][0]:''; ?>"> + <input type="text" id="adr_pobox" name="value[ADR][0]" placeholder="<?php echo $l->t('PO Box'); ?>" value="<?php echo isset($adr['value'][0])?$adr['value'][0]:''; ?>"> </dd> <dd> <dt> <label class="label" for="adr_extended"><?php echo $l->t('Extended'); ?></label> </dt> <dd> - <input type="text" id="adr_extended" name="value[ADR][1]" value="<?php echo isset($adr['value'][1])?$adr['value'][1]:''; ?>"> + <input type="text" id="adr_extended" name="value[ADR][1]" placeholder="<?php echo $l->t('Extended'); ?>" value="<?php echo isset($adr['value'][1])?$adr['value'][1]:''; ?>"> </dd> <dt> <label class="label" for="adr_street"><?php echo $l->t('Street'); ?></label> </dt> <dd> - <input type="text" id="adr_street" name="value[ADR][2]" value="<?php echo isset($adr['value'][2])?$adr['value'][2]:''; ?>"> + <input type="text" id="adr_street" name="value[ADR][2]" placeholder="<?php echo $l->t('Street'); ?>" value="<?php echo isset($adr['value'][2])?$adr['value'][2]:''; ?>"> </dd> <dt> <label class="label" for="adr_city"><?php echo $l->t('City'); ?></label> </dt> <dd> - <input type="text" id="adr_city" name="value[ADR][3]" value="<?php echo isset($adr['value'][3])?$adr['value'][3]:''; ?>"> + <input type="text" id="adr_city" name="value[ADR][3]" placeholder="<?php echo $l->t('City'); ?>" value="<?php echo isset($adr['value'][3])?$adr['value'][3]:''; ?>"> </dd> <dt> <label class="label" for="adr_region"><?php echo $l->t('Region'); ?></label> </dt> <dd> - <input type="text" id="adr_region" name="value[ADR][4]" value="<?php echo isset($adr['value'][4])?$adr['value'][4]:''; ?>"> + <input type="text" id="adr_region" name="value[ADR][4]" placeholder="<?php echo $l->t('Region'); ?>" value="<?php echo isset($adr['value'][4])?$adr['value'][4]:''; ?>"> </dd> <dt> <label class="label" for="adr_zipcode"><?php echo $l->t('Zipcode'); ?></label> </dt> <dd> - <input type="text" id="adr_zipcode" name="value[ADR][5]" value="<?php echo isset($adr['value'][5])?$adr['value'][5]:''; ?>"> + <input type="text" id="adr_zipcode" name="value[ADR][5]" placeholder="<?php echo $l->t('Zipcode'); ?>" value="<?php echo isset($adr['value'][5])?$adr['value'][5]:''; ?>"> </dd> <dt> <label class="label" for="adr_country"><?php echo $l->t('Country'); ?></label> </dt> <dd> - <input type="text" id="adr_country" name="value[ADR][6]" value="<?php echo isset($adr['value'][6])?$adr['value'][6]:''; ?>"> + <input type="text" id="adr_country" name="value[ADR][6]" placeholder="<?php echo $l->t('Country'); ?>" value="<?php echo isset($adr['value'][6])?$adr['value'][6]:''; ?>"> </dd> </dl> </fieldset> diff --git a/apps/contacts/templates/part.no_contacts.php b/apps/contacts/templates/part.no_contacts.php new file mode 100644 index 00000000000..7024a142aec --- /dev/null +++ b/apps/contacts/templates/part.no_contacts.php @@ -0,0 +1,8 @@ +<div id="firstrun"> + <?php echo $l->t('You have no contacts in your list.') ?> + <div id="selections"> + <input type="button" value="<?php echo $l->t('Import contacts') ?>" onclick="Contacts.UI.Addressbooks.doImport()" /> + <input type="button" value="<?php echo $l->t('Add contact') ?>" onclick="Contacts.UI.Card.editNew()" /> + <input type="button" value="<?php echo $l->t('Edit addressbooks') ?>" onclick="Contacts.UI.Addressbooks.overview()" /> + </div> +</div> diff --git a/apps/contacts/templates/settings.php b/apps/contacts/templates/settings.php index f56de0ec8b0..5627a15c50a 100644 --- a/apps/contacts/templates/settings.php +++ b/apps/contacts/templates/settings.php @@ -8,5 +8,6 @@ <dt><?php echo $l->t('iOS/OS X'); ?></dt> <dd><code><?php echo OC_Helper::linkToAbsolute('contacts', 'carddav.php'); ?>/principals/<?php echo OC_User::getUser(); ?></code>/</dd> </dl> + Powered by <a href="http://geonames.org/" target="_blank">geonames.org webservice</a> </fieldset> </form> diff --git a/apps/files_texteditor/js/editor.js b/apps/files_texteditor/js/editor.js index 02d39b98430..bc8a20408c2 100644 --- a/apps/files_texteditor/js/editor.js +++ b/apps/files_texteditor/js/editor.js @@ -22,9 +22,11 @@ function setSyntaxMode(ext){ filetype["css"] = "css"; filetype["groovy"] = "groovy"; filetype["haxe"] = "hx"; + filetype["htm"] = "html"; filetype["html"] = "html"; filetype["java"] = "java"; filetype["js"] = "javascript"; + filetype["jsm"] = "javascript"; filetype["json"] = "json"; filetype["latex"] = "latex"; filetype["ly"] = "latex"; @@ -141,32 +143,40 @@ function doSearch(){ // Tries to save the file. function doFileSave(){ if(editorIsShown()){ - // Get file path - var path = $('#editor').attr('data-dir')+'/'+$('#editor').attr('data-filename'); - // Get original mtime - var mtime = $('#editor').attr('data-mtime'); - // Show saving spinner - $("#editor_save").die('click',doFileSave); - $('#save_result').remove(); - $('#editor_save').text(t('files_texteditor','Saving...')); - // Get the data - var filecontents = window.aceEditor.getSession().getValue(); - // Send the data - $.post(OC.filePath('files_texteditor','ajax','savefile.php'), { filecontents: filecontents, path: path, mtime: mtime },function(jsondata){ - if(jsondata.status!='success'){ - // Save failed - $('#editor_save').text(t('files_texteditor','Save')); - $('#editor_save').after('<p id="save_result" style="float: left">Failed to save file</p>'); - $("#editor_save").live('click',doFileSave); - } else { - // Save OK - // Update mtime - $('#editor').attr('data-mtime',jsondata.data.mtime); - $('#editor_save').text(t('files_texteditor','Save')); - $("#editor_save").live('click',doFileSave); - } - },'json'); + // Changed contents? + if($('#editor').attr('data-edited')=='true'){ + // Get file path + var path = $('#editor').attr('data-dir')+'/'+$('#editor').attr('data-filename'); + // Get original mtime + var mtime = $('#editor').attr('data-mtime'); + // Show saving spinner + $("#editor_save").die('click',doFileSave); + $('#save_result').remove(); + $('#editor_save').text(t('files_texteditor','Saving...')); + // Get the data + var filecontents = window.aceEditor.getSession().getValue(); + // Send the data + $.post(OC.filePath('files_texteditor','ajax','savefile.php'), { filecontents: filecontents, path: path, mtime: mtime },function(jsondata){ + if(jsondata.status!='success'){ + // Save failed + $('#editor_save').text(t('files_texteditor','Save')); + $('#editor_save').after('<p id="save_result" style="float: left">Failed to save file</p>'); + $("#editor_save").live('click',doFileSave); + } else { + // Save OK + // Update mtime + $('#editor').attr('data-mtime',jsondata.data.mtime); + $('#editor_save').text(t('files_texteditor','Save')); + $("#editor_save").live('click',doFileSave); + // Update titles + $('#editor').attr('data-edited', 'false'); + $('#breadcrumb_file').text($('#editor').attr('data-filename')); + document.title = $('#editor').attr('data-filename')+' - ownCloud'; + } + },'json'); + } } + giveEditorFocus(); }; // Gives the editor focus @@ -176,6 +186,8 @@ function giveEditorFocus(){ // Loads the file editor. Accepts two parameters, dir and filename. function showFileEditor(dir,filename){ + // Delete any old editors + $('#editor').remove(); if(!editorIsShown()){ // Loads the file editor and display it. $('#content').append('<div id="editor"></div>'); @@ -192,10 +204,11 @@ function showFileEditor(dir,filename){ // Show the control bar showControls(filename,result.data.write); // Update document title - document.title = filename; + document.title = filename+' - ownCloud'; $('#editor').text(result.data.filecontents); $('#editor').attr('data-dir', dir); $('#editor').attr('data-filename', filename); + $('#editor').attr('data-edited', 'false'); window.aceEditor = ace.edit("editor"); aceEditor.setShowPrintMargin(false); aceEditor.getSession().setUseWrapMode(true); @@ -207,10 +220,17 @@ function showFileEditor(dir,filename){ OC.addScript('files_texteditor','aceeditor/theme-clouds', function(){ window.aceEditor.setTheme("ace/theme/clouds"); }); + window.aceEditor.getSession().on('change', function(){ + if($('#editor').attr('data-edited')!='true'){ + $('#editor').attr('data-edited', 'true'); + $('#breadcrumb_file').text($('#breadcrumb_file').text()+' *'); + document.title = $('#editor').attr('data-filename')+' * - ownCloud'; + } + }); }); } else { // Failed to get the file. - alert(result.data.message); + OC.dialogs.alert(result.data.message, t('files_texteditor','An error occurred!')); } // End success } @@ -222,37 +242,54 @@ function showFileEditor(dir,filename){ // Fades out the editor. function hideFileEditor(){ - // Fades out editor controls - $('#editorcontrols').fadeOut('slow',function(){ - $(this).remove(); - $(".crumb:last").addClass('last'); - }); - // Fade out editor - $('#editor').fadeOut('slow', function(){ - $(this).remove(); - // Reset document title - document.title = "ownCloud"; - var editorhtml = '<div id="editor"></div>'; - $('table').after(editorhtml); - $('.actions,#file_access_panel').fadeIn('slow'); - $('table').fadeIn('slow'); - }); - is_editor_shown = false; + if($('#editor').attr('data-edited') == 'true'){ + // Hide, not remove + $('#editorcontrols').fadeOut('slow',function(){ + // Check if there is a folder in the breadcrumb + if($('.crumb.ui-droppable').length){ + $('.crumb.ui-droppable:last').addClass('last'); + } + }); + // Fade out editor + $('#editor').fadeOut('slow', function(){ + // Reset document title + document.title = "ownCloud"; + $('.actions,#file_access_panel').fadeIn('slow'); + $('table').fadeIn('slow'); + }); + $('#notification').text(t('files_texteditor','There were unsaved changes, click here to go back')); + $('#notification').data('reopeneditor',true); + $('#notification').fadeIn(); + is_editor_shown = false; + } else { + // Remove editor + $('#editorcontrols').fadeOut('slow',function(){ + $(this).remove(); + $(".crumb:last").addClass('last'); + }); + // Fade out editor + $('#editor').fadeOut('slow', function(){ + $(this).remove(); + // Reset document title + document.title = "ownCloud"; + $('.actions,#file_access_panel').fadeIn('slow'); + $('table').fadeIn('slow'); + }); + is_editor_shown = false; + } } -// Keyboard Shortcuts -var ctrlBtn = false; +// Reopens the last document +function reopenEditor(){ + $('.actions,#file_action_panel').fadeOut('slow'); + $('table').fadeOut('slow', function(){ + $('#controls .last').not('#breadcrumb_file').removeClass('last'); + $('#editor').fadeIn('fast'); + $('#editorcontrols').fadeIn('fast', function(){ -// TODO fix detection of ctrl keyup -// returns true if ctrl+s or cmd+s is being pressed -function checkForSaveKeyPress(e){ - if(e.which == 17 || e.which == 91) ctrlBtn=true; - if(e.which == 83 && ctrlBtn == true) { - e.preventDefault(); - $('#editor_save').trigger('click'); - return false; - - } + }); + }); + is_editor_shown = true; } // resizes the editor window @@ -285,6 +322,11 @@ $(document).ready(function(){ // Binds the file save and close editor events, and gotoline button bindControlEvents(); $('#editor').remove(); - // Binds the save keyboard shortcut events - //$(document).unbind('keydown').bind('keydown',checkForSaveKeyPress); + $('#notification').click(function(){ + if($('#notification').data('reopeneditor')) + { + reopenEditor(); + } + $('#notification').fadeOut(); + }); }); diff --git a/apps/files_versioning/ajax/gethead.php b/apps/files_versioning/ajax/gethead.php new file mode 100644 index 00000000000..cc93b7a1d17 --- /dev/null +++ b/apps/files_versioning/ajax/gethead.php @@ -0,0 +1,12 @@ +<?php +/** + * Copyright (c) 2011 Craig Roberts craig0990@googlemail.com + * This file is licensed under the Affero General Public License version 3 or + * later. + */ +require_once('../../../lib/base.php'); + +OC_JSON::checkLoggedIn(); +// Fetch current commit (or HEAD if not yet set) +$head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); +OC_JSON::encodedPrint(array("head" => $head)); diff --git a/apps/files_versioning/ajax/sethead.php b/apps/files_versioning/ajax/sethead.php new file mode 100644 index 00000000000..d1b2df9b00f --- /dev/null +++ b/apps/files_versioning/ajax/sethead.php @@ -0,0 +1,14 @@ +<?php +/** + * Copyright (c) 2011 Craig Roberts craig0990@googlemail.com + * This file is licensed under the Affero General Public License version 3 or + * later. + */ +require_once('../../../lib/base.php'); +OC_JSON::checkLoggedIn(); +if(isset($_POST["file_versioning_head"])){ + OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $_POST["file_versioning_head"]); + OC_JSON::success(); +}else{ + OC_JSON::error(); +} diff --git a/apps/files_versioning/appinfo/app.php b/apps/files_versioning/appinfo/app.php new file mode 100644 index 00000000000..24a8701dbb0 --- /dev/null +++ b/apps/files_versioning/appinfo/app.php @@ -0,0 +1,20 @@ +<?php + +// Include required files +require_once('apps/files_versioning/versionstorage.php'); +require_once('apps/files_versioning/versionwrapper.php'); +// Register streamwrapper for versioned:// paths +stream_wrapper_register('versioned', 'OC_VersionStreamWrapper'); + +// Add an entry in the app list for versioning and backup +OC_App::register( array( + 'order' => 10, + 'id' => 'files_versioning', + 'name' => 'Versioning and Backup' )); + +// Include stylesheets for the settings page +OC_Util::addStyle( 'files_versioning', 'settings' ); +OC_Util::addScript('files_versioning','settings'); + +// Register a settings section in the Admin > Personal page +OC_APP::registerPersonal('files_versioning','settings'); diff --git a/apps/files_versioning/appinfo/info.xml b/apps/files_versioning/appinfo/info.xml new file mode 100644 index 00000000000..4c67894f9f9 --- /dev/null +++ b/apps/files_versioning/appinfo/info.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<info> + <id>files_versioning</id> + <name>Versioning and Backup</name> + <version>1.0.0</version> + <licence>GPLv2</licence> + <author>Craig Roberts</author> + <require>3</require> + <description>Versions files using Git repositories, providing a simple backup facility. Currently in *beta* and explicitly without warranty of any kind.</description> + <types> + <filesystem/> + </types> +</info> diff --git a/apps/files_versioning/css/settings.css b/apps/files_versioning/css/settings.css new file mode 100644 index 00000000000..afe2cd5508f --- /dev/null +++ b/apps/files_versioning/css/settings.css @@ -0,0 +1,3 @@ +#file_versioning_commit_chzn { + width: 15em; +} diff --git a/apps/files_versioning/js/settings.js b/apps/files_versioning/js/settings.js new file mode 100644 index 00000000000..8dd13bac033 --- /dev/null +++ b/apps/files_versioning/js/settings.js @@ -0,0 +1,25 @@ +$(document).ready(function(){ + $('#file_versioning_head').chosen(); + + $.getJSON(OC.filePath('files_versioning', 'ajax', 'gethead.php'), function(jsondata, status) { + + if (jsondata.head == 'HEAD') { + // Most recent commit, do nothing + } else { + $("#file_versioning_head").val(jsondata.head); + // Trigger the chosen update call + // See http://harvesthq.github.com/chosen/ + $("#file_versioning_head").trigger("liszt:updated"); + } + }); + + $('#file_versioning_head').change(function() { + + var data = $(this).serialize(); + $.post( OC.filePath('files_versioning', 'ajax', 'sethead.php'), data, function(data){ + if(data == 'error'){ + console.log('Saving new HEAD failed'); + } + }); + }); +}); diff --git a/apps/files_versioning/lib_granite.php b/apps/files_versioning/lib_granite.php new file mode 100644 index 00000000000..571e5cea637 --- /dev/null +++ b/apps/files_versioning/lib_granite.php @@ -0,0 +1,12 @@ +<?php + +require_once('granite/git/blob.php'); +require_once('granite/git/commit.php'); +require_once('granite/git/repository.php'); +require_once('granite/git/tag.php'); +require_once('granite/git/tree.php'); +require_once('granite/git/tree/node.php'); +require_once('granite/git/object/index.php'); +require_once('granite/git/object/raw.php'); +require_once('granite/git/object/loose.php'); +require_once('granite/git/object/packed.php'); diff --git a/apps/files_versioning/settings.php b/apps/files_versioning/settings.php new file mode 100644 index 00000000000..94af587a215 --- /dev/null +++ b/apps/files_versioning/settings.php @@ -0,0 +1,34 @@ +<?php + +// Get the full path to the repository folder (FIXME: hard-coded to 'Backup') +$path = OC_Config::getValue('datadirectory', OC::$SERVERROOT.'/data') + . DIRECTORY_SEPARATOR + . OC_User::getUser() + . DIRECTORY_SEPARATOR + . 'files' + . DIRECTORY_SEPARATOR + . 'Backup' + . DIRECTORY_SEPARATOR + . '.git' + . DIRECTORY_SEPARATOR; + +$repository = new Granite\Git\Repository($path); + +$commits = array(); +// Fetch most recent 50 commits (FIXME - haven't tested this much) +$commit = $repository->head(); +for ($i = 0; $i < 50; $i++) { + $commits[] = $commit; + $parents = $commit->parents(); + if (count($parents) > 0) { + $parent = $parents[0]; + } else { + break; + } + + $commit = $repository->factory('commit', $parent); +} + +$tmpl = new OC_Template( 'files_versioning', 'settings'); +$tmpl->assign('commits', $commits); +return $tmpl->fetchPage(); diff --git a/apps/files_versioning/templates/settings.php b/apps/files_versioning/templates/settings.php new file mode 100644 index 00000000000..17f4cc7f77f --- /dev/null +++ b/apps/files_versioning/templates/settings.php @@ -0,0 +1,12 @@ +<fieldset id="status_list" class="personalblock"> + <strong>Versioning and Backup</strong><br> + <p><em>Please note: Backing up large files (around 16MB+) will cause your backup history to grow very large, very quickly.</em></p> + <label class="bold">Backup Folder</label> + <select name="file_versioning_head" id="file_versioning_head"> + <?php + foreach ($_['commits'] as $commit): + echo '<option value="' . $commit->sha() . '">' . $commit->message() . '</option>'; + endforeach; + ?> + </select> +</fieldset> diff --git a/apps/files_versioning/versionstorage.php b/apps/files_versioning/versionstorage.php new file mode 100644 index 00000000000..d083e623df9 --- /dev/null +++ b/apps/files_versioning/versionstorage.php @@ -0,0 +1,386 @@ +<?php +/** + * ownCloud file storage implementation for Git repositories + * @author Craig Roberts + * @copyright 2012 Craig Roberts craig0990@googlemail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +// Include Granite +require_once('lib_granite.php'); + +// Create a top-level 'Backup' directory if it does not already exist +$user = OC_User::getUser(); +if (OC_Filesystem::$loaded and !OC_Filesystem::is_dir('/Backup')) { + OC_Filesystem::mkdir('/Backup'); + OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); +} + +// Generate the repository path (currently using 'full' repositories, as opposed to bare ones) +$repo_path = DIRECTORY_SEPARATOR + . OC_User::getUser() + . DIRECTORY_SEPARATOR + . 'files' + . DIRECTORY_SEPARATOR + . 'Backup'; + +// Mount the 'Backup' folder using the versioned storage provider below +OC_Filesystem::mount('OC_Filestorage_Versioned', array('repo'=>$repo_path), $repo_path . DIRECTORY_SEPARATOR); + +class OC_Filestorage_Versioned extends OC_Filestorage { + + /** + * Holds an instance of Granite\Git\Repository + */ + protected $repo; + + /** + * Constructs a new OC_Filestorage_Versioned instance, expects an associative + * array with a `repo` key set to the path of the repository's `.git` folder + * + * @param array $parameters An array containing the key `repo` pointing to the + * repository path. + */ + public function __construct($parameters) { + // Get the full path to the repository folder + $path = OC_Config::getValue('datadirectory', OC::$SERVERROOT.'/data') + . $parameters['repo'] + . DIRECTORY_SEPARATOR + . '.git' + . DIRECTORY_SEPARATOR; + + try { + // Attempt to load the repository + $this->repo = new Granite\Git\Repository($path); + } catch (InvalidArgumentException $e) { + // $path is not a valid Git repository, we must create one + Granite\Git\Repository::init($path); + + // Load the newly-initialised repository + $this->repo = new Granite\Git\Repository($path); + + /** + * Create an initial commit with a README file + * FIXME: This functionality should be transferred to the Granite library + */ + $blob = new Granite\Git\Blob($this->repo->path()); + $blob->content('Your Backup directory is now ready for use.'); + + // Create a new tree to hold the README file + $tree = $this->repo->factory('tree'); + // Create a tree node to represent the README blob + $tree_node = new Granite\Git\Tree\Node('README', '100644', $blob->sha()); + $tree->nodes(array($tree_node->name() => $tree_node)); + + // Create an initial commit + $commit = new Granite\Git\Commit($this->repo->path()); + $user_string = OC_User::getUser() . ' ' . time() . ' +0000'; + $commit->author($user_string); + $commit->committer($user_string); + $commit->message('Initial commit'); + $commit->tree($tree); + + // Write it all to disk + $blob->write(); + $tree->write(); + $commit->write(); + + // Update the HEAD for the 'master' branch + $this->repo->head('master', $commit->sha()); + } + + // Update the class pointer to the HEAD + $head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); + + // Load the most recent commit if the preference is not set + if ($head == 'HEAD') { + $this->head = $this->repo->head()->sha(); + } else { + $this->head = $head; + } + } + + public function mkdir($path) { + if (mkdir("versioned:/{$this->repo->path()}$path#{$this->head}")) { + $this->head = $this->repo->head()->sha(); + OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $head); + return true; + } + + return false; + } + + public function rmdir($path) { + + } + + /** + * Returns a directory handle to the requested path, or FALSE on failure + * + * @param string $path The directory path to open + * + * @return boolean|resource A directory handle, or FALSE on failure + */ + public function opendir($path) { + return opendir("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + /** + * Returns TRUE if $path is a directory, or FALSE if not + * + * @param string $path The path to check + * + * @return boolean + */ + public function is_dir($path) { + return $this->filetype($path) == 'dir'; + } + + /** + * Returns TRUE if $path is a file, or FALSE if not + * + * @param string $path The path to check + * + * @return boolean + */ + public function is_file($path) { + return $this->filetype($path) == 'file'; + } + + public function stat($path) + { + return stat("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + /** + * Returns the strings 'dir' or 'file', depending on the type of $path + * + * @param string $path The path to check + * + * @return string Returns 'dir' if a directory, 'file' otherwise + */ + public function filetype($path) { + if ($path == "" || $path == "/") { + return 'dir'; + } else { + if (substr($path, -1) == '/') { + $path = substr($path, 0, -1); + } + + $node = $this->tree_search($this->repo, $this->repo->factory('commit', $this->head)->tree(), $path); + + // Does it exist, or is it new? + if ($node == null) { + // New file + return 'file'; + } else { + // Is it a tree? + try { + $this->repo->factory('tree', $node); + return 'dir'; + } catch (InvalidArgumentException $e) { + // Nope, must be a blob + return 'file'; + } + } + } + } + + public function filesize($path) { + return filesize("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + /** + * Returns a boolean value representing whether $path is readable + * + * @param string $path The path to check + *( + * @return boolean Whether or not the path is readable + */ + public function is_readable($path) { + return true; + } + + /** + * Returns a boolean value representing whether $path is writable + * + * @param string $path The path to check + *( + * @return boolean Whether or not the path is writable + */ + public function is_writable($path) { + + $head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); + if ($head !== 'HEAD' && $head !== $this->repo->head()->sha()) { + // Cannot modify previous commits + return false; + } + return true; + } + + /** + * Returns a boolean value representing whether $path exists + * + * @param string $path The path to check + *( + * @return boolean Whether or not the path exists + */ + public function file_exists($path) { + return file_exists("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + /** + * Returns an integer value representing the inode change time + * (NOT IMPLEMENTED) + * + * @param string $path The path to check + *( + * @return int Timestamp of the last inode change + */ + public function filectime($path) { + return -1; + } + + /** + * Returns an integer value representing the file modification time + * + * @param string $path The path to check + *( + * @return int Timestamp of the last file modification + */ + public function filemtime($path) { + return filemtime("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + public function file_get_contents($path) { + return file_get_contents("versioned:/{$this->repo->path()}$path#{$this->head}"); + } + + public function file_put_contents($path, $data) { + $success = file_put_contents("versioned:/{$this->repo->path()}$path#{$this->head}", $data); + if ($success !== false) { + // Update the HEAD in the preferences + OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $this->repo->head()->sha()); + return $success; + } + + return false; + } + + public function unlink($path) { + + } + + public function rename($path1, $path2) { + + } + + public function copy($path1, $path2) { + + } + + public function fopen($path, $mode) { + return fopen("versioned:/{$this->repo->path()}$path#{$this->head}", $mode); + } + + public function getMimeType($path) { + if ($this->filetype($path) == 'dir') { + return 'httpd/unix-directory'; + } elseif ($this->filesize($path) == 0) { + // File's empty, returning text/plain allows opening in the web editor + return 'text/plain'; + } else { + $finfo = new finfo(FILEINFO_MIME_TYPE); + /** + * We need to represent the repository path, the file path, and the + * revision, which can be simply achieved with a convention of using + * `.git` in the repository directory (bare or not) and the '#part' + * segment of a URL to specify the revision. For example + * + * versioned://var/www/myrepo.git/docs/README.md#HEAD ('bare' repo) + * versioned://var/www/myrepo/.git/docs/README.md#HEAD ('full' repo) + * versioned://var/www/myrepo/.git/docs/README.md#6a8f...8a54 ('full' repo and SHA-1 commit ID) + */ + $mime = $finfo->buffer(file_get_contents("versioned:/{$this->repo->path()}$path#{$this->head}")); + return $mime; + } + } + + /** + * Generates a hash based on the file contents + * + * @param string $type The hashing algorithm to use (e.g. 'md5', 'sha256', etc.) + * @param string $path The file to be hashed + * @param boolean $raw Outputs binary data if true, lowercase hex digits otherwise + * + * @return string Hashed string representing the file contents + */ + public function hash($type, $path, $raw) { + return hash($type, file_get_contents($path), $raw); + } + + public function free_space($path) { + } + + public function search($query) { + + } + + public function touch($path, $mtime=null) { + + } + + + public function getLocalFile($path) { + } + + /** + * Recursively searches a tree for a path, returning FALSE if is not found + * or an SHA-1 id if it is found. + * + * @param string $repo The repository containing the tree object + * @param string $tree The tree object to search + * @param string $path The path to search for (relative to the tree) + * @param int $depth The depth of the current search (for recursion) + * + * @return string|boolean The SHA-1 id of the sub-tree + */ + private function tree_search($repo, $tree, $path, $depth = 0) + { + $paths = array_values(explode(DIRECTORY_SEPARATOR, $path)); + + $current_path = $paths[$depth]; + + $nodes = $tree->nodes(); + foreach ($nodes as $node) { + if ($node->name() == $current_path) { + + if (count($paths)-1 == $depth) { + // Stop, found it + return $node->sha(); + } + + // Recurse if necessary + if ($node->isDirectory()) { + $tree = $this->repo->factory('tree', $node->sha()); + return $this->tree_search($repo, $tree, $path, $depth + 1); + } + } + } + + return false; + } + +} diff --git a/apps/files_versioning/versionwrapper.php b/apps/files_versioning/versionwrapper.php new file mode 100644 index 00000000000..b83a4fd3b22 --- /dev/null +++ b/apps/files_versioning/versionwrapper.php @@ -0,0 +1,686 @@ +<?php + +final class OC_VersionStreamWrapper { + + /** + * Determines whether or not to log debug messages with `OC_Log::write()` + */ + private $debug = true; + + /** + * The name of the ".empty" files created in new directories + */ + const EMPTYFILE = '.empty'; + + /** + * Stores the current position for `readdir()` etc. calls + */ + private $dir_position = 0; + + /** + * Stores the current position for `fread()`, `fseek()` etc. calls + */ + private $file_position = 0; + + /** + * Stores the current directory tree for `readdir()` etc. directory traversal + */ + private $tree; + + /** + * Stores the current file for `fread()`, `fseek()`, etc. calls + */ + private $blob; + + /** + * Stores the current commit for `fstat()`, `stat()`, etc. calls + */ + private $commit; + + /** + * Stores the current path for `fwrite()`, `file_put_contents()` etc. calls + */ + private $path; + + /** + * Close directory handle + */ + public function dir_closedir() { + unset($this->tree); + return true; + } + + /** + * Open directory handle + */ + public function dir_opendir($path, $options) { + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); + + if ($repo_file == '' || $repo_file == '/') { + // Set the tree property for the future `readdir()` etc. calls + $this->tree = array_values($this->commit->tree()->nodes()); + return true; + } elseif ($this->tree_search($this->repo, $this->commit->tree(), $repo_file) !== false) { + // Something exists at this path, is it a directory though? + try { + $tree = $this->repo->factory( + 'tree', + $this->tree_search($this->repo, $this->commit->tree(), $repo_file) + ); + $this->tree = array_values($tree->nodes()); + return true; + } catch (InvalidArgumentException $e) { + // Trying to call `opendir()` on a file, return false below + } + } + + // Unable to find the directory, return false + return false; + } + + /** + * Read entry from directory handle + */ + public function dir_readdir() { + return isset($this->tree[$this->dir_position]) + ? $this->tree[$this->dir_position++]->name() + : false; + } + + /** + * Rewind directory handle + */ + public function dir_rewinddir() { + $this->dir_position = 0; + } + + /** + * Create a directory + * Git doesn't track empty directories, so a ".empty" file is added instead + */ + public function mkdir($path, $mode, $options) { + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); + + // Create an empty file for Git + $empty = new Granite\Git\Blob($this->repo->path()); + $empty->content(''); + $empty->write(); + + if (dirname($repo_file) == '.') { + // Adding a new directory to the root tree + $tree = $this->repo->head()->tree(); + } else { + $tree = $this->repo->factory('tree', $this->tree_search( + $this->repo, $this->repo->head()->tree(), dirname($repo_file) + ) + ); + } + + // Create our new tree, with our empty file + $dir = $this->repo->factory('tree'); + $nodes = array(); + $nodes[self::EMPTYFILE] = new Granite\Git\Tree\Node(self::EMPTYFILE, '100644', $empty->sha()); + $dir->nodes($nodes); + $dir->write(); + + // Add our new tree to its parent + $nodes = $tree->nodes(); + $nodes[basename($repo_file)] = new Granite\Git\Tree\Node(basename($repo_file), '040000', $dir->sha()); + $tree->nodes($nodes); + $tree->write(); + + // We need to recursively update each parent tree, since they are all + // hashed and the changes will cascade back up the chain + + // So, we're currently at the bottom-most directory + $current_dir = dirname($repo_file); + $previous_tree = $tree; + + if ($current_dir !== '.') { + do { + // Determine the parent directory + $previous_dir = $current_dir; + $current_dir = dirname($current_dir); + + $current_tree = $current_dir !== '.' + ? $this->repo->factory( + 'tree', $this->tree_search( + $this->repo, + $this->repo->head()->tree(), + $current_dir + ) + ) + : $this->repo->head()->tree(); + + $current_nodes = $current_tree->nodes(); + $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( + basename($previous_dir), '040000', $previous_tree->sha() + ); + $current_tree->nodes($current_nodes); + $current_tree->write(); + + $previous_tree = $current_tree; + } while ($current_dir !== '.'); + + $tree = $previous_tree; + } + + // Create a new commit to represent this write + $commit = $this->repo->factory('commit'); + $username = OC_User::getUser(); + $user_string = $username . ' ' . time() . ' +0000'; + $commit->author($user_string); + $commit->committer($user_string); + $commit->message("$username created the `$repo_file` directory, " . date('d F Y H:i', time()) . '.'); + $commit->parents(array($this->repo->head()->sha())); + $commit->tree($tree); + + // Write it to disk + $commit->write(); + + // Update the HEAD for the 'master' branch + $this->repo->head('master', $commit->sha()); + + return true; + } + + /** + * Renames a file or directory + */ + public function rename($path_from, $path_to) { + + } + + /** + * Removes a directory + */ + public function rmdir($path, $options) { + + } + + /** + * Retrieve the underlaying resource (NOT IMPLEMENTED) + */ + public function stream_cast($cast_as) { + return false; + } + + /** + * Close a resource + */ + public function stream_close() { + unset($this->blob); + return true; + } + + /** + * Tests for end-of-file on a file pointer + */ + public function stream_eof() { + return !($this->file_position < strlen($this->blob)); + } + + /** + * Flushes the output (NOT IMPLEMENTED) + */ + public function stream_flush() { + return false; + } + + /** + * Advisory file locking (NOT IMPLEMENTED) + */ + public function stream_lock($operation) { + return false; + } + + /** + * Change stream options (NOT IMPLEMENTED) + * Called in response to `chgrp()`, `chown()`, `chmod()` and `touch()` + */ + public function stream_metadata($path, $option, $var) { + return false; + } + + /** + * Opens file or URL + */ + public function stream_open($path, $mode, $options, &$opened_path) { + // Store the path, so we can use it later in `stream_write()` if necessary + $this->path = $path; + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); + + $file = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); + if ($file !== false) { + try { + $this->blob = $this->repo->factory('blob', $file)->content(); + return true; + } catch (InvalidArgumentException $e) { + // Trying to open a directory, return false below + } + } elseif ($mode !== 'r') { + // All other modes allow opening for reading and writing, clearly + // some 'write' files may not exist yet... + return true; + } + + // File could not be found or is not actually a file + return false; + } + + /** + * Read from stream + */ + public function stream_read($count) { + // Fetch the remaining set of bytes + $bytes = substr($this->blob, $this->file_position, $count); + + // If EOF or empty string, return false + if ($bytes == '' || $bytes == false) { + return false; + } + + // If $count does not extend past EOF, add $count to stream offset + if ($this->file_position + $count < strlen($this->blob)) { + $this->file_position += $count; + } else { + // Otherwise return all remaining bytes + $this->file_position = strlen($this->blob); + } + + return $bytes; + } + + /** + * Seeks to specific location in a stream + */ + public function stream_seek($offset, $whence = SEEK_SET) { + $new_offset = false; + + switch ($whence) + { + case SEEK_SET: + $new_offset = $offset; + break; + case SEEK_CUR: + $new_offset = $this->file_position += $offset; + break; + case SEEK_END: + $new_offset = strlen($this->blob) + $offset; + break; + } + + $this->file_position = $offset; + + return ($new_offset !== false); + } + + /** + * Change stream options (NOT IMPLEMENTED) + */ + public function stream_set_option($option, $arg1, $arg2) { + return false; + } + + /** + * Retrieve information about a file resource (NOT IMPLEMENTED) + */ + public function stream_stat() { + + } + + /** + * Retrieve the current position of a stream + */ + public function stream_tell() { + return $this->file_position; + } + + /** + * Truncate stream + */ + public function stream_truncate($new_size) { + + } + + /** + * Write to stream + * FIXME: Could use heavy refactoring + */ + public function stream_write($data) { + /** + * FIXME: This also needs to be added to Granite, in the form of `add()`, + * `rm()` and `commit()` calls + */ + + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($this->path); + + $node = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); + + if ($node !== false) { + // File already exists, attempting modification of existing tree + try { + $this->repo->factory('blob', $node); + + // Create our new blob with the provided $data + $blob = $this->repo->factory('blob'); + $blob->content($data); + $blob->write(); + + // We know the tree exists, so strip the filename from the path and + // find it... + + if (dirname($repo_file) == '.' || dirname($repo_file) == '') { + // Root directory + $tree = $this->repo->head()->tree(); + } else { + // Sub-directory + $tree = $this->repo->factory('tree', $this->tree_search( + $this->repo, + $this->repo->head()->tree(), + dirname($repo_file) + ) + ); + } + + // Replace the old blob with our newly modified one + $tree_nodes = $tree->nodes(); + $tree_nodes[basename($repo_file)] = new Granite\Git\Tree\Node( + basename($repo_file), '100644', $blob->sha() + ); + $tree->nodes($tree_nodes); + $tree->write(); + + // We need to recursively update each parent tree, since they are all + // hashed and the changes will cascade back up the chain + + // So, we're currently at the bottom-most directory + $current_dir = dirname($repo_file); + $previous_tree = $tree; + + if ($current_dir !== '.') { + do { + // Determine the parent directory + $previous_dir = $current_dir; + $current_dir = dirname($current_dir); + + $current_tree = $current_dir !== '.' + ? $this->repo->factory( + 'tree', $this->tree_search( + $this->repo, + $this->repo->head()->tree(), + $current_dir + ) + ) + : $this->repo->head()->tree(); + + $current_nodes = $current_tree->nodes(); + $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( + basename($previous_dir), '040000', $previous_tree->sha() + ); + $current_tree->nodes($current_nodes); + $current_tree->write(); + + $previous_tree = $current_tree; + } while ($current_dir !== '.'); + } + + // Create a new commit to represent this write + $commit = $this->repo->factory('commit'); + $username = OC_User::getUser(); + $user_string = $username . ' ' . time() . ' +0000'; + $commit->author($user_string); + $commit->committer($user_string); + $commit->message("$username modified the `$repo_file` file, " . date('d F Y H:i', time()) . '.'); + $commit->parents(array($this->repo->head()->sha())); + $commit->tree($previous_tree); + + // Write it to disk + $commit->write(); + + // Update the HEAD for the 'master' branch + $this->repo->head('master', $commit->sha()); + + // If we made it this far, write was successful - update the stream + // position and return the number of bytes written + $this->file_position += strlen($data); + return strlen($data); + + } catch (InvalidArgumentException $e) { + // Attempting to write to a directory or other error, fail + return 0; + } + } else { + // File does not exist, needs to be created + + // Create our new blob with the provided $data + $blob = $this->repo->factory('blob'); + $blob->content($data); + $blob->write(); + + if (dirname($repo_file) == '.') { + // Trying to add a new file to the root tree, nice and easy + $tree = $this->repo->head()->tree(); + $tree_nodes = $tree->nodes(); + $tree_nodes[basename($repo_file)] = new Granite\Git\Tree\Node( + basename($repo_file), '100644', $blob->sha() + ); + $tree->nodes($tree_nodes); + $tree->write(); + } else { + // Trying to add a new file to a subdirectory, try and find it + $tree = $this->repo->factory('tree', $this->tree_search( + $this->repo, $this->repo->head()->tree(), dirname($repo_file) + ) + ); + + // Add the blob to the tree + $nodes = $tree->nodes(); + $nodes[basename($repo_file)] = new Granite\Git\Tree\Node( + basename($repo_file), '100644', $blob->sha() + ); + $tree->nodes($nodes); + $tree->write(); + + // We need to recursively update each parent tree, since they are all + // hashed and the changes will cascade back up the chain + + // So, we're currently at the bottom-most directory + $current_dir = dirname($repo_file); + $previous_tree = $tree; + + if ($current_dir !== '.') { + do { + // Determine the parent directory + $previous_dir = $current_dir; + $current_dir = dirname($current_dir); + + $current_tree = $current_dir !== '.' + ? $this->repo->factory( + 'tree', $this->tree_search( + $this->repo, + $this->repo->head()->tree(), + $current_dir + ) + ) + : $this->repo->head()->tree(); + + $current_nodes = $current_tree->nodes(); + $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( + basename($previous_dir), '040000', $previous_tree->sha() + ); + $current_tree->nodes($current_nodes); + $current_tree->write(); + + $previous_tree = $current_tree; + } while ($current_dir !== '.'); + + $tree = $previous_tree; + } + } + + // Create a new commit to represent this write + $commit = $this->repo->factory('commit'); + $username = OC_User::getUser(); + $user_string = $username . ' ' . time() . ' +0000'; + $commit->author($user_string); + $commit->committer($user_string); + $commit->message("$username created the `$repo_file` file, " . date('d F Y H:i', time()) . '.'); + $commit->parents(array($this->repo->head()->sha())); + $commit->tree($tree); // Top-level tree (NOT the newly modified tree) + + // Write it to disk + $commit->write(); + + // Update the HEAD for the 'master' branch + $this->repo->head('master', $commit->sha()); + + // If we made it this far, write was successful - update the stream + // position and return the number of bytes written + $this->file_position += strlen($data); + return strlen($data); + } + + // Write failed + return 0; + } + + /** + * Delete a file + */ + public function unlink($path) { + + } + + /** + * Retrieve information about a file + */ + public function url_stat($path, $flags) { + // Parse the URL into a repository directory, file path and commit ID + list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); + + $node = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); + + if ($node == false && $this->commit->sha() == $this->repo->head()->sha()) { + // A new file - no information available + $size = 0; + $mtime = -1; + } else { + + // Is it a directory? + try { + $this->repo->factory('tree', $node); + $size = 4096; // FIXME + } catch (InvalidArgumentException $e) { + // Must be a file + $size = strlen(file_get_contents($path)); + } + + // Parse the timestamp from the commit message + preg_match('/[0-9]{10}+/', $this->commit->committer(), $matches); + $mtime = $matches[0]; + } + + $stat["dev"] = ""; + $stat["ino"] = ""; + $stat["mode"] = ""; + $stat["nlink"] = ""; + $stat["uid"] = ""; + $stat["gid"] = ""; + $stat["rdev"] = ""; + $stat["size"] = $size; + $stat["atime"] = $mtime; + $stat["mtime"] = $mtime; + $stat["ctime"] = $mtime; + $stat["blksize"] = ""; + $stat["blocks"] = ""; + + return $stat; + } + + /** + * Debug function for development purposes + */ + private function debug($message, $level = OC_Log::DEBUG) + { + if ($this->debug) { + OC_Log::write('files_versioning', $message, $level); + } + } + + /** + * Parses a URL of the form: + * `versioned://path/to/git/repository/.git/path/to/file#SHA-1-commit-id` + * FIXME: Will throw an InvalidArgumentException if $path is invaid + * + * @param string $path The path to parse + * + * @return array An array containing an instance of Granite\Git\Repository, + * the file path, and an instance of Granite\Git\Commit + * @throws InvalidArgumentException If the repository cannot be loaded + */ + private function parse_url($path) + { + preg_match('/\/([A-Za-z0-9\/]+\.git\/)([A-Za-z0-9\/\.\/]*)(#([A-Fa-f0-9]+))*/', $path, $matches); + + // Load up the repo + $repo = new \Granite\Git\Repository($matches[1]); + // Parse the filename (stripping any trailing slashes) + $repo_file = $matches[2]; + if (substr($repo_file, -1) == '/') { + $repo_file = substr($repo_file, 0, -1); + } + + // Default to HEAD if no commit is provided + $repo_commit = isset($matches[4]) + ? $matches[4] + : $repo->head()->sha(); + + // Load the relevant commit + $commit = $repo->factory('commit', $repo_commit); + + return array($repo, $repo_file, $commit); + } + + /** + * Recursively searches a tree for a path, returning FALSE if is not found + * or an SHA-1 id if it is found. + * + * @param string $repo The repository containing the tree object + * @param string $tree The tree object to search + * @param string $path The path to search for (relative to the tree) + * @param int $depth The depth of the current search (for recursion) + * + * @return string|boolean The SHA-1 id of the sub-tree + */ + private function tree_search($repo, $tree, $path, $depth = 0) + { + $paths = array_values(explode(DIRECTORY_SEPARATOR, $path)); + + $current_path = $paths[$depth]; + + $nodes = $tree->nodes(); + foreach ($nodes as $node) { + if ($node->name() == $current_path) { + + if (count($paths)-1 == $depth) { + // Stop, found it + return $node->sha(); + } + + // Recurse if necessary + if ($node->isDirectory()) { + $tree = $this->repo->factory('tree', $node->sha()); + return $this->tree_search($repo, $tree, $path, $depth + 1); + } + } + } + + return false; + } + +} diff --git a/apps/gallery/js/album_cover.js b/apps/gallery/js/album_cover.js index 061bbcd0b47..d44e7f83d1f 100644 --- a/apps/gallery/js/album_cover.js +++ b/apps/gallery/js/album_cover.js @@ -43,8 +43,9 @@ function shareGallery() { {text: 'Shared gallery address', name: 'address', type: 'text', value: existing_token}]; OC.dialogs.form(form_fields, t('gallery', 'Share gallery'), function(values){ var p = ''; - for (var i in paths) p += '/'+paths[i]; + for (var i in paths) p += paths[i]+'/'; if (p == '') p = '/'; + alert(p); $.getJSON(OC.filePath('gallery', 'ajax', 'galleryOp.php'), {operation: 'share', path: p, share: values[0].value, recursive: values[1].value}, function(r) { if (r.status == 'success') { Albums.shared = r.sharing; @@ -112,42 +113,28 @@ function scanForAlbums(cleanup) { } function settings() { - $( '#g-dialog-settings' ).dialog({ - height: 180, - width: 350, - modal: false, - buttons: [ - { - text: t('gallery', 'Apply'), - click: function() { - var scanning_root = $('#g-scanning-root').val(); - var disp_order = $('#g-display-order option:selected').val(); + OC.dialogs.form([{text: t('gallery', 'Scanning root'), name: 'root', type:'text', value:gallery_scanning_root}, + {text: t('gallery', 'Default order'), name: 'order', type:'select', value:gallery_default_order, options:[ + {text:t('gallery', 'Ascending'), value:'ASC'}, {text: t('gallery', 'Descending'), value:'DESC'} ]}], + t('gallery', 'Settings'), + function(values) { + var scanning_root = values[0].value; + var disp_order = values[1].value; if (scanning_root == '') { - alert('Scanning root cannot be empty'); + OC.dialogs.alert(t('gallery', 'Scanning root cannot be empty'), t('gallery', 'Error')); return; } $.getJSON(OC.filePath('gallery','ajax','galleryOp.php'), {operation: 'store_settings', root: scanning_root, order: disp_order}, function(r) { if (r.status == 'success') { - if (r.rescan == 'yes') { - $('#g-dialog-settings').dialog('close'); - Albums.clear(document.getElementById('gallery_list')); - scanForAlbums(true); - return; - } + if (r.rescan == 'yes') { + Albums.clear(document.getElementById('gallery_list')); + scanForAlbums(true); + } + gallery_scanning_root = scanning_root; } else { - alert('Error: ' + r.cause); - return; + OC.dialogs.alert(t('gallery', 'Error: ') + r.cause, t('gallery', 'Error')); + return; } - $('#g-dialog-settings').dialog('close'); }); - } - }, - { - text: t('gallery', 'Cancel'), - click: function() { - $(this).dialog('close'); - } - } - ], - }); + }); } diff --git a/apps/gallery/templates/index.php b/apps/gallery/templates/index.php index c6373d3b0a2..9bec5db1b91 100644 --- a/apps/gallery/templates/index.php +++ b/apps/gallery/templates/index.php @@ -9,7 +9,7 @@ OC_Util::addScript('files_imageviewer', 'jquery.fancybox-1.3.4.pack'); OC_Util::addStyle( 'files_imageviewer', 'jquery.fancybox-1.3.4' ); $l = new OC_L10N('gallery'); ?> - +<script type="text/javascript">var gallery_scanning_root='<? echo OC_Preferences::getValue(OC_User::getUser(), 'gallery', 'root', '/'); ?>'; var gallery_default_order = '<? echo OC_Preferences::getValue(OC_User::getUser(), 'gallery', 'order', 'ASC'); ?>';</script> <div id="controls"> <div id="scan"> <div id="scanprogressbar"></div> @@ -29,40 +29,3 @@ $l = new OC_L10N('gallery'); </div> <div id="gallery_list"> </div> - -<div id="dialog-confirm" title="<?php echo $l->t('Remove confirmation');?>" style="display: none"> - <p><span class="ui-icon ui-icon-alert" style="float:left; margin:0 7px 20px 0;"></span><?php echo $l->t('Do you want to remove album');?> <span id="albumName"></span>?</p> -</div> - -<div id="dialog-form" title="<?php echo $l->t('Change album name');?>" style="display:none"> - <form> - <fieldset> - <label for="name"><?php echo $l->t('New album name');?></label> - <input type="text" name="name" id="name" class="text ui-widget-content ui-corner-all" /> - </fieldset> - </form> -</div> - -<div id="g-dialog-settings" title="<?php echo $l->t('Settings');?>" style="display:none"> - <form> - <fieldset><?php $root = OC_Preferences::getValue(OC_User::getUser(), 'gallery', 'root', '/'); $order = OC_Preferences::getValue(OC_User::getUser(), 'gallery', 'order', 'ASC');?> - <label for="name"><?php echo $l->t('Scanning root');?></label> - <input type="text" name="g-scanning-root" id="g-scanning-root" class="text ui-widget-content ui-corner-all" value="<?php echo $root;?>" /><br/> - - <label for="sort"><?php echo $l->t('Default sorting'); ?></label> - <select id="g-display-order"> - <option value="ASC"<?php echo $order=='ASC'?'selected':'';?>><?php echo $l->t('Ascending'); ?></option> - <option value="DESC"<?php echo $order=='DESC'?'selected':'';?>><?php echo $l->t('Descending'); ?></option> - </select><br/> -<!-- - <label for="sort"><?php echo $l->t('Thumbnails size'); ?></label> - <select> - <option value="100">100px</option> - <option value="150">150px</option> - <option value="200">200px</option> - </select> - --> - </fieldset> - </form> -</div> - diff --git a/apps/remoteStorage/appinfo/info.xml b/apps/remoteStorage/appinfo/info.xml index 0936bf9bd0f..1f9618a3334 100644 --- a/apps/remoteStorage/appinfo/info.xml +++ b/apps/remoteStorage/appinfo/info.xml @@ -2,8 +2,8 @@ <info> <id>remoteStorage</id> <name>remoteStorage compatibility</name> - <description>Enables you to use ownCloud as their remote storage for unhosted applications. This app requires the Webfinger app to be enabled as well. More info on <a href="http://unhosted.org">the website of the unhosted movement</a>.</description> - <version>0.5</version> + <description>Enables you to use ownCloud as their remote storage for unhosted applications. This app requires the Webfinger app to be installed and enabled correctly. More info on <a href="http://unhosted.org">the website of the unhosted movement</a>.</description> + <version>0.6</version> <licence>AGPL or MIT</licence> <author>Michiel de Jong</author> <require>2</require> diff --git a/apps/remoteStorage/appinfo/webfinger.php b/apps/remoteStorage/appinfo/webfinger.php new file mode 100644 index 00000000000..7c0ab846057 --- /dev/null +++ b/apps/remoteStorage/appinfo/webfinger.php @@ -0,0 +1,6 @@ + <Link + rel="remoteStorage" + template="<?php echo WF_BASEURL; ?>/apps/remoteStorage/WebDAV.php/<?php echo WF_USER; ?>/remoteStorage/{category}/" + api="WebDAV" + auth="<?php echo WF_BASEURL; ?>/apps/remoteStorage/auth.php/<?php echo WF_USER; ?>"> + </Link> diff --git a/apps/user_ldap/appinfo/info.xml b/apps/user_ldap/appinfo/info.xml index 9a6ee1436fc..99830dd1ffd 100644 --- a/apps/user_ldap/appinfo/info.xml +++ b/apps/user_ldap/appinfo/info.xml @@ -7,4 +7,7 @@ <licence>AGPL</licence> <author>Dominik Schmidt</author> <require>2</require> + <types> + <authentication/> + </types> </info> diff --git a/apps/user_migrate/admin.php b/apps/user_migrate/admin.php new file mode 100644 index 00000000000..0160753af22 --- /dev/null +++ b/apps/user_migrate/admin.php @@ -0,0 +1,87 @@ +<?php + +/** + * ownCloud - user_migrate + * + * @author Tom Needham + * @copyright 2012 Tom Needham tom@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ +OC_Util::checkAdminUser(); +OC_Util::checkAppEnabled('user_migrate'); + +// Import? +if (isset($_POST['user_import'])) { + + $root = OC::$SERVERROOT . "/"; + $importname = "owncloud_import_" . date("y-m-d_H-i-s"); + + // Save data dir for later + $datadir = OC_Config::getValue( 'datadirectory' ); + + // Copy the uploaded file + $from = $_FILES['owncloud_import']['tmp_name']; + $to = get_temp_dir().'/'.$importname.'.zip'; + if( !move_uploaded_file( $from, $to ) ){ + $error = array('error'=>'Failed to move the uploaded file','hint'=>'Try checking the permissions of the '.get_temp_dir().' dir.'); + OC_Log::write( 'user_migrate', "Failed to copy the uploaded file", OC_Log::ERROR ); + $tmpl = new OC_Template('user_migrate', 'admin'); + $tmpl->assign('error',$error); + return $tmpl->fetchPage(); + } + $response = json_decode( OC_Migrate::import( $to, 'user' ) ); + if( !$response->success ){ + $error = array('error'=>'There was an error while importing the user!','hint'=>'Please check the logs for a more detailed explaination'); + $tmpl = new OC_Template('user_migrate', 'admin'); + $tmpl->assign('error',$error); + return $tmpl->fetchPage(); + } else { + // Check import status + foreach( $response->data as $app => $status ){ + if( $status != 'true' ){ + // It failed for some reason + if( $status == 'notsupported' ){ + $notsupported[] = $app; + } else if( !$status ){ + $failed[] = $app; + } + } + } + // Any problems? + if( isset( $notsupported ) || isset( $failed ) ){ + if( count( $failed ) > 0 ){ + $error = array('error'=>'Some app data failed to import','hint'=>'App data for: '.implode(', ', $failed).' failed to import.'); + $tmpl = new OC_Template('user_migrate', 'admin'); + $tmpl->assign('error',$error); + return $tmpl->fetchPage(); + } else if( count( $notsupported ) > 0 ){ + $error = array('error'=>'Some app data could not be imported, as the apps are not installed on this instance','hint'=>'App data for: '.implode(', ', $notsupported).' failed to import as they were not found. Please install the apps and try again'); + $tmpl = new OC_Template('user_migrate', 'admin'); + $tmpl->assign('error',$error); + return $tmpl->fetchPage(); + } + } else { + // Went swimmingly! + $tmpl = new OC_Template('user_migrate', 'admin'); + return $tmpl->fetchPage(); + } + } + +} else { +// fill template + $tmpl = new OC_Template('user_migrate', 'admin'); + return $tmpl->fetchPage(); +}
\ No newline at end of file diff --git a/apps/user_migrate/ajax/export.php b/apps/user_migrate/ajax/export.php new file mode 100644 index 00000000000..86745d6b162 --- /dev/null +++ b/apps/user_migrate/ajax/export.php @@ -0,0 +1,62 @@ +<?php + +/** + * ownCloud - user_migrate + * + * @author Tom Needham + * @copyright 2012 Tom Needham tom@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ +// Init owncloud +require_once('../../../lib/base.php'); + +// Check if we are a user +OC_JSON::checkLoggedIn(); +OC_Util::checkAppEnabled('user_migrate'); +// Which operation +if( $_GET['operation']=='create' ){ + $uid = !empty( $_POST['uid'] ) ? $_POST['uid'] : OC_User::getUser(); + if( $uid != OC_User::getUser() ){ + // Needs to be admin to export someone elses account + OC_JSON::error(); + die(); + } + // Create the export zip + $response = json_decode( OC_Migrate::export( $uid ) ); + if( !$response->success ){ + // Error + OC_JSON::error(); + die(); + } else { + // Save path in session + $_SESSION['ocuserexportpath'] = $response->data; + } + OC_JSON::success(); + die(); +} else if( $_GET['operation']=='download' ){ + // Download the export + $path = isset( $_SESSION['ocuserexportpath'] ) ? $_SESSION['ocuserexportpath'] : false; + if( !$path ){ + OC_JSON::error(); + } + header("Content-Type: application/zip"); + header("Content-Disposition: attachment; filename=" . basename($path)); + header("Content-Length: " . filesize($path)); + @ob_end_clean(); + readfile($path); + unlink( $path ); + $_SESSION['ocuserexportpath'] = ''; +} diff --git a/apps/user_migrate/appinfo/app.php b/apps/user_migrate/appinfo/app.php new file mode 100644 index 00000000000..a59b6dd705c --- /dev/null +++ b/apps/user_migrate/appinfo/app.php @@ -0,0 +1,35 @@ +<?php + +/** +* ownCloud - user_migrate +* +* @author Tom Needham +* @copyright 2012 Tom Needham tom@owncloud.com +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU AFFERO GENERAL PUBLIC LICENSE for more details. +* +* You should have received a copy of the GNU Affero General Public +* License along with this library. If not, see <http://www.gnu.org/licenses/>. +* +*/ + +OC_APP::registerPersonal( 'user_migrate', 'settings' ); +OC_APP::registerAdmin( 'user_migrate', 'admin' ); +OC_Util::addScript( 'user_migrate', 'export'); + +// add settings page to navigation +$entry = array( + 'id' => "user_migrate_settings", + 'order'=>1, + 'href' => OC_Helper::linkTo( "user_migrate", "admin.php" ), + 'name' => 'Import' +); +?>
\ No newline at end of file diff --git a/apps/user_migrate/appinfo/info.xml b/apps/user_migrate/appinfo/info.xml new file mode 100644 index 00000000000..6abcb4af92c --- /dev/null +++ b/apps/user_migrate/appinfo/info.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> +<info> + <id>user_migrate</id> + <name>User Account Migration</name> + <description>Migrate your user accounts</description> + <version>0.1</version> + <licence>AGPL</licence> + <author>Tom Needham</author> + <require>2</require> + <default_enable/> +</info> diff --git a/apps/user_migrate/js/export.js b/apps/user_migrate/js/export.js new file mode 100644 index 00000000000..2d660b2de6b --- /dev/null +++ b/apps/user_migrate/js/export.js @@ -0,0 +1,27 @@ +$(document).ready(function(){ + // Do the export + $('#exportbtn').click(function(){ + // Show loader + $('.loading').show(); + $.getJSON( + OC.filePath('user_migrate','ajax','export.php'), + {operation:'create'}, + function(result){ + if(result.status == 'success'){ + // Download the file + window.location = OC.filePath('user_migrate','ajax','export.php?operation=download') ; + $('.loading').hide(); + $('#exportbtn').val(t('user_migrate', 'Export')); + } else { + // Cancel loading + $('#exportbtn').html('Failed'); + // Show Dialog + OC.dialogs.alert(t('user_migrate', 'Something went wrong while the export file was being generated'), t('user_migrate', 'An error has occurred'), function(){ + $('#exportbtn').html(t('user_migrate', 'Export')+'<img style="display: none;" class="loading" src="'+OC.filePath('core','img','loading.gif')+'" />'); + }); + } + } + // End ajax + ); + }); +});
\ No newline at end of file diff --git a/apps/user_migrate/settings.php b/apps/user_migrate/settings.php new file mode 100644 index 00000000000..62f347b3557 --- /dev/null +++ b/apps/user_migrate/settings.php @@ -0,0 +1,29 @@ +<?php + +/** + * ownCloud - user_migrate + * + * @author Thomas Schmidt + * @copyright 2011 Thomas Schmidt tom@opensuse.org + * @author Tom Needham + * @copyright 2012 Tom Needham tom@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ +OC_Util::checkAppEnabled('user_migrate'); + +// fill template +$tmpl = new OC_Template('user_migrate', 'settings'); +return $tmpl->fetchPage();
\ No newline at end of file diff --git a/apps/user_migrate/templates/admin.php b/apps/user_migrate/templates/admin.php new file mode 100644 index 00000000000..b01e5c7579a --- /dev/null +++ b/apps/user_migrate/templates/admin.php @@ -0,0 +1,13 @@ +<form id="import" action="#" method="post" enctype="multipart/form-data"> + <fieldset class="personalblock"> + <?php if(isset($_['error'])){ ?> + <h3><?php echo $_['error']['error']; ?></h3> + <p><?php echo $_['error']['hint']; ?></p> + <?php } ?> + <legend><strong><?php echo $l->t('Import user account');?></strong></legend> + </p> + <p><input type="file" id="owncloud_import" name="owncloud_import"><label for="owncloud_import"><?php echo $l->t('ownCloud User Zip');?></label> + </p> + <input type="submit" name="user_import" value="<?php echo $l->t('Import'); ?>" /> + </fieldset> +</form> diff --git a/apps/user_migrate/templates/settings.php b/apps/user_migrate/templates/settings.php new file mode 100644 index 00000000000..5f4857de5fa --- /dev/null +++ b/apps/user_migrate/templates/settings.php @@ -0,0 +1,6 @@ +<fieldset class="personalblock"> + <legend><strong><?php echo $l->t('Export your user account');?></strong></legend> + <p><?php echo $l->t('This will create a compressed file that contains your ownCloud account.');?> + </p> + <button id="exportbtn">Export<img style="display: none;" class="loading" src="<?php echo OC_Helper::linkTo('core', 'img/loading.gif'); ?>" /></button> +</fieldset> diff --git a/apps/user_openid/appinfo/info.xml b/apps/user_openid/appinfo/info.xml index 37be15abfda..721db1877e3 100644 --- a/apps/user_openid/appinfo/info.xml +++ b/apps/user_openid/appinfo/info.xml @@ -7,4 +7,7 @@ <licence>AGPL</licence> <author>Robin Appelman</author> <require>2</require> + <types> + <authentication/> + </types> </info> diff --git a/apps/user_webfinger/.htaccess b/apps/user_webfinger/.htaccess deleted file mode 100644 index 4d4d2e9c58f..00000000000 --- a/apps/user_webfinger/.htaccess +++ /dev/null @@ -1,3 +0,0 @@ -RewriteEngine On -RewriteBase / -RewriteRule host-meta$ \/\.well-known\/host-meta\.php [L] diff --git a/apps/user_webfinger/appinfo/info.xml b/apps/user_webfinger/appinfo/info.xml index 55cf2cf2201..d47fb723a3a 100644 --- a/apps/user_webfinger/appinfo/info.xml +++ b/apps/user_webfinger/appinfo/info.xml @@ -2,9 +2,9 @@ <info> <id>user_webfinger</id> <name>Webfinger</name> - <description>Provide WebFinger for all users so they get a user address like user@owncloudinstance which can be used for unhosted applications. If you don't run ownCloud in the root of your domain, for instance if you run it on example.com/owncloud/, then make sure you link example.com/.well-known/ to example.com/owncloud/apps/user_webfinger/ - by running something like "ln -s /var/www/owncloud/apps/user_webfinger /var/www/.well-known". Only enable this app if you run this ownCloud installation on a public web address, not if you run it on an intranet or on localhost.</description> - <version>0.2</version> + <description>Provide WebFinger for all users so they get a user address like user@owncloudinstance which can be used for external applications. Other apps can provide information for webfinger requests, such as remoteStorage compatibility.</description> + <version>0.3</version> <licence>AGPL or MIT</licence> - <author>Michiel de Jong</author> + <author>Michiel de Jong, Florian Hülsmann</author> <require>2</require> </info> diff --git a/apps/user_webfinger/appinfo/install.php b/apps/user_webfinger/appinfo/install.php index f570a3a249b..c8d9a427425 100644 --- a/apps/user_webfinger/appinfo/install.php +++ b/apps/user_webfinger/appinfo/install.php @@ -1,6 +1,51 @@ <?php +$hostMetaHeader = array( + 'Access-Control-Allow-Origin' => '*', + 'Content-Type' => 'application/xml+xrd' +); $appInfoDir = __DIR__; $thisAppDir = dirname($appInfoDir); $appsDir = dirname($thisAppDir); $ownCloudDir = dirname($appsDir); -@symlink($thisAppDir, $ownCloudDir.'/.well-known'); +$docRoot = $_SERVER['DOCUMENT_ROOT']; +try { + $webRoot = substr(realpath($ownCloudDir), strlen(realpath($docRoot))); +} catch(Exception $e) { + // some servers fail on realpath(), let's try it the unsecure way: + $webRoot = substr($ownCloudDir, strlen($docRoot)); +} +$serverName = $_SERVER['SERVER_NAME']; +$lrddTmpl = 'http'; +if(isset($_SERVER['HTTPS'])) { + $lrddTmpl .= 's'; +} +$lrddTmpl .= '://' . $serverName . $webRoot . '/apps/user_webfinger/webfinger.php?q={uri}'; +$hostMetaPath = $docRoot . '/.well-known/host-meta'; +$hostMetaDir = $docRoot . '/.well-known'; +$hostMetaContents = "<?xml version=\"1.0\" encoding=\"UTF-8\"?> +<XRD xmlns=\"http://docs.oasis-open.org/ns/xri/xrd-1.0\" xmlns:hm=\"http://host-meta.net/xrd/1.0\"> + <hm:Host xmlns=\"http://host-meta.net/xrd/1.0\">" . $serverName . "</hm:Host> + <Link rel=\"lrdd\" template=\"" . $lrddTmpl . "\"> + <Title>Resource Descriptor</Title> + </Link> +</XRD>"; +@mkdir($hostMetaDir); +$hostMeta = fopen($hostMetaPath, 'w'); +if(!$hostMeta) { + die("Could not open " . $hostMetaPath . " for writing, please check permissions!"); +} +if(!fwrite($hostMeta, $hostMetaContents, strlen($hostMetaContents))) { + die("Could not write to " . $hostMetaPath . ", please check permissions!"); +} +fclose($hostMeta); + +// write custom headers into .htaccess: +$htaccess = fopen($hostMetaDir . '/.htaccess', 'w'); +//TODO: check compatibility! +fwrite($htaccess, "<filesMatch \"^host-meta$\"> +<ifModule mod_headers.c>\n"); +foreach($hostMetaHeader as $header => $value) { + fwrite($htaccess, "Header set " . $header . " \"" . $value . "\"\n"); +} +fwrite($htaccess, "</ifModule>\n</filesMatch>"); +fclose($htaccess); diff --git a/apps/user_webfinger/host-meta b/apps/user_webfinger/host-meta deleted file mode 100644 index dfaf3636145..00000000000 --- a/apps/user_webfinger/host-meta +++ /dev/null @@ -1 +0,0 @@ -please run 'a2enmod rewrite' on your server, set 'AllowOverride All' for /var/www in /etc/apache2/sites-enabled/000-default or equivalent, and then run '/etc/init.d/apache2 restart' diff --git a/apps/user_webfinger/host-meta.php b/apps/user_webfinger/host-meta.php deleted file mode 100644 index ac577cf9a0c..00000000000 --- a/apps/user_webfinger/host-meta.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -if($_SERVER['SCRIPT_NAME'] == '/.well-known/host-meta.php') { - header("Access-Control-Allow-Origin: *"); -} else { - header('Please-first: activate'); -} -header("Content-Type: application/xrd+xml"); -echo "<"; -?> -?xml version="1.0" encoding="UTF-8"?> -<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0" xmlns:hm="http://host-meta.net/xrd/1.0"> - <hm:Host xmlns="http://host-meta.net/xrd/1.0"><?php echo $_SERVER['SERVER_NAME'] ?></hm:Host> - <Link rel="lrdd" template="http<?php echo (isset($_SERVER['HTTPS'])?'s':''); ?>://<?php echo $_SERVER['SERVER_NAME'] ?>/.well-known/webfinger.php?q={uri}"> - </Link> -</XRD> - diff --git a/apps/user_webfinger/webfinger.php b/apps/user_webfinger/webfinger.php index 5c2a24aa070..9ada473ca87 100644 --- a/apps/user_webfinger/webfinger.php +++ b/apps/user_webfinger/webfinger.php @@ -1,41 +1,71 @@ <?php -if($_SERVER['SCRIPT_NAME'] == '/.well-known/webfinger.php') { - header("Access-Control-Allow-Origin: *"); -} else { - header('Please-first: activate'); -} +header("Access-Control-Allow-Origin: *"); header("Content-Type: application/xrd+xml"); +/** + * To include your app in the webfinger XML, add a new script with file name + * 'webfinger.php' to /apps/yourapp/appinfo/, which prints out the XML parts + * to be included. That script can make use of the constants WF_USER (e. g. + * "user"), WF_ID (user@host) and WF_BASEURL (e. g. https://host/owncloud). + * An example could look like this: + * + * <Link + * rel="myProfile" + * type="text/html" + * href="<?php echo WF_BASEURL; ?>/apps/myApp/profile.php?user=<?php echo WF_USER; ?>"> + * </Link> + * + '* but can also use complex database queries to generate the webfinger result + **/ // calculate the documentroot // modified version of the one in lib/base.php that takes the .well-known symlink into account -$DOCUMENTROOT=realpath($_SERVER['DOCUMENT_ROOT']); +/*$DOCUMENTROOT=realpath($_SERVER['DOCUMENT_ROOT']); $SERVERROOT=str_replace("\\",'/',dirname(dirname(dirname(dirname(__FILE__))))); $SUBURI=substr(realpath($_SERVER["SCRIPT_FILENAME"]),strlen($SERVERROOT)); $WEBROOT=substr($SUBURI,0,-34); +*/ +require_once('../../lib/base.php'); +$request = urldecode($_GET['q']); if($_GET['q']) { - $bits = explode('@', $_GET['q']); - $userName = $bits[0]; + $reqParts = explode('@', $request); + $userName = $reqParts[0]; + $hostName = $reqParts[1]; } else { $userName = ''; + $hostName = ''; } if(substr($userName, 0, 5) == 'acct:') { $userName = substr($userName, 5); } +if($userName == "") { + $id = ""; +} else { + $id = $userName . '@' . $hostName; +} if(isset($_SERVER['HTTPS'])) { - $baseAddress = 'https://'.$_SERVER['SERVER_NAME'].'/apps/remoteStorage/'; + $baseAddress = 'https://'; } else { - $baseAddress = 'http://'.$_SERVER['SERVER_NAME'].'/apps/remoteStorage/'; + $baseAddress = 'http://'; } +$baseAddress .= $_SERVER['SERVER_NAME'].OC::$WEBROOT; +define('WF_USER', $userName); +define('WF_ID', $id); +define('WF_BASEURL', $baseAddress); echo "<"; ?> ?xml version="1.0" encoding="UTF-8"?> <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0" xmlns:hm="http://host-meta.net/xrd/1.0"> - <hm:Host xmlns="http://host-meta.net/xrd/1.0"><?php echo $_SERVER['SERVER_NAME'] ?></hm:Host> - <Link - rel="remoteStorage" - template="<?php echo $baseAddress ?>WebDAV.php/<?php echo $userName ?>/remoteStorage/{category}/" - api="WebDAV" - auth="<?php echo $baseAddress; ?>auth.php/<?php echo $userName ?>" - ></Link> + <hm:Host xmlns="http://host-meta.net/xrd/1.0"><?php echo $_SERVER['SERVER_NAME']; ?></hm:Host> + <Subject>acct:<?php echo $id ?></Subject> +<?php +$apps = OC_Appconfig::getApps(); +foreach($apps as $app) { + if(OC_App::isEnabled($app)) { + if(is_file(OC::$APPSROOT . '/apps/' . $app . '/appinfo/webfinger.php')) { + require($app . '/appinfo/webfinger.php'); + } + } +} +?> </XRD> diff --git a/core/css/styles.css b/core/css/styles.css index 720fe578b92..e5380b1b6b0 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -131,3 +131,10 @@ li.error { width:640px; margin:4em auto; padding:1em 1em 1em 4em; background:#ff .separator { display: inline; border-left: 1px solid #d3d3d3; border-right: 1px solid #fff; height: 10px; width:0px; margin: 4px; } a.bookmarklet { background-color: #ddd; border:1px solid #ccc; padding: 5px;padding-top: 0px;padding-bottom: 2px; text-decoration: none; margin-top: 5px } + +/* ---- DIALOGS ---- */ + +#dirtree {width: 100%;} +#filelist {height: 270px; overflow:scroll; background-color: white;} +.filepicker_element_selected { background-color: lightblue;} +.filepicker_loader {height: 120px; width: 100%; background-color: #333; opacity: 0.3; visibility: visible; position:absolute; top:0; left:0; text-align:center; padding-top: 150px;} diff --git a/core/img/filetypes/application-sgf.png b/core/img/filetypes/application-sgf.png Binary files differnew file mode 100644 index 00000000000..f171f5579e7 --- /dev/null +++ b/core/img/filetypes/application-sgf.png diff --git a/core/js/eventsource.js b/core/js/eventsource.js index dece1a69d04..08259e02cae 100644 --- a/core/js/eventsource.js +++ b/core/js/eventsource.js @@ -33,8 +33,12 @@ */ OC.EventSource=function(src,data){ var dataStr=''; - for(name in data){ - dataStr+=name+'='+encodeURIComponent(data[name])+'&'; + this.typelessListeners=[]; + this.listeners={}; + if(data){ + for(name in data){ + dataStr+=name+'='+encodeURIComponent(data[name])+'&'; + } } if(!this.useFallBack && typeof EventSource !='undefined'){ this.source=new EventSource(src+'?'+dataStr); @@ -42,7 +46,7 @@ OC.EventSource=function(src,data){ for(var i=0;i<this.typelessListeners.length;i++){ this.typelessListeners[i](JSON.parse(e.data)); } - } + }.bind(this); }else{ iframeId='oc_eventsource_iframe_'+OC.EventSource.iframeCount; OC.EventSource.fallBackSources[OC.EventSource.iframeCount]=this; @@ -64,7 +68,7 @@ OC.EventSource=function(src,data){ OC.EventSource.fallBackSources=[]; OC.EventSource.iframeCount=0;//number of fallback iframes OC.EventSource.fallBackCallBack=function(id,type,data){ - OC.EventSource.fallBackSources[id].fallBackCallBack(type,JSON.parse(data)); + OC.EventSource.fallBackSources[id].fallBackCallBack(type,data); } OC.EventSource.prototype={ typelessListeners:[], diff --git a/core/js/js.js b/core/js/js.js index df1b5c6ce76..44b4f503b8c 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -126,7 +126,13 @@ OC={ }); } }, - dialogs:OCdialogs + dialogs:OCdialogs, + mtime2date:function(mtime) { + mtime = parseInt(mtime); + var date = new Date(1000*mtime); + var ret = date.getDate()+'.'+(date.getMonth()+1)+'.'+date.getFullYear()+', '+date.getHours()+':'+date.getMinutes(); + return ret; + } }; OC.search.customResults={}; OC.search.currentResult=-1; diff --git a/core/js/oc-dialogs.js b/core/js/oc-dialogs.js index c11ac13332b..a3aa1e8c149 100644 --- a/core/js/oc-dialogs.js +++ b/core/js/oc-dialogs.js @@ -17,7 +17,6 @@ * You should have received a copy of the GNU Affero General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * - * todo(bartek): add select option in form */ /** @@ -30,9 +29,9 @@ OCdialogs = { * @param title dialog title * @param callback which will be triggered when user press OK */ - alert:function(text, title, callback) { + alert:function(text, title, callback, modal) { var content = '<p><span class="ui-icon ui-icon-alert"></span>'+text+'</p>'; - OCdialogs.message(content, title, OCdialogs.ALERT_DIALOG, OCdialogs.OK_BUTTON, callback); + OCdialogs.message(content, title, OCdialogs.ALERT_DIALOG, OCdialogs.OK_BUTTON, callback, modal); }, /** * displays info dialog @@ -40,9 +39,9 @@ OCdialogs = { * @param title dialog title * @param callback which will be triggered when user press OK */ - info:function(text, title, callback) { + info:function(text, title, callback, modal) { var content = '<p><span class="ui-icon ui-icon-info"></span>'+text+'</p>'; - OCdialogs.message(content, title, OCdialogs.ALERT_DIALOG, OCdialogs.OK_BUTTON, callback); + OCdialogs.message(content, title, OCdialogs.ALERT_DIALOG, OCdialogs.OK_BUTTON, callback, modal); }, /** * displays confirmation dialog @@ -50,9 +49,9 @@ OCdialogs = { * @param title dialog title * @param callback which will be triggered when user press YES or NO (true or false would be passed to callback respectively) */ - confirm:function(text, title, callback) { + confirm:function(text, title, callback, modal) { var content = '<p><span class="ui-icon ui-icon-notice"></span>'+text+'</p>'; - OCdialogs.message(content, title, OCdialogs.ALERT_DIALOG, OCdialogs.YES_NO_BUTTONS, callback); + OCdialogs.message(content, title, OCdialogs.ALERT_DIALOG, OCdialogs.YES_NO_BUTTONS, callback, modal); }, /** * prompt for user input @@ -60,9 +59,9 @@ OCdialogs = { * @param title dialog title * @param callback which will be triggered when user press OK (input text will be passed to callback) */ - prompt:function(text, title, default_value, callback) { + prompt:function(text, title, default_value, callback, modal) { var content = '<p><span class="ui-icon ui-icon-pencil"></span>'+text+':<br/><input type="text" id="oc-dialog-prompt-input" value="'+default_value+'" style="width:90%"></p>'; - OCdialogs.message(content, title, OCdialogs.PROMPT_DIALOG, OCdialogs.OK_CANCEL_BUTTONS, callback); + OCdialogs.message(content, title, OCdialogs.PROMPT_DIALOG, OCdialogs.OK_CANCEL_BUTTONS, callback, modal); }, /** * prompt user for input with custom form @@ -71,7 +70,7 @@ OCdialogs = { * @param title dialog title * @param callback which will be triggered when user press OK (user answers will be passed to callback in following format: [{name:'return name', value: 'user value'},...]) */ - form:function(fields, title, callback) { + form:function(fields, title, callback, modal) { var content = '<table>'; for (var a in fields) { content += '<tr><td>'+fields[a].text+'</td><td>'; @@ -84,16 +83,63 @@ OCdialogs = { } else content += '>'; } else if (type == 'text' || type == 'password' && fields[a].value) content += ' value="'+fields[a].value+'">'; + } else if (type == 'select') { + content += '<select name="'+fields[a].name+'"'; + if (fields[a].value != undefined) + content += ' value="'+fields[a].value+'"'; + content += '>'; + for (var o in fields[a].options) + content += '<option value="'+fields[a].options[o].value+'">'+fields[a].options[o].text+'</option>'; + content += '</select>'; } - content += "</td></tr>" + content += '</td></tr>'; } - content += "</table>"; - OCdialogs.message(content, title, OCdialogs.FORM_DIALOG, OCdialogs.OK_CANCEL_BUTTONS, callback); + content += '</table>'; + OCdialogs.message(content, title, OCdialogs.FORM_DIALOG, OCdialogs.OK_CANCEL_BUTTONS, callback, modal); }, - message:function(content, title, dialog_type, buttons, callback) { + filepicker:function(title, callback, multiselect, mimetype_filter, modal) { + var c_name = 'oc-dialog-'+OCdialogs.dialogs_counter+'-content'; + var c_id = '#'+c_name; + var d = '<div id="'+c_name+'" title="'+title+'"><select id="dirtree"><option value="0">'+OC.currentUser+'</option></select><div id="filelist"></div><div class="filepicker_loader"><img src="'+OC.filePath('gallery','img','loading.gif')+'"></div></div>'; + if (!modal) modal = false; + if (!multiselect) multiselect = false; + $('body').append(d); + $(c_id + ' #dirtree').focus(function() { var t = $(this); t.data('oldval', t.val())}) + .change({dcid: c_id}, OC.dialogs.handleTreeListSelect); + $(c_id).ready(function(){ + $.getJSON(OC.webroot+'/files/ajax/rawlist.php', {mimetype: mimetype_filter} ,function(r){OC.dialogs.fillFilePicker(r, c_id, callback)}); + }).data('multiselect', multiselect).data('mimetype',mimetype_filter); + // build buttons + var b = [ + {text: t('dialogs', 'Choose'), click: function(){ + if (callback != undefined) { + var p; + if ($(c_id).data('multiselect') == true) { + p = []; + $(c_id+' .filepicker_element_selected #filename').each(function(i, elem) { + p.push(($(c_id).data('path')?$(c_id).data('path'):'')+'/'+$(elem).text()); + }); + } else { + var p = $(c_id).data('path'); + if (p == undefined) p = ''; + p = p+'/'+$(c_id+' .filepicker_element_selected #filename').text() + } + callback(p); + $(c_id).dialog('close'); + } + } + }, + {text: t('dialogs', 'Cancel'), click: function(){$(c_id).dialog('close'); }} + ]; + $(c_id).dialog({width: 4*$(document).width()/9, height: 400, modal: modal, buttons: b}); + OCdialogs.dialogs_counter++; + }, + // guts, dont use, dont touch + message:function(content, title, dialog_type, buttons, callback, modal) { var c_name = 'oc-dialog-'+OCdialogs.dialogs_counter+'-content'; var c_id = '#'+c_name; var d = '<div id="'+c_name+'" title="'+title+'">'+content+'</div>'; + if (modal == undefined) modal = false; $('body').append(d); var b = []; switch (buttons) { @@ -107,7 +153,7 @@ OCdialogs = { var f; switch(dialog_type) { case OCdialogs.ALERT_DIALOG: - f = function(){$(c_id).dialog('close'); }; + f = function(){$(c_id).dialog('close'); callback();}; break; case OCdialogs.PROMPT_DIALOG: f = function(){OCdialogs.prompt_ok_handler(callback, c_id)}; @@ -120,7 +166,7 @@ OCdialogs = { break; } var possible_height = ($('tr', d).size()+1)*30; - $(c_id).dialog({width: 4*$(document).width()/9, height: possible_height + 120, modal: false, buttons: b}); + $(c_id).dialog({width: 4*$(document).width()/9, height: possible_height + 120, modal: modal, buttons: b}); OCdialogs.dialogs_counter++; }, // dialogs buttons types @@ -144,7 +190,7 @@ OCdialogs = { if (callback != undefined) { var r = []; var c = 0; - $(c_id + ' input').each(function(i, elem) { + $(c_id + ' input, '+c_id+' select').each(function(i, elem) { r[c] = {name: $(elem).attr('name'), value: OCdialogs.determineValue(elem)}; c++; }); @@ -153,5 +199,47 @@ OCdialogs = { } else { $(c_id).dialog('close'); } + }, + fillFilePicker:function(r, dialog_content_id) { + var entry_template = '<div onclick="javascript:OC.dialogs.handlePickerClick(this, \'*ENTRYNAME*\',\''+dialog_content_id+'\')" data="*ENTRYTYPE*"><img src="*MIMETYPEICON*" style="margin-right:1em;"><span id="filename">*NAME*</span><div style="float:right;margin-right:1em;">*LASTMODDATE*</div></div>'; + var names = ''; + for (var a in r.data) { + names += entry_template.replace('*LASTMODDATE*', OC.mtime2date(r.data[a].mtime)).replace('*NAME*', r.data[a].name).replace('*MIMETYPEICON*', r.data[a].mimetype_icon).replace('*ENTRYNAME*', r.data[a].name).replace('*ENTRYTYPE*', r.data[a].type); + } + $(dialog_content_id + ' #filelist').html(names); + $(dialog_content_id + ' .filepicker_loader').css('visibility', 'hidden'); + }, + handleTreeListSelect:function(event) { + var newval = parseInt($(this).val()); + var oldval = parseInt($(this).data('oldval')); + while (newval != oldval && oldval > 0) { + $('option:last', this).remove(); + $('option:last', this).attr('selected','selected'); + oldval--; + } + var skip_first = true; + var path = ''; + $(this).children().each(function(i, element) { if (skip_first) {skip_first = false; return; }path += '/'+$(element).text(); }); + $(event.data.dcid).data('path', path); + $(event.data.dcid + ' .filepicker_loader').css('visibility', 'visible'); + $.getJSON(OC.webroot+'/files/ajax/rawlist.php', {dir: path, mimetype: $(event.data.dcid).data('mimetype')}, function(r){OC.dialogs.fillFilePicker(r, event.data.dcid)}); + }, + // this function is in early development state, please dont use it unlsess you know what you are doing + handlePickerClick:function(element, name, dcid) { + var p = $(dcid).data('path'); + if (p == undefined) p = ''; + p = p+'/'+name; + if ($(element).attr('data') == 'file'){ + if ($(dcid).data('multiselect') != true) + $(dcid+' .filepicker_element_selected').removeClass('filepicker_element_selected'); + $(element).toggleClass('filepicker_element_selected'); + return; + } + $(dcid).data('path', p); + $(dcid + ' #dirtree option:last').removeAttr('selected'); + var newval = parseInt($(dcid + ' #dirtree option:last').val())+1; + $(dcid + ' #dirtree').append('<option selected="selected" value="'+newval+'">'+name+'</option>'); + $(dcid + ' .filepicker_loader').css('visibility', 'visible'); + $.getJSON(OC.webroot+'/files/ajax/rawlist.php', {dir: p, mimetype: $(dcid).data('mimetype')}, function(r){OC.dialogs.fillFilePicker(r, dcid)}); } }; diff --git a/core/js/oc-vcategories.js b/core/js/oc-vcategories.js index a6dcccf88e0..e3b1abba08d 100644 --- a/core/js/oc-vcategories.js +++ b/core/js/oc-vcategories.js @@ -19,7 +19,7 @@ OCCategories={ height: 350, minHeight:200, width: 250, minWidth: 200, buttons: { 'Delete':function() { - OCCategories.delete(); + OCCategories.doDelete(); }, 'Rescan':function() { OCCategories.rescan(); @@ -53,7 +53,7 @@ OCCategories={ } }); }, - delete:function(){ + doDelete:function(){ var categories = $('#categorylist').find('input[type="checkbox"]').serialize(); categories += '&app=' + OCCategories.app; console.log('OCCategories.delete: ' + categories); diff --git a/core/lostpassword/index.php b/core/lostpassword/index.php index da0428e3ced..a9b7d10804f 100644 --- a/core/lostpassword/index.php +++ b/core/lostpassword/index.php @@ -12,7 +12,7 @@ require_once('../../lib/base.php'); // Someone lost their password: if (isset($_POST['user'])) { if (OC_User::userExists($_POST['user'])) { - $token = sha1($_POST['user']+uniqId()); + $token = sha1($_POST['user'].md5(uniqid(rand(), true))); OC_Preferences::setValue($_POST['user'], 'owncloud', 'lostpassword', $token); $email = OC_Preferences::getValue($_POST['user'], 'settings', 'email', ''); if (!empty($email)) { diff --git a/core/templates/login.php b/core/templates/login.php index 82222c82129..4ba92221a7d 100644 --- a/core/templates/login.php +++ b/core/templates/login.php @@ -7,7 +7,7 @@ <?php endif; ?> <p class="infield"> <label for="user" class="infield"><?php echo $l->t( 'Username' ); ?></label> - <input type="text" name="user" id="user" value="<?php echo !empty($_POST['user'])?$_POST['user'].'"':'" autofocus'; ?> autocomplete="off" required /> + <input type="text" name="user" id="user" value="<?php echo !empty($_POST['user'])?htmlentities($_POST['user']).'"':'" autofocus'; ?> autocomplete="off" required /> </p> <p class="infield"> <label for="password" class="infield"><?php echo $l->t( 'Password' ); ?></label> diff --git a/db_structure.xml b/db_structure.xml index 82d2a731d4c..2df218d359c 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -67,8 +67,7 @@ <field> <name>path_hash</name> <type>text</type> - <default> - </default> + <default></default> <notnull>true</notnull> <length>32</length> </field> diff --git a/files/ajax/rawlist.php b/files/ajax/rawlist.php new file mode 100644 index 00000000000..e02c5b62733 --- /dev/null +++ b/files/ajax/rawlist.php @@ -0,0 +1,26 @@ +<?php + +// only need filesystem apps +$RUNTIME_APPTYPES=array('filesystem'); + +// Init owncloud +require_once('../../lib/base.php'); +require_once('../../lib/template.php'); + +OC_JSON::checkLoggedIn(); + +// Load the files +$dir = isset( $_GET['dir'] ) ? $_GET['dir'] : ''; +$mimetype = isset($_GET['mimetype']) ? $_GET['mimetype'] : ''; + +// make filelist +$files = array(); +foreach( OC_Files::getdirectorycontent( $dir, $mimetype ) as $i ){ + $i["date"] = OC_Util::formatDate($i["mtime"] ); + $i['mimetype_icon'] = $i['type'] == 'dir' ? mimetype_icon('dir'): mimetype_icon($i['mimetype']); + $files[] = $i; +} + +OC_JSON::success(array('data' => $files)); + +?> diff --git a/files/ajax/scan.php b/files/ajax/scan.php index 565275911b4..db09b7d5c64 100644 --- a/files/ajax/scan.php +++ b/files/ajax/scan.php @@ -17,6 +17,7 @@ if($force or !OC_FileCache::inCache('')){ if(!$checkOnly){ OC_DB::beginTransaction(); OC_FileCache::scan('',$eventSource); + OC_FileCache::clean(); OC_DB::commit(); $eventSource->send('success',true); }else{ diff --git a/files/js/files.js b/files/js/files.js index df9f45a7af4..1c0a40c2368 100644 --- a/files/js/files.js +++ b/files/js/files.js @@ -417,7 +417,7 @@ var folderDropOptions={ var dir=$('#dir').val(); $.ajax({ url: 'ajax/move.php', - data: "dir="+dir+"&file="+file+'&target='+dir+'/'+target, + data: "dir="+encodeURIComponent(dir)+"&file="+encodeURIComponent(file)+'&target='+encodeURIComponent(dir)+'/'+encodeURIComponent(target), complete: function(data){boolOperationFinished(data, function(){ var el = $('#fileList tr').filterAttr('data-file',file).find('td.filename'); el.draggable('destroy'); @@ -443,7 +443,7 @@ var crumbDropOptions={ } $.ajax({ url: 'ajax/move.php', - data: "dir="+dir+"&file="+file+'&target='+target, + data: "dir="+encodeURIComponent(dir)+"&file="+encodeURIComponent(file)+'&target='+encodeURIComponent(target), complete: function(data){boolOperationFinished(data, function(){ FileList.remove(file); });} diff --git a/files/templates/index.php b/files/templates/index.php index 418a170fecb..f591d066d8c 100644 --- a/files/templates/index.php +++ b/files/templates/index.php @@ -63,12 +63,12 @@ </div> <div id="scanning-message"> <h3> - <?php echo $l->t('Files are being scanned, please wait.');?> <span id='scan-count'></spann> + <?php echo $l->t('Files are being scanned, please wait.');?> <span id='scan-count'></span> </h3> <p> - <?php echo $l->t('Current scanning');?> <span id='scan-current'></spann> + <?php echo $l->t('Current scanning');?> <span id='scan-current'></span> </p> </div> <!-- config hints for javascript --> -<input type="hidden" name="allowZipDownload" id="allowZipDownload" value="<?php echo $_['allowZipDownload']; ?>" />
\ No newline at end of file +<input type="hidden" name="allowZipDownload" id="allowZipDownload" value="<?php echo $_['allowZipDownload']; ?>" /> diff --git a/files/webdav.php b/files/webdav.php index 1120973787c..25e33024470 100644 --- a/files/webdav.php +++ b/files/webdav.php @@ -27,7 +27,7 @@ $RUNTIME_NOSETUPFS = true; // only need filesystem apps -$RUNTIME_APPTYPES=array('filesystem'); +$RUNTIME_APPTYPES=array('filesystem','authentication'); require_once('../lib/base.php'); diff --git a/lib/app.php b/lib/app.php index 6c882963a02..1c81fbd4242 100755 --- a/lib/app.php +++ b/lib/app.php @@ -55,7 +55,7 @@ class OC_App{ // Our very own core apps are hardcoded foreach( array('files', 'settings') as $app ){ - if(is_null($types) or self::isType($app,$types)){ + if(is_null($types)){ require( $app.'/appinfo/app.php' ); } } @@ -103,9 +103,9 @@ class OC_App{ */ public static function getEnabledApps(){ $apps=array(); - $query = OC_DB::prepare( 'SELECT appid FROM *PREFIX*appconfig WHERE configkey = "enabled" AND configvalue="yes"' ); - $query->execute(); - while($row=$query->fetchRow()){ + $query = OC_DB::prepare( 'SELECT appid FROM *PREFIX*appconfig WHERE configkey = \'enabled\' AND configvalue=\'yes\'' ); + $result=$query->execute(); + while($row=$result->fetchRow()){ $apps[]=$row['appid']; } return $apps; @@ -319,7 +319,7 @@ class OC_App{ $file=OC::$APPSROOT.'/apps/'.$appid.'/appinfo/info.xml'; } $data=array(); - $content=file_get_contents($file); + $content=@file_get_contents($file); if(!$content){ return; } @@ -452,7 +452,7 @@ class OC_App{ */ public static function getAppVersions(){ $versions=array(); - $query = OC_DB::prepare( 'SELECT appid, configvalue FROM *PREFIX*appconfig WHERE configkey = "installed_version"' ); + $query = OC_DB::prepare( 'SELECT appid, configvalue FROM *PREFIX*appconfig WHERE configkey = \'installed_version\'' ); $result = $query->execute(); while($row = $result->fetchRow()){ $versions[$row['appid']]=$row['configvalue']; diff --git a/lib/base.php b/lib/base.php index b031572f177..83dd0c98f45 100644 --- a/lib/base.php +++ b/lib/base.php @@ -229,6 +229,39 @@ class OC{ } } + public static function initTemplateEngine() { + // if the formfactor is not yet autodetected do the autodetection now. For possible forfactors check the detectFormfactor documentation + if(!isset($_SESSION['formfactor'])){ + $_SESSION['formfactor']=OC::detectFormfactor(); + } + // allow manual override via GET parameter + if(isset($_GET['formfactor'])){ + $_SESSION['formfactor']=$_GET['formfactor']; + } + + // Add the stuff we need always + OC_Util::addScript( "jquery-1.6.4.min" ); + OC_Util::addScript( "jquery-ui-1.8.16.custom.min" ); + OC_Util::addScript( "jquery-showpassword" ); + OC_Util::addScript( "jquery.infieldlabel.min" ); + OC_Util::addScript( "jquery-tipsy" ); + OC_Util::addScript( "oc-dialogs" ); + OC_Util::addScript( "js" ); + OC_Util::addScript( "eventsource" ); + OC_Util::addScript( "config" ); + //OC_Util::addScript( "multiselect" ); + OC_Util::addScript('search','result'); + OC_Util::addStyle( "styles" ); + OC_Util::addStyle( "multiselect" ); + OC_Util::addStyle( "jquery-ui-1.8.16.custom" ); + OC_Util::addStyle( "jquery-tipsy" ); + } + + public static function initSession() { + ini_set('session.cookie_httponly','1;'); + session_start(); + } + public static function init(){ // register autoloader spl_autoload_register(array('OC','autoload')); @@ -244,6 +277,24 @@ class OC{ date_default_timezone_set('Europe/Berlin'); ini_set('arg_separator.output','&'); + //try to configure php to enable big file uploads. + //this doesn´t work always depending on the webserver and php configuration. + //Let´s try to overwrite some defaults anyways + + //try to set the maximum execution time to 60min + @set_time_limit(3600); + @ini_set('max_execution_time',3600); + @ini_set('max_input_time',3600); + + //try to set the maximum filesize to 10G + @ini_set('upload_max_filesize','10G'); + @ini_set('post_max_size','10G'); + @ini_set('file_uploads','50'); + + //try to set the session lifetime to 60min + @ini_set('gc_maxlifetime','3600'); + + //set http auth headers for apache+php-cgi work around if (isset($_SERVER['HTTP_AUTHORIZATION']) && preg_match('/Basic\s+(.*)$/i', $_SERVER['HTTP_AUTHORIZATION'], $matches)) { @@ -270,37 +321,10 @@ class OC{ self::checkInstalled(); self::checkSSL(); - self::checkUpgrade(); - ini_set('session.cookie_httponly','1;'); - session_start(); - - // if the formfactor is not yet autodetected do the autodetection now. For possible forfactors check the detectFormfactor documentation - if(!isset($_SESSION['formfactor'])){ - $_SESSION['formfactor']=OC::detectFormfactor(); - } - // allow manual override via GET parameter - if(isset($_GET['formfactor'])){ - $_SESSION['formfactor']=$_GET['formfactor']; - } - - - // Add the stuff we need always - OC_Util::addScript( "jquery-1.6.4.min" ); - OC_Util::addScript( "jquery-ui-1.8.16.custom.min" ); - OC_Util::addScript( "jquery-showpassword" ); - OC_Util::addScript( "jquery.infieldlabel.min" ); - OC_Util::addScript( "jquery-tipsy" ); - OC_Util::addScript( "oc-dialogs" ); - OC_Util::addScript( "js" ); - OC_Util::addScript( "eventsource" ); - OC_Util::addScript( "config" ); - //OC_Util::addScript( "multiselect" ); - OC_Util::addScript('search','result'); - OC_Util::addStyle( "styles" ); - OC_Util::addStyle( "multiselect" ); - OC_Util::addStyle( "jquery-ui-1.8.16.custom" ); - OC_Util::addStyle( "jquery-tipsy" ); + self::initSession(); + self::initTemplateEngine(); + self::checkUpgrade(); $errors=OC_Util::checkServer(); if(count($errors)>0) { @@ -341,6 +365,9 @@ class OC{ OC_App::loadApps(); } } + + // Check for blacklisted files + OC_Hook::connect('OC_Filesystem','write','OC_Filesystem','isBlacklisted'); //make sure temporary files are cleaned up register_shutdown_function(array('OC_Helper','cleanTmp')); diff --git a/lib/db.php b/lib/db.php index a0fb6c385d8..9c46a40addb 100644 --- a/lib/db.php +++ b/lib/db.php @@ -483,6 +483,30 @@ class OC_DB { } /** + * @breif replaces the owncloud tables with a new set + * @param $file string path to the MDB2 xml db export file + */ + public static function replaceDB( $file ){ + + $apps = OC_App::getAllApps(); + self::beginTransaction(); + // Delete the old tables + self::removeDBStructure( OC::$SERVERROOT . '/db_structure.xml' ); + + foreach($apps as $app){ + $path = OC::$SERVERROOT.'/apps/'.$app.'/appinfo/database.xml'; + if(file_exists($path)){ + self::removeDBStructure( $path ); + } + } + + // Create new tables + self::createDBFromStructure( $file ); + self::commit(); + + } + + /** * Start a transaction */ public static function beginTransaction(){ @@ -586,3 +610,4 @@ class PDOStatementWrapper{ return $this->statement->fetchColumn($colnum); } } + diff --git a/lib/eventsource.php b/lib/eventsource.php index 523f72403c3..cf10660b94c 100644 --- a/lib/eventsource.php +++ b/lib/eventsource.php @@ -32,6 +32,7 @@ class OC_EventSource{ private $fallBackId=0; public function __construct(){ + @ob_end_clean(); header('Cache-Control: no-cache'); $this->fallback=isset($_GET['fallback']) and $_GET['fallback']=='true'; if($this->fallback){ @@ -58,7 +59,7 @@ class OC_EventSource{ $type=null; } if($this->fallback){ - $response='<script type="text/javascript">window.parent.OC.EventSource.fallBackCallBack('.$this->fallBackId.',"'.$type.'","'.json_encode($data).'")</script>'.PHP_EOL; + $response='<script type="text/javascript">window.parent.OC.EventSource.fallBackCallBack('.$this->fallBackId.',"'.$type.'",'.json_encode($data).')</script>'.PHP_EOL; echo $response; }else{ if($type){ diff --git a/lib/filecache.php b/lib/filecache.php index a8c48e3f144..cdd91dcbfa4 100644 --- a/lib/filecache.php +++ b/lib/filecache.php @@ -240,7 +240,7 @@ class OC_FileCache{ * - encrypted * - versioned */ - public static function getFolderContent($path,$root=''){ + public static function getFolderContent($path,$root='',$mimetype_filter=''){ if(self::isUpdated($path,$root)){ self::updateFolder($path,$root); } @@ -252,8 +252,8 @@ class OC_FileCache{ } $path=$root.$path; $parent=self::getFileId($path); - $query=OC_DB::prepare('SELECT name,ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE parent=?'); - $result=$query->execute(array($parent))->fetchAll(); + $query=OC_DB::prepare('SELECT name,ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE parent=? AND (mimetype LIKE ? OR mimetype = ?)'); + $result=$query->execute(array($parent, $mimetype_filter.'%', 'httpd/unix-directory'))->fetchAll(); if(is_array($result)){ return $result; }else{ @@ -469,6 +469,10 @@ class OC_FileCache{ * @param string root (optionak) */ public static function scan($path,$eventSource=false,&$count=0,$root=''){ + if($eventSource){ + $eventSource->send('scanning',array('file'=>$path,'count'=>$count)); + } + $lastSend=$count; if(!$root){ $view=OC_Filesystem::getView(); }else{ @@ -482,13 +486,14 @@ class OC_FileCache{ if($filename != '.' and $filename != '..'){ $file=$path.'/'.$filename; if($view->is_dir($file.'/')){ - if($eventSource){ - $eventSource->send('scanning',array('file'=>$file,'count'=>$count)); - } self::scan($file,$eventSource,$count,$root); }else{ $totalSize+=self::scanFile($file,$root); $count++; + if($count>$lastSend+25 and $eventSource){ + $lastSend=$count; + $eventSource->send('scanning',array('file'=>$path,'count'=>$count)); + } } } } @@ -637,6 +642,14 @@ class OC_FileCache{ self::fileSystemWatcherWrite(array('path'=>$path),$root); } } + + /** + * clean old pre-path_hash entries + */ + public static function clean(){ + $query=OC_DB::prepare('DELETE FROM *PREFIX*fscache WHERE LENGTH(path_hash)<30'); + $query->execute(); + } } //watch for changes and try to keep the cache up to date diff --git a/lib/files.php b/lib/files.php index e7bfbbc19bb..a68c29ad989 100644 --- a/lib/files.php +++ b/lib/files.php @@ -32,11 +32,11 @@ class OC_Files { * get the content of a directory * @param dir $directory */ - public static function getDirectoryContent($directory){ + public static function getDirectoryContent($directory, $mimetype_filter = ''){ if(strpos($directory,OC::$CONFIG_DATADIRECTORY)===0){ $directory=substr($directory,strlen(OC::$CONFIG_DATADIRECTORY)); } - $files=OC_FileCache::getFolderContent($directory); + $files=OC_FileCache::getFolderContent($directory, '', $mimetype_filter); foreach($files as &$file){ $file['directory']=$directory; $file['type']=($file['mimetype']=='httpd/unix-directory')?'dir':'file'; diff --git a/lib/filestorage/local.php b/lib/filestorage/local.php index 688501aee90..bd757f52ce7 100644 --- a/lib/filestorage/local.php +++ b/lib/filestorage/local.php @@ -86,6 +86,10 @@ class OC_Filestorage_Local extends OC_Filestorage{ return $this->delTree($path); } public function rename($path1,$path2){ + if (!$this->is_writable($path1)) { + OC_Log::write('core','unable to rename, file is not writable : '.$path1,OC_Log::ERROR); + return false; + } if(! $this->file_exists($path1)){ OC_Log::write('core','unable to rename, file does not exists : '.$path1,OC_Log::ERROR); return false; diff --git a/lib/filesystem.php b/lib/filesystem.php index 12905d189f9..b6909f5acdf 100644 --- a/lib/filesystem.php +++ b/lib/filesystem.php @@ -298,6 +298,19 @@ class OC_Filesystem{ } return true; } + + /** + * checks if a file is blacklsited for storage in the filesystem + * @param array $data from hook + */ + static public function isBlacklisted($data){ + $blacklist = array('.htaccess'); + $filename = strtolower(basename($data['path'])); + if(in_array($filename,$blacklist)){ + $data['run'] = false; + } + } + /** * following functions are equivilent to their php buildin equivilents for arguments/return values. */ diff --git a/lib/filesystemview.php b/lib/filesystemview.php index 39e47975b28..9d530c7ad63 100644 --- a/lib/filesystemview.php +++ b/lib/filesystemview.php @@ -137,13 +137,16 @@ class OC_FilesystemView { } public function readfile($path){ $handle=$this->fopen($path,'r'); - $chunkSize = 1024*1024;// 1 MB chunks - while (!feof($handle)) { - echo fread($handle, $chunkSize); - @ob_flush(); - flush(); + if ($handle) { + $chunkSize = 1024*1024;// 1 MB chunks + while (!feof($handle)) { + echo fread($handle, $chunkSize); + @ob_flush(); + flush(); + } + return $this->filesize($path); } - return $this->filesize($path); + return false; } public function is_readable($path){ return $this->basicOperation('is_readable',$path); @@ -189,7 +192,7 @@ class OC_FilesystemView { return $this->basicOperation('unlink',$path,array('delete')); } public function rename($path1,$path2){ - if(OC_FileProxy::runPreProxies('rename',$path1,$path2) and $this->is_writable($path1) and OC_Filesystem::isValidPath($path2)){ + if(OC_FileProxy::runPreProxies('rename',$path1,$path2) and OC_Filesystem::isValidPath($path2)){ $run=true; OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_rename, array( OC_Filesystem::signal_param_oldpath => $path1 , OC_Filesystem::signal_param_newpath=>$path2, OC_Filesystem::signal_param_run => &$run)); if($run){ diff --git a/lib/log.php b/lib/log.php index 4e450a027f5..8bb2839be66 100644 --- a/lib/log.php +++ b/lib/log.php @@ -1,78 +1,39 @@ <?php /** - * ownCloud - * - * @author Robin Appelman - * @copyright 2012 Robin Appelman icewind1991@gmail.com - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE - * License as published by the Free Software Foundation; either - * version 3 of the License, or any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU AFFERO GENERAL PUBLIC LICENSE for more details. - * - * You should have received a copy of the GNU Affero General Public - * License along with this library. If not, see <http://www.gnu.org/licenses/>. - * + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. */ /** - *logging utilities + * logging utilities * - * Log is saved at data/owncloud.log (on default) + * Log is saved by default at data/owncloud.log using OC_Log_Owncloud. + * Selecting other backend is done with a config option 'log_type'. */ -class OC_Log{ +class OC_Log { const DEBUG=0; const INFO=1; const WARN=2; const ERROR=3; const FATAL=4; + static protected $class = null; + /** * write a message in the log * @param string $app * @param string $message * @param int level */ - public static function write($app,$message,$level){ - $minLevel=OC_Config::getValue( "loglevel", 2 ); - if($level>=$minLevel){ - $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' ); - $logFile=OC_Config::getValue( "logfile", $datadir.'/owncloud.log' ); - $entry=array('app'=>$app,'message'=>$message,'level'=>$level,'time'=>time()); - $fh=fopen($logFile,'a'); - fwrite($fh,json_encode($entry)."\n"); - fclose($fh); - } - } - - /** - * get entries from the log in reverse chronological order - * @param int limit - * @param int offset - * @return array - */ - public static function getEntries($limit=50,$offset=0){ - $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' ); - $logFile=OC_Config::getValue( "logfile", $datadir.'/owncloud.log' ); - $entries=array(); - if(!file_exists($logFile)){ - return array(); - } - $contents=file($logFile); - if(!$contents){//error while reading log - return array(); - } - $end=max(count($contents)-$offset-1,0); - $start=max($end-$limit,0); - for($i=$end;$i>$start;$i--){ - $entries[]=json_decode($contents[$i]); + public static function write($app, $message, $level) { + if (!self::$class) { + self::$class = 'OC_Log_'.ucfirst(OC_Config::getValue('log_type', 'owncloud')); + call_user_func(array(self::$class, 'init')); } - return $entries; + $log_class=self::$class; + $log_class::write($app, $message, $level); } } diff --git a/lib/log/owncloud.php b/lib/log/owncloud.php new file mode 100644 index 00000000000..0ed30510134 --- /dev/null +++ b/lib/log/owncloud.php @@ -0,0 +1,79 @@ +<?php +/** + * ownCloud + * + * @author Robin Appelman + * @copyright 2012 Robin Appelman icewind1991@gmail.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/** + * logging utilities + * + * Log is saved at data/owncloud.log (on default) + */ + +class OC_Log_Owncloud { + static protected $logFile; + + /** + * Init class data + */ + public static function init() { + $datadir=OC_Config::getValue( "datadirectory", OC::$SERVERROOT.'/data' ); + self::$logFile=OC_Config::getValue( "logfile", $datadir.'/owncloud.log' ); + } + + /** + * write a message in the log + * @param string $app + * @param string $message + * @param int level + */ + public static function write($app, $message, $level) { + $minLevel=OC_Config::getValue( "loglevel", 2 ); + if($level>=$minLevel){ + $entry=array('app'=>$app, 'message'=>$message, 'level'=>$level,'time'=>time()); + $fh=fopen(self::$logFile, 'a'); + fwrite($fh, json_encode($entry)."\n"); + fclose($fh); + } + } + + /** + * get entries from the log in reverse chronological order + * @param int limit + * @param int offset + * @return array + */ + public static function getEntries($limit=50, $offset=0){ + self::init(); + $entries=array(); + if(!file_exists(self::$logFile)) { + return array(); + } + $contents=file(self::$logFile); + if(!$contents) {//error while reading log + return array(); + } + $end=max(count($contents)-$offset-1, 0); + $start=max($end-$limit,0); + for($i=$end;$i>$start;$i--) { + $entries[]=json_decode($contents[$i]); + } + return $entries; + } +} diff --git a/lib/log/syslog.php b/lib/log/syslog.php new file mode 100644 index 00000000000..d1fb28d8b0a --- /dev/null +++ b/lib/log/syslog.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_Log_Syslog { + static protected $levels = array( + OC_Log::DEBUG => LOG_DEBUG, + OC_Log::INFO => LOG_INFO, + OC_Log::WARN => LOG_WARNING, + OC_Log::ERROR => LOG_ERR, + OC_Log::FATAL => LOG_CRIT, + ); + + /** + * Init class data + */ + public static function init() { + openlog('ownCloud', LOG_PID | LOG_CONS, LOG_USER); + // Close at shutdown + register_shutdown_function('closelog'); + } + + /** + * write a message in the log + * @param string $app + * @param string $message + * @param int level + */ + public static function write($app, $message, $level) { + $syslog_level = self::$levels[$level]; + syslog($syslog_level, '{'.$app.'} '.$message); + } +} diff --git a/lib/migrate.php b/lib/migrate.php new file mode 100644 index 00000000000..dff3abe9e93 --- /dev/null +++ b/lib/migrate.php @@ -0,0 +1,714 @@ +<?php +/** + * ownCloud + * + * @author Tom Needham + * @copyright 2012 Tom Needham tom@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +/** + * provides an interface to migrate users and whole ownclouds + */ +class OC_Migrate{ + + + // Array of OC_Migration_Provider objects + static private $providers=array(); + // User id of the user to import/export + static private $uid=false; + // Holds the ZipArchive object + static private $zip=false; + // Stores the type of export + static private $exporttype=false; + // Array of temp files to be deleted after zip creation + static private $tmpfiles=array(); + // Holds the db object + static private $MDB2=false; + // Schema db object + static private $schema=false; + // Path to the sqlite db + static private $dbpath=false; + // Holds the path to the zip file + static private $zippath=false; + // Holds the OC_Migration_Content object + static private $content=false; + + /** + * register a new migration provider + * @param OC_Migrate_Provider $provider + */ + public static function registerProvider($provider){ + self::$providers[]=$provider; + } + + /** + * @breif finds and loads the providers + */ + static private function findProviders(){ + // Find the providers + $apps = OC_App::getAllApps(); + + foreach($apps as $app){ + $path = OC::$SERVERROOT . '/apps/' . $app . '/appinfo/migrate.php'; + if( file_exists( $path ) ){ + include( $path ); + } + } + } + + /** + * @breif exports a user, or owncloud instance + * @param optional $uid string user id of user to export if export type is user, defaults to current + * @param ootional $type string type of export, defualts to user + * @param otional $path string path to zip output folder + * @return false on error, path to zip on success + */ + public static function export( $uid=null, $type='user', $path=null ){ + $datadir = OC_Config::getValue( 'datadirectory' ); + // Validate export type + $types = array( 'user', 'instance', 'system', 'userfiles' ); + if( !in_array( $type, $types ) ){ + OC_Log::write( 'migration', 'Invalid export type', OC_Log::ERROR ); + return json_encode( array( array( 'success' => false ) ) ); + } + self::$exporttype = $type; + // Userid? + if( self::$exporttype == 'user' ){ + // Check user exists + if( !is_null($uid) ){ + if( !OC_User_Database::userExists( $uid ) ){ + OC_Log::write('migration', 'User: '.$uid.' is not in the database and so cannot be exported.', OC_Log::ERROR); + return json_encode( array( 'success' => false ) ); + } + self::$uid = $uid; + } else { + self::$uid = OC_User::getUser(); + } + } + // Calculate zipname + if( self::$exporttype == 'user' ){ + $zipname = 'oc_export_' . self::$uid . '_' . date("y-m-d_H-i-s") . '.zip'; + } else { + $zipname = 'oc_export_' . self::$exporttype . '_' . date("y-m-d_H-i-s") . '.zip'; + } + // Calculate path + if( self::$exporttype == 'user' ){ + self::$zippath = $datadir . '/' . self::$uid . '/' . $zipname; + } else { + if( !is_null( $path ) ){ + // Validate custom path + if( !file_exists( $path ) || !is_writeable( $path ) ){ + OC_Log::write( 'migration', 'Path supplied is invalid.', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + self::$zippath = $path . $zipname; + } else { + // Default path + self::$zippath = get_temp_dir() . '/' . $zipname; + } + } + // Create the zip object + if( !self::createZip() ){ + return json_encode( array( 'success' => false ) ); + } + // Do the export + self::findProviders(); + $exportdata = array(); + switch( self::$exporttype ){ + case 'user': + // Connect to the db + self::$dbpath = $datadir . '/' . self::$uid . '/migration.db'; + if( !self::connectDB() ){ + return json_encode( array( 'success' => false ) ); + } + self::$content = new OC_Migration_Content( self::$zip, self::$MDB2 ); + // Export the app info + $exportdata = self::exportAppData(); + // Add the data dir to the zip + self::$content->addDir( $datadir . '/' . self::$uid, true, '/' ); + break; + case 'instance': + self::$content = new OC_Migration_Content( self::$zip ); + // Creates a zip that is compatable with the import function + $dbfile = tempnam( "/tmp", "owncloud_export_data_" ); + OC_DB::getDbStructure( $dbfile, 'MDB2_SCHEMA_DUMP_ALL'); + + // Now add in *dbname* and *dbprefix* + $dbexport = file_get_contents( $dbfile ); + $dbnamestring = "<database>\n\n <name>" . OC_Config::getValue( "dbname", "owncloud" ); + $dbtableprefixstring = "<table>\n\n <name>" . OC_Config::getValue( "dbtableprefix", "oc_" ); + $dbexport = str_replace( $dbnamestring, "<database>\n\n <name>*dbname*", $dbexport ); + $dbexport = str_replace( $dbtableprefixstring, "<table>\n\n <name>*dbprefix*", $dbexport ); + // Add the export to the zip + self::$content->addFromString( $dbexport, "dbexport.xml" ); + // Add user data + foreach(OC_User::getUsers() as $user){ + self::$content->addDir( $datadir . '/' . $user . '/', true, "/userdata/" ); + } + break; + case 'userfiles': + self::$content = new OC_Migration_Content( self::$zip ); + // Creates a zip with all of the users files + foreach(OC_User::getUsers() as $user){ + self::$content->addDir( $datadir . '/' . $user . '/', true, "/" ); + } + break; + case 'system': + self::$content = new OC_Migration_Content( self::$zip ); + // Creates a zip with the owncloud system files + self::$content->addDir( OC::$SERVERROOT . '/', false, '/'); + foreach (array(".git", "3rdparty", "apps", "core", "files", "l10n", "lib", "ocs", "search", "settings", "tests") as $dir) { + self::$content->addDir( OC::$SERVERROOT . '/' . $dir, true, "/"); + } + break; + } + if( !$info = self::getExportInfo( $exportdata ) ){ + return json_encode( array( 'success' => false ) ); + } + // Add the export info json to the export zip + self::$content->addFromString( $info, 'export_info.json' ); + if( !self::$content->finish() ){ + return json_encode( array( 'success' => false ) ); + } + return json_encode( array( 'success' => true, 'data' => self::$zippath ) ); + } + + /** + * @breif imports a user, or owncloud instance + * @param $path string path to zip + * @param optional $type type of import (user or instance) + * @param optional $uid userid of new user + */ + public static function import( $path, $type='user', $uid=null ){ + OC_Util::checkAdminUser(); + $datadir = OC_Config::getValue( 'datadirectory' ); + // Extract the zip + if( !$extractpath = self::extractZip( $path ) ){ + return json_encode( array( 'success' => false ) ); + } + // Get export_info.json + $scan = scandir( $extractpath ); + // Check for export_info.json + if( !in_array( 'export_info.json', $scan ) ){ + OC_Log::write( 'migration', 'Invalid import file, export_info.json note found', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + $json = json_decode( file_get_contents( $extractpath . 'export_info.json' ) ); + if( $json->exporttype != $type ){ + OC_Log::write( 'migration', 'Invalid import file', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + self::$exporttype = $type; + + // Have we got a user if type is user + if( self::$exporttype == 'user' ){ + if( !$uid ){ + self::$uid = $json->exporteduser; + } else { + self::$uid = $uid; + } + } + + // Handle export types + switch( self::$exporttype ){ + case 'user': + // Check user availability + if( OC_User::userExists( self::$uid ) ){ + OC_Log::write( 'migration', 'User already exists', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + $run = true; + OC_Hook::emit( "OC_User", "pre_createUser", array( "run" => &$run, "uid" => self::$uid, "password" => $json->hash )); + if( !$run ){ + // Something stopped the user creation + OC_Log::write( 'migration', 'User creation failed', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + // Create the user + if( !self::createUser( self::$uid, $json->hash ) ){ + return json_encode( array( 'success' => false ) ); + } + // Emit the post_createUser hook (password is already hashed, will cause problems + OC_Hook::emit( "OC_User", "post_createUser", array( "uid" => self::$uid, "password" => $json->hash )); + // Make the new users data dir + $path = $datadir . '/' . self::$uid; + if( !mkdir( $path, 0755, true ) ){ + OC_Log::write( 'migration', 'Failed to create users data dir: '.$path, OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + // Copy data + if( !self::copy_r( $extractpath . $json->exporteduser, $datadir . '/' . self::$uid ) ){ + return json_encode( array( 'success' => false ) ); + } + // Import user app data + if( !$appsimported = self::importAppData( $extractpath . $json->exporteduser . '/migration.db', $json, self::$uid ) ){ + return json_encode( array( 'success' => false ) ); + } + // All done! + if( !self::unlink_r( $extractpath ) ){ + OC_Log::write( 'migration', 'Failed to delete the extracted zip', OC_Log::ERROR ); + } + return json_encode( array( 'success' => true, 'data' => $appsimported ) ); + break; + case 'instance': + /* + * EXPERIMENTAL + // Check for new data dir and dbexport before doing anything + // TODO + + // Delete current data folder. + OC_Log::write( 'migration', "Deleting current data dir", OC_Log::INFO ); + if( !self::unlink_r( $datadir, false ) ){ + OC_Log::write( 'migration', 'Failed to delete the current data dir', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + + // Copy over data + if( !self::copy_r( $extractpath . 'userdata', $datadir ) ){ + OC_Log::write( 'migration', 'Failed to copy over data directory', OC_Log::ERROR ); + return json_encode( array( 'success' => false ) ); + } + + // Import the db + if( !OC_DB::replaceDB( $extractpath . 'dbexport.xml' ) ){ + return json_encode( array( 'success' => false ) ); + } + // Done + return json_encode( 'success' => true ); + */ + break; + } + + } + + /** + * @breif recursively deletes a directory + * @param $dir string path of dir to delete + * $param optional $deleteRootToo bool delete the root directory + * @return bool + */ + private static function unlink_r( $dir, $deleteRootToo=true ){ + if( !$dh = @opendir( $dir ) ){ + return false; + } + while (false !== ($obj = readdir($dh))){ + if($obj == '.' || $obj == '..') { + continue; + } + if (!@unlink($dir . '/' . $obj)){ + self::unlink_r($dir.'/'.$obj, true); + } + } + closedir($dh); + if ( $deleteRootToo ) { + @rmdir($dir); + } + return true; + } + + /** + * @breif copies recursively + * @param $path string path to source folder + * @param $dest string path to destination + * @return bool + */ + private static function copy_r( $path, $dest ){ + if( is_dir($path) ){ + @mkdir( $dest ); + $objects = scandir( $path ); + if( sizeof( $objects ) > 0 ){ + foreach( $objects as $file ){ + if( $file == "." || $file == ".." ) + continue; + // go on + if( is_dir( $path . '/' . $file ) ){ + self::copy_r( $path .'/' . $file, $dest . '/' . $file ); + } else { + copy( $path . '/' . $file, $dest . '/' . $file ); + } + } + } + return true; + } + elseif( is_file( $path ) ){ + return copy( $path, $dest ); + } else { + return false; + } + } + + /** + * @breif tries to extract the import zip + * @param $path string path to the zip + * @return string path to extract location (with a trailing slash) or false on failure + */ + static private function extractZip( $path ){ + self::$zip = new ZipArchive; + // Validate path + if( !file_exists( $path ) ){ + OC_Log::write( 'migration', 'Zip not found', OC_Log::ERROR ); + return false; + } + if ( self::$zip->open( $path ) != TRUE ) { + OC_Log::write( 'migration', "Failed to open zip file", OC_Log::ERROR ); + return false; + } + $to = get_temp_dir() . '/oc_import_' . self::$exporttype . '_' . date("y-m-d_H-i-s") . '/'; + if( !self::$zip->extractTo( $to ) ){ + return false; + } + self::$zip->close(); + return $to; + } + + /** + * @brief connects to a MDB2 database scheme + * @returns bool + */ + static private function connectScheme(){ + // We need a mdb2 database connection + self::$MDB2->loadModule( 'Manager' ); + self::$MDB2->loadModule( 'Reverse' ); + + // Connect if this did not happen before + if( !self::$schema ){ + require_once('MDB2/Schema.php'); + self::$schema=MDB2_Schema::factory( self::$MDB2 ); + } + + return true; + } + + /** + * @breif creates a migration.db in the users data dir with their app data in + * @return bool whether operation was successfull + */ + private static function exportAppData( ){ + + $success = true; + $return = array(); + + // Foreach provider + foreach( self::$providers as $provider ){ + $success = true; + // Does this app use the database? + if( file_exists( OC::$SERVERROOT.'/apps/'.$provider->getID().'/appinfo/database.xml' ) ){ + // Create some app tables + $tables = self::createAppTables( $provider->getID() ); + if( is_array( $tables ) ){ + // Save the table names + foreach($tables as $table){ + $return['apps'][$provider->getID()]['tables'][] = $table; + } + } else { + // It failed to create the tables + $success = false; + } + } + + // Run the export function? + if( $success ){ + // Set the provider properties + $provider->setData( self::$uid, self::$content ); + $return['apps'][$provider->getID()]['success'] = $provider->export(); + } else { + $return['apps'][$provider->getID()]['success'] = false; + $return['apps'][$provider->getID()]['message'] = 'failed to create the app tables'; + } + + // Now add some app info the the return array + $appinfo = OC_App::getAppInfo( $provider->getID() ); + $return['apps'][$provider->getID()]['version'] = $appinfo['version']; + + } + + return $return; + + } + + + /** + * @breif generates json containing export info, and merges any data supplied + * @param optional $array array of data to include in the returned json + * @return bool + */ + static private function getExportInfo( $array=array() ){ + $info = array( + 'ocversion' => OC_Util::getVersion(), + 'exporttime' => time(), + 'exportedby' => OC_User::getUser(), + 'exporttype' => self::$exporttype + ); + // Add hash if user export + if( self::$exporttype == 'user' ){ + $query = OC_DB::prepare( "SELECT password FROM *PREFIX*users WHERE uid LIKE ?" ); + $result = $query->execute( array( self::$uid ) ); + $row = $result->fetchRow(); + $hash = $row ? $row['password'] : false; + if( !$hash ){ + OC_Log::write( 'migration', 'Failed to get the users password hash', OC_log::ERROR); + return false; + } + $info['hash'] = $hash; + $info['exporteduser'] = self::$uid; + } + if( !is_array( $array ) ){ + OC_Log::write( 'migration', 'Supplied $array was not an array in getExportInfo()', OC_Log::ERROR ); + } + // Merge in other data + $info = array_merge( $info, (array)$array ); + // Create json + $json = json_encode( $info ); + return $json; + } + + /** + * @breif connects to migration.db, or creates if not found + * @param $db optional path to migration.db, defaults to user data dir + * @return bool whether the operation was successful + */ + static private function connectDB( $path=null ){ + // Has the dbpath been set? + self::$dbpath = !is_null( $path ) ? $path : self::$dbpath; + if( !self::$dbpath ){ + OC_Log::write( 'migration', 'connectDB() was called without dbpath being set', OC_Log::ERROR ); + return false; + } + // Already connected + if(!self::$MDB2){ + require_once('MDB2.php'); + + $datadir = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ); + + // DB type + if( class_exists( 'SQLite3' ) ){ + $dbtype = 'sqlite3'; + } else if( is_callable( 'sqlite_open' ) ){ + $dbtype = 'sqlite'; + } else { + OC_Log::write( 'migration', 'SQLite not found', OC_Log::ERROR ); + return false; + } + + // Prepare options array + $options = array( + 'portability' => MDB2_PORTABILITY_ALL & (!MDB2_PORTABILITY_FIX_CASE), + 'log_line_break' => '<br>', + 'idxname_format' => '%s', + 'debug' => true, + 'quote_identifier' => true + ); + $dsn = array( + 'phptype' => $dbtype, + 'database' => self::$dbpath, + 'mode' => '0644' + ); + + // Try to establish connection + self::$MDB2 = MDB2::factory( $dsn, $options ); + // Die if we could not connect + if( PEAR::isError( self::$MDB2 ) ){ + die( self::$MDB2->getMessage() ); + OC_Log::write( 'migration', 'Failed to create/connect to migration.db', OC_Log::FATAL ); + OC_Log::write( 'migration', self::$MDB2->getUserInfo(), OC_Log::FATAL ); + OC_Log::write( 'migration', self::$MDB2->getMessage(), OC_Log::FATAL ); + return false; + } + // We always, really always want associative arrays + self::$MDB2->setFetchMode(MDB2_FETCHMODE_ASSOC); + } + return true; + + } + + /** + * @breif creates the tables in migration.db from an apps database.xml + * @param $appid string id of the app + * @return bool whether the operation was successful + */ + static private function createAppTables( $appid ){ + + if( !self::connectScheme() ){ + return false; + } + + // There is a database.xml file + $content = file_get_contents( OC::$SERVERROOT . '/apps/' . $appid . '/appinfo/database.xml' ); + + $file2 = 'static://db_scheme'; + // TODO get the relative path to migration.db from the data dir + // For now just cheat + $path = pathinfo( self::$dbpath ); + $content = str_replace( '*dbname*', self::$uid.'/migration', $content ); + $content = str_replace( '*dbprefix*', '', $content ); + + $xml = new SimpleXMLElement($content); + foreach($xml->table as $table){ + $tables[] = (string)$table->name; + } + + file_put_contents( $file2, $content ); + + // Try to create tables + $definition = self::$schema->parseDatabaseDefinitionFile( $file2 ); + + unlink( $file2 ); + + // Die in case something went wrong + if( $definition instanceof MDB2_Schema_Error ){ + OC_Log::write( 'migration', 'Failed to parse database.xml for: '.$appid, OC_Log::FATAL ); + OC_Log::write( 'migration', $definition->getMessage().': '.$definition->getUserInfo(), OC_Log::FATAL ); + return false; + } + + $definition['overwrite'] = true; + + $ret = self::$schema->createDatabase( $definition ); + + // Die in case something went wrong + if( $ret instanceof MDB2_Error ){ + OC_Log::write( 'migration', 'Failed to create tables for: '.$appid, OC_Log::FATAL ); + OC_Log::write( 'migration', $ret->getMessage().': '.$ret->getUserInfo(), OC_Log::FATAL ); + return false; + } + return $tables; + + } + + /** + * @breif tries to create the zip + * @param $path string path to zip destination + * @return bool + */ + static private function createZip(){ + self::$zip = new ZipArchive; + // Check if properties are set + if( !self::$zippath ){ + OC_Log::write('migration', 'createZip() called but $zip and/or $zippath have not been set', OC_Log::ERROR); + return false; + } + if ( self::$zip->open( self::$zippath, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE ) !== TRUE ) { + OC_Log::write('migration', 'Failed to create the zip with error: '.self::$zip->getStatusString(), OC_Log::ERROR); + return false; + } else { + return true; + } + } + + /** + * @breif returns an array of apps that support migration + * @return array + */ + static public function getApps(){ + $allapps = OC_App::getAllApps(); + foreach($allapps as $app){ + $path = OC::$SERVERROOT . '/apps/' . $app . '/lib/migrate.php'; + if( file_exists( $path ) ){ + $supportsmigration[] = $app; + } + } + return $supportsmigration; + } + + /** + * @breif imports a new user + * @param $db string path to migration.db + * @param $info object of migration info + * @param $uid optional uid to use + * @return array of apps with import statuses, or false on failure. + */ + public static function importAppData( $db, $info, $uid=null ){ + // Check if the db exists + if( file_exists( $db ) ){ + // Connect to the db + if(!self::connectDB( $db )){ + OC_Log::write('migration','Failed to connect to migration.db',OC_Log::ERROR); + return false; + } + } else { + OC_Log::write('migration','Migration.db not found at: '.$db, OC_Log::FATAL ); + return false; + } + + // Find providers + self::findProviders(); + + // Generate importinfo array + $importinfo = array( + 'olduid' => $info->exporteduser, + 'newuid' => self::$uid + ); + + foreach( self::$providers as $provider){ + // Is the app in the export? + $id = $provider->getID(); + if( isset( $info->apps->$id ) ){ + // Is the app installed + if( !OC_App::isEnabled( $id ) ){ + OC_Log::write( 'migration', 'App: ' . $id . ' is not installed, can\'t import data.', OC_Log::INFO ); + $appsstatus[$id] = 'notsupported'; + } else { + // Did it succeed on export? + if( $info->apps->$id->success ){ + // Give the provider the content object + if( !self::connectDB( $db ) ){ + return false; + } + $content = new OC_Migration_Content( self::$zip, self::$MDB2 ); + $provider->setData( self::$uid, $content, $info ); + // Then do the import + if( !$appsstatus[$id] = $provider->import( $info->apps->$id, $importinfo ) ){ + // Failed to import app + OC_Log::write( 'migration', 'Failed to import app data for user: ' . self::$uid . ' for app: ' . $id, OC_Log::ERROR ); + } + } else { + // Add to failed list + $appsstatus[$id] = false; + } + } + } + } + + return $appsstatus; + + } + + /* + * @breif creates a new user in the database + * @param $uid string user_id of the user to be created + * @param $hash string hash of the user to be created + * @return bool result of user creation + */ + public static function createUser( $uid, $hash ){ + + // Check if userid exists + if(OC_User::userExists( $uid )){ + return false; + } + + // Create the user + $query = OC_DB::prepare( "INSERT INTO `*PREFIX*users` ( `uid`, `password` ) VALUES( ?, ? )" ); + $result = $query->execute( array( $uid, $hash)); + if( !$result ){ + OC_Log::write('migration', 'Failed to create the new user "'.$uid.""); + } + return $result ? true : false; + + } + +} diff --git a/lib/migration/content.php b/lib/migration/content.php new file mode 100644 index 00000000000..d304051f3e6 --- /dev/null +++ b/lib/migration/content.php @@ -0,0 +1,252 @@ +<?php +/** + * ownCloud + * + * @author Tom Needham + * @copyright 2012 Tom Needham tom@owncloud.com + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +/** + * provides methods to add and access data from the migration + */ +class OC_Migration_Content{ + + private $zip=false; + // Holds the MDB2 object + private $db=null; + // Holds an array of tmpfiles to delete after zip creation + private $tmpfiles=false; + + /** + * @breif sets up the + * @param $zip ZipArchive object + * @param optional $db a MDB2 database object (required for exporttype user) + * @return bool + */ + public function __construct( $zip, $db=null ){ + + $this->zip = $zip; + $this->db = $db; + + if( !is_null( $db ) ){ + // Get db path + $db = $this->db->getDatabase(); + $this->tmpfiles[] = $db; + } + + } + + // @breif prepares the db + // @param $query the sql query to prepare + public function prepare( $query ){ + + // Optimize the query + $query = $this->processQuery( $query ); + + // Optimize the query + $query = $this->db->prepare( $query ); + + // Die if we have an error (error means: bad query, not 0 results!) + if( PEAR::isError( $query ) ) { + $entry = 'DB Error: "'.$result->getMessage().'"<br />'; + $entry .= 'Offending command was: '.$query.'<br />'; + OC_Log::write( 'migration', $entry, OC_Log::FATAL ); + return false; + } else { + return $query; + } + + } + + /** + * @breif processes the db query + * @param $query the query to process + * @return string of processed query + */ + private function processQuery( $query ){ + $query = str_replace( '`', '\'', $query ); + $query = str_replace( 'NOW()', 'datetime(\'now\')', $query ); + $query = str_replace( 'now()', 'datetime(\'now\')', $query ); + // remove table prefixes + $query = str_replace( '*PREFIX*', '', $query ); + return $query; + } + + /** + * @brief copys rows to migration.db from the main database + * @param $options array of options. + * @return bool + */ + public function copyRows( $options ){ + if( !array_key_exists( 'table', $options ) ){ + return false; + } + + $return = array(); + + // Need to include 'where' in the query? + if( array_key_exists( 'matchval', $options ) && array_key_exists( 'matchcol', $options ) ){ + + // If only one matchval, create an array + if(!is_array($options['matchval'])){ + $options['matchval'] = array( $options['matchval'] ); + } + + foreach( $options['matchval'] as $matchval ){ + // Run the query for this match value (where x = y value) + $sql = "SELECT * FROM *PREFIX*" . $options['table'] . " WHERE " . $options['matchcol'] . " LIKE ?"; + $query = OC_DB::prepare( $sql ); + $results = $query->execute( array( $matchval ) ); + $newreturns = $this->insertData( $results, $options ); + $return = array_merge( $return, $newreturns ); + } + + } else { + // Just get everything + $sql = "SELECT * FROM *PREFIX*" . $options['table']; + $query = OC_DB::prepare( $sql ); + $results = $query->execute(); + $return = $this->insertData( $results, $options ); + + } + + return $return; + + } + + /** + * @breif saves a sql data set into migration.db + * @param $data a sql data set returned from self::prepare()->query() + * @param $options array of copyRows options + * @return void + */ + private function insertData( $data, $options ){ + $return = array(); + // Foreach row of data to insert + while( $row = $data->fetchRow() ){ + // Now save all this to the migration.db + foreach($row as $field=>$value){ + $fields[] = $field; + $values[] = $value; + } + + // Generate some sql + $sql = "INSERT INTO `" . $options['table'] . '` ( `'; + $fieldssql = implode( '`, `', $fields ); + $sql .= $fieldssql . "` ) VALUES( "; + $valuessql = substr( str_repeat( '?, ', count( $fields ) ),0,-2 ); + $sql .= $valuessql . " )"; + // Make the query + $query = $this->prepare( $sql ); + if( !$query ){ + OC_Log::write( 'migration', 'Invalid sql produced: '.$sql, OC_Log::FATAL ); + return false; + exit(); + } else { + $query->execute( $values ); + // Do we need to return some values? + if( array_key_exists( 'idcol', $options ) ){ + // Yes we do + $return[] = $row[$options['idcol']]; + } else { + // Take a guess and return the first field :) + $return[] = reset($row); + } + } + $fields = ''; + $values = ''; + } + return $return; + } + + /** + * @breif adds a directory to the zip object + * @param $dir string path of the directory to add + * @param $recursive bool + * @param $internaldir string path of folder to add dir to in zip + * @return bool + */ + public function addDir( $dir, $recursive=true, $internaldir='' ) { + $dirname = basename($dir); + $this->zip->addEmptyDir($internaldir . $dirname); + $internaldir.=$dirname.='/'; + if( !file_exists( $dir ) ){ + return false; + } + if ($dirhandle = opendir($dir)) { + while (false !== ( $file = readdir($dirhandle))) { + + if (( $file != '.' ) && ( $file != '..' )) { + + if (is_dir($dir . '/' . $file) && $recursive) { + $this->addDir($dir . '/' . $file, $recursive, $internaldir); + } elseif (is_file($dir . '/' . $file)) { + $this->zip->addFile($dir . '/' . $file, $internaldir . $file); + } + } + } + closedir($dirhandle); + } else { + OC_Log::write('admin_export',"Was not able to open directory: " . $dir,OC_Log::ERROR); + return false; + } + return true; + } + + /** + * @breif adds a file to the zip from a given string + * @param $data string of data to add + * @param $path the relative path inside of the zip to save the file to + * @return bool + */ + public function addFromString( $data, $path ){ + // Create a temp file + $file = tempnam( get_temp_dir(). '/', 'oc_export_tmp_' ); + $this->tmpfiles[] = $file; + if( !file_put_contents( $file, $data ) ){ + OC_Log::write( 'migation', 'Failed to save data to a temporary file', OC_Log::ERROR ); + return false; + } + // Add file to the zip + $this->zip->addFile( $file, $path ); + return true; + } + + /** + * @breif closes the zip, removes temp files + * @return bool + */ + public function finish(){ + if( !$this->zip->close() ){ + OC_Log::write( 'migration', 'Failed to write the zip file with error: '.$this->zip->getStatusString(), OC_Log::ERROR ); + return false; + } + $this->cleanup(); + return true; + } + + /** + * @breif cleans up after the zip + */ + private function cleanup(){ + // Delete tmp files + foreach($this->tmpfiles as $i){ + unlink( $i ); + } + } +}
\ No newline at end of file diff --git a/lib/migration/provider.php b/lib/migration/provider.php new file mode 100644 index 00000000000..feae29f1354 --- /dev/null +++ b/lib/migration/provider.php @@ -0,0 +1,52 @@ +<?php +/** + * provides search functionalty + */ +abstract class OC_Migration_Provider{ + + protected $id=false; + protected $content=false; + protected $uid=false; + protected $olduid=false; + protected $appinfo=false; + + public function __construct( $appid ){ + // Set the id + $this->id = $appid; + OC_Migrate::registerProvider( $this ); + } + + /** + * @breif exports data for apps + * @return array appdata to be exported + */ + abstract function export( ); + + /** + * @breif imports data for the app + * @return void + */ + abstract function import( ); + + /** + * @breif sets the OC_Migration_Content object to $this->content + * @param $content a OC_Migration_Content object + */ + public function setData( $uid, $content, $info=null ){ + $this->content = $content; + $this->uid = $uid; + $id = $this->id; + if( !is_null( $info ) ){ + $this->olduid = $info->exporteduser; + $this->appinfo = $info->apps->$id; + } + } + + /** + * @breif returns the appid of the provider + * @return string + */ + public function getID(){ + return $this->id; + } +} diff --git a/lib/mimetypes.fixlist.php b/lib/mimetypes.fixlist.php index 51f12dbcc29..a40fbd9e228 100644 --- a/lib/mimetypes.fixlist.php +++ b/lib/mimetypes.fixlist.php @@ -16,5 +16,6 @@ return array( 'xls'=>'application/msexcel', 'xlsx'=>'application/msexcel', 'ppt'=>'application/mspowerpoint', - 'pptx'=>'application/mspowerpoint' + 'pptx'=>'application/mspowerpoint', + 'sgf' => 'application/sgf' ); diff --git a/lib/updater.php b/lib/updater.php index 57623797ae5..196822ac35d 100644 --- a/lib/updater.php +++ b/lib/updater.php @@ -36,6 +36,7 @@ class OC_Updater{ $version['installed']=OC_Config::getValue('installedat'); $version['updated']=OC_Appconfig::getValue('core', 'lastupdatedat', OC_Config::getValue( 'lastupdatedat')); $version['updatechannel']='stable'; + $version['edition']=OC_Util::getEditionString(); $versionstring=implode('x',$version); //fetch xml data from updater diff --git a/lib/util.php b/lib/util.php index 529b6d958fb..722b7404d0c 100644 --- a/lib/util.php +++ b/lib/util.php @@ -77,6 +77,14 @@ class OC_Util { return '3'; } + /** + * get the current installed edition of ownCloud. There is the community edition that just returns an empty string and the enterprise edition that returns "Enterprise". + * @return string + */ + public static function getEditionString(){ + return ''; + } + /** * add a javascript file * diff --git a/settings/ajax/getlog.php b/settings/ajax/getlog.php index 600ebefcece..ed48b2cae1a 100644 --- a/settings/ajax/getlog.php +++ b/settings/ajax/getlog.php @@ -13,5 +13,5 @@ OC_JSON::checkAdminUser(); $count=(isset($_GET['count']))?$_GET['count']:50; $offset=(isset($_GET['offset']))?$_GET['offset']:0; -$entries=OC_Log::getEntries($count,$offset); +$entries=OC_Log_Owncloud::getEntries($count,$offset); OC_JSON::success(array("data" => $entries)); diff --git a/settings/log.php b/settings/log.php index 946f2b6f8e5..ddbf72c4433 100644 --- a/settings/log.php +++ b/settings/log.php @@ -28,7 +28,7 @@ OC_Util::addStyle( "settings", "settings" ); OC_Util::addScript( "settings", "apps" ); OC_App::setActiveNavigationEntry( "core_log" ); -$entries=OC_Log::getEntries(); +$entries=OC_Log_Owncloud::getEntries(); OC_Util::addScript('settings','log'); OC_Util::addStyle('settings','settings'); diff --git a/settings/templates/apps.php b/settings/templates/apps.php index 27133e9e67e..1e49b4c8928 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -5,7 +5,7 @@ */?> <div id="controls"> - <a class="button" target="_blank" href="http://owncloud.org/contribute/writing-apps/"><?php echo $l->t('Add your application');?></a> + <a class="button" target="_blank" href="http://owncloud.org/dev/writing-apps/"><?php echo $l->t('Add your application');?></a> </div> <ul id="leftcontent"> <?php foreach($_['apps'] as $app):?> diff --git a/settings/templates/personal.php b/settings/templates/personal.php index 57731d979d9..d40da7eb773 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -50,7 +50,7 @@ };?> <p class="personalblock"> - <strong>ownCloud</strong> <?php echo(OC_Util::getVersionString()); ?><br /> + <strong>ownCloud</strong> <?php echo(OC_Util::getVersionString()); ?> <?php echo(OC_Util::getEditionString()); ?><br /> developed by the <a href="http://ownCloud.org/credits" target="_blank">ownCloud community</a><br /> <?php echo(OC_Updater::ShowUpdatingHint()); ?><br /> <a href="http://gitorious.org/owncloud" target="_blank">source code</a> licensed freely under <a href="http://www.gnu.org/licenses/agpl-3.0.html" target="_blank">AGPL</a> diff --git a/status.php b/status.php index 94c8cfce842..81f339fa53f 100644 --- a/status.php +++ b/status.php @@ -26,7 +26,7 @@ $RUNTIME_NOAPPS = TRUE; //no apps, yet require_once('lib/base.php'); if(OC_Config::getValue('installed')==1) $installed='true'; else $installed='false'; -$values=array('installed'=>$installed,'version'=>implode('.',OC_Util::getVersion()),'versionstring'=>OC_Util::getVersionString()); +$values=array('installed'=>$installed,'version'=>implode('.',OC_Util::getVersion()),'versionstring'=>OC_Util::getVersionString(),'edition'=>OC_Util::getEditionString()); echo(json_encode($values)); |