aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Gapczynski <mtgap@owncloud.com>2012-07-25 17:08:18 -0400
committerMichael Gapczynski <mtgap@owncloud.com>2012-07-25 17:08:18 -0400
commit30b58f56771aa54304069d40a62070c06f5308fc (patch)
treee0bb2bba21c561f96d27bb9cfc09d714aa21d332
parent4d17ed2f71c8cbb0d34c039aa7953b2427ce5c78 (diff)
parentf25ccaff59c135d7f1f22196bf266916ef131b35 (diff)
downloadnextcloud-server-30b58f56771aa54304069d40a62070c06f5308fc.tar.gz
nextcloud-server-30b58f56771aa54304069d40a62070c06f5308fc.zip
Merge branch 'master' into share_api
Conflicts: apps/calendar/js/loader.js apps/contacts/index.php apps/contacts/js/loader.js apps/files/js/files.js apps/files_sharing/sharedstorage.php lib/filesystemview.php
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre.includes.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Backend/Abstract.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Backend/PDO.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Calendar.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/CalendarObject.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/CalendarQueryParser.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/CalendarQueryValidator.php28
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/CalendarRootNode.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/ICSExportPlugin.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/ICalendar.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/ICalendarObject.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Plugin.php36
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Principal/Collection.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Principal/ProxyRead.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Principal/ProxyWrite.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Principal/User.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Property/SupportedCalendarData.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Property/SupportedCollationSet.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Schedule/IMip.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Schedule/IOutbox.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Schedule/Outbox.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Server.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/UserCalendars.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/Version.php2
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CalDAV/includes.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CardDAV/AddressBook.php4
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CardDAV/AddressBookQueryParser.php18
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CardDAV/AddressBookRoot.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CardDAV/Backend/Abstract.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CardDAV/Backend/PDO.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CardDAV/Card.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CardDAV/IAddressBook.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CardDAV/ICard.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CardDAV/IDirectory.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CardDAV/Plugin.php79
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CardDAV/Property/SupportedAddressData.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CardDAV/UserAddressBooks.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CardDAV/Version.php2
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/CardDAV/includes.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Auth/Backend/AbstractBasic.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Auth/Backend/AbstractDigest.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Auth/Backend/Apache.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Auth/Backend/File.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Auth/Backend/PDO.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Auth/IBackend.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Auth/Plugin.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Browser/GuessContentType.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Browser/MapGetToPropFind.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Browser/Plugin.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Browser/assets/favicon.icobin4286 -> 4286 bytes
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Browser/assets/icons/addressbook.pngbin7232 -> 7232 bytes
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Browser/assets/icons/calendar.pngbin4388 -> 4388 bytes
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Browser/assets/icons/card.pngbin5695 -> 5695 bytes
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Browser/assets/icons/collection.pngbin3474 -> 3474 bytes
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Browser/assets/icons/file.pngbin2837 -> 2837 bytes
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Browser/assets/icons/parent.pngbin3474 -> 3474 bytes
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Browser/assets/icons/principal.pngbin5480 -> 5480 bytes
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Client.php49
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Collection.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Directory.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/BadRequest.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/Conflict.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/ConflictingLock.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/FileNotFound.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/Forbidden.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/InsufficientStorage.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/InvalidResourceType.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/LockTokenMatchesRequestUri.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/Locked.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/MethodNotAllowed.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/NotAuthenticated.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/NotFound.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/NotImplemented.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/PaymentRequired.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/PreconditionFailed.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/ReportNotImplemented.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Exception/UnsupportedMediaType.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/FS/Directory.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/FS/File.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/FS/Node.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/FSExt/Directory.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/FSExt/File.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/FSExt/Node.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/File.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/ICollection.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/IExtendedCollection.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/IFile.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/INode.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/IProperties.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/IQuota.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Locks/Backend/Abstract.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Locks/Backend/FS.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Locks/Backend/File.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Locks/Backend/PDO.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Locks/LockInfo.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Locks/Plugin.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Mount/Plugin.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Node.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/ObjectTree.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Property.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Property/GetLastModified.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Property/Href.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Property/HrefList.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Property/IHref.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Property/LockDiscovery.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Property/ResourceType.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Property/Response.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Property/ResponseList.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Property/SupportedLock.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Property/SupportedReportSet.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Server.php9
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/ServerPlugin.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/SimpleCollection.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/SimpleDirectory.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/SimpleFile.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/StringUtil.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/TemporaryFileFilterPlugin.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Tree.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Tree/Filesystem.php4
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/URLUtil.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/UUIDUtil.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/Version.php2
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/XMLUtil.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAV/includes.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/AbstractPrincipalCollection.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/Exception/AceConflict.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/Exception/NeedPrivileges.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/Exception/NoAbstract.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/Exception/NotRecognizedPrincipal.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/Exception/NotSupportedPrivilege.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/IACL.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/IPrincipal.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/IPrincipalBackend.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/Plugin.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/Principal.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/PrincipalBackend/PDO.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/PrincipalCollection.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/Property/Acl.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/Property/AclRestrictions.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/Property/CurrentUserPrivilegeSet.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/Property/Principal.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/Property/SupportedPrivilegeSet.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/Version.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/DAVACL/includes.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/HTTP/AWSAuth.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/HTTP/AbstractAuth.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/HTTP/BasicAuth.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/HTTP/DigestAuth.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/HTTP/Request.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/HTTP/Response.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/HTTP/Util.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/HTTP/Version.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/HTTP/includes.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Component.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Component/VAlarm.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Component/VCalendar.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Component/VEvent.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Component/VJournal.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Component/VTodo.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/DateTimeParser.php4
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Element.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Element/DateTime.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Element/MultiDateTime.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/ElementList.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/FreeBusyGenerator.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Node.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Parameter.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/ParseException.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Property.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Property/DateTime.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Property/MultiDateTime.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Reader.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/RecurrenceIterator.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/Version.php2
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/WindowsTimezoneMap.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/VObject/includes.php0
-rwxr-xr-x[-rw-r--r--]3rdparty/Sabre/autoload.php0
-rw-r--r--3rdparty/miniColors/GPL-LICENSE.txt278
-rw-r--r--3rdparty/miniColors/MIT-LICENSE.txt20
-rwxr-xr-x3rdparty/miniColors/css/images/colors.pngbin0 -> 12485 bytes
-rwxr-xr-x3rdparty/miniColors/css/images/trigger.pngbin0 -> 1406 bytes
-rwxr-xr-x3rdparty/miniColors/css/jquery.miniColors.css81
-rwxr-xr-x3rdparty/miniColors/js/jquery.miniColors.js580
-rwxr-xr-x3rdparty/miniColors/js/jquery.miniColors.min.js9
-rw-r--r--3rdparty/openid/class.openid.v3.php (renamed from apps/user_openid/class.openid.v3.php)0
-rw-r--r--3rdparty/openid/phpmyid.php (renamed from apps/user_openid/phpmyid.php)0
-rw-r--r--apps/atnotes/ajax/browse.php112
-rw-r--r--apps/atnotes/ajax/check.php56
-rw-r--r--apps/atnotes/ajax/delete.php34
-rw-r--r--apps/atnotes/ajax/export.php56
-rw-r--r--apps/atnotes/ajax/save.php36
-rw-r--r--apps/atnotes/appinfo/app.php39
-rw-r--r--apps/atnotes/appinfo/database.xml79
-rw-r--r--apps/atnotes/appinfo/info.xml11
-rw-r--r--apps/atnotes/atnotes.php29
-rw-r--r--apps/atnotes/css/atnotes.min.css22
-rw-r--r--apps/atnotes/css/jquery-te-1.0.3.min.css11
-rw-r--r--apps/atnotes/css/jquery.qtip.min.css1
-rw-r--r--apps/atnotes/img/action.pngbin0 -> 697 bytes
-rw-r--r--apps/atnotes/img/icon.pngbin0 -> 117 bytes
-rw-r--r--apps/atnotes/img/jquery-te-toolbar.pngbin0 -> 53849 bytes
-rw-r--r--apps/atnotes/img/save.pngbin0 -> 201 bytes
-rw-r--r--apps/atnotes/js/atnotes.min.js22
-rw-r--r--apps/atnotes/js/jquery-te-1.0.3.min.js10
-rw-r--r--apps/atnotes/js/jquery.qtip.min.js13
-rw-r--r--apps/atnotes/lib/atnotes.class.php88
-rw-r--r--apps/atnotes/templates/main.tpl.php71
-rw-r--r--apps/calendar/ajax/calendar/activation.php2
-rw-r--r--apps/calendar/ajax/calendar/delete.php1
-rw-r--r--apps/calendar/ajax/calendar/new.php3
-rw-r--r--apps/calendar/ajax/calendar/update.php1
-rw-r--r--apps/calendar/ajax/categories/rescan.php1
-rw-r--r--apps/calendar/ajax/event/delete.php1
-rw-r--r--apps/calendar/ajax/event/edit.php1
-rw-r--r--apps/calendar/ajax/event/move.php1
-rw-r--r--apps/calendar/ajax/event/new.form.php2
-rw-r--r--apps/calendar/ajax/event/new.php1
-rw-r--r--apps/calendar/ajax/event/resize.php1
-rw-r--r--apps/calendar/ajax/import/calendarcheck.php18
-rw-r--r--apps/calendar/ajax/import/dialog.php2
-rw-r--r--apps/calendar/ajax/import/dropimport.php89
-rw-r--r--apps/calendar/ajax/import/import.php77
-rw-r--r--apps/calendar/ajax/settings/setfirstday.php1
-rw-r--r--apps/calendar/ajax/settings/settimeformat.php1
-rw-r--r--apps/calendar/ajax/settings/settimezone.php1
-rw-r--r--apps/calendar/ajax/share/changepermission.php4
-rw-r--r--apps/calendar/ajax/share/share.php2
-rw-r--r--apps/calendar/ajax/share/unshare.php4
-rw-r--r--apps/calendar/appinfo/app.php4
-rw-r--r--apps/calendar/appinfo/version2
-rw-r--r--apps/calendar/css/import.css14
-rw-r--r--apps/calendar/js/calendar.js19
-rw-r--r--apps/calendar/js/loader.js214
-rw-r--r--apps/calendar/js/settings.js4
-rw-r--r--apps/calendar/l10n/de.php41
-rw-r--r--apps/calendar/lib/app.php14
-rw-r--r--apps/calendar/lib/calendar.php36
-rw-r--r--apps/calendar/lib/hooks.php13
-rw-r--r--apps/calendar/lib/import.php334
-rw-r--r--apps/calendar/lib/object.php2
-rw-r--r--apps/calendar/lib/search.php2
-rw-r--r--apps/calendar/lib/share_backend.php44
-rw-r--r--apps/calendar/templates/calendar.php8
-rw-r--r--apps/calendar/templates/part.import.php74
-rw-r--r--apps/contacts/ajax/activation.php8
-rw-r--r--apps/contacts/ajax/addaddressbook.php37
-rw-r--r--apps/contacts/ajax/addcontact.php10
-rw-r--r--apps/contacts/ajax/addproperty.php42
-rw-r--r--apps/contacts/ajax/categories/categoriesfor.php15
-rw-r--r--apps/contacts/ajax/categories/delete.php3
-rw-r--r--apps/contacts/ajax/categories/rescan.php1
-rw-r--r--apps/contacts/ajax/contactdetails.php5
-rw-r--r--apps/contacts/ajax/createaddressbook.php2
-rw-r--r--apps/contacts/ajax/currentphoto.php2
-rw-r--r--apps/contacts/ajax/deletecard.php12
-rw-r--r--apps/contacts/ajax/deleteproperty.php11
-rw-r--r--apps/contacts/ajax/editaddress.php4
-rw-r--r--apps/contacts/ajax/editname.php6
-rw-r--r--apps/contacts/ajax/importaddressbook.php23
-rw-r--r--apps/contacts/ajax/loadcard.php14
-rw-r--r--apps/contacts/ajax/loadintro.php2
-rw-r--r--apps/contacts/ajax/loghandler.php10
-rw-r--r--apps/contacts/ajax/oc_photo.php6
-rw-r--r--apps/contacts/ajax/savecrop.php22
-rw-r--r--apps/contacts/ajax/saveproperty.php21
-rw-r--r--apps/contacts/ajax/selectaddressbook.php16
-rw-r--r--apps/contacts/ajax/updateaddressbook.php2
-rw-r--r--apps/contacts/ajax/uploadimport.php40
-rw-r--r--apps/contacts/ajax/uploadphoto.php46
-rw-r--r--apps/contacts/appinfo/app.php2
-rw-r--r--apps/contacts/appinfo/database.xml4
-rw-r--r--apps/contacts/appinfo/migrate.php15
-rw-r--r--apps/contacts/appinfo/remote.php4
-rw-r--r--apps/contacts/appinfo/update.php2
-rw-r--r--apps/contacts/appinfo/version2
-rw-r--r--apps/contacts/carddav.php6
-rw-r--r--apps/contacts/css/contacts.css22
-rw-r--r--apps/contacts/export.php18
-rw-r--r--apps/contacts/import.php70
-rw-r--r--apps/contacts/index.php46
-rw-r--r--apps/contacts/js/contacts.js1204
-rw-r--r--apps/contacts/js/jquery.inview.js4
-rw-r--r--apps/contacts/lib/VCFExportPlugin.php8
-rw-r--r--apps/contacts/lib/addressbook.php82
-rw-r--r--apps/contacts/lib/app.php12
-rw-r--r--apps/contacts/lib/connector_sabre.php6
-rw-r--r--apps/contacts/lib/hooks.php24
-rw-r--r--apps/contacts/lib/search.php6
-rw-r--r--apps/contacts/lib/vcard.php130
-rw-r--r--apps/contacts/photo.php23
-rw-r--r--apps/contacts/templates/index.php51
-rw-r--r--apps/contacts/templates/part.chooseaddressbook.php1
-rw-r--r--apps/contacts/templates/part.cropphoto.php1
-rw-r--r--apps/contacts/templates/part.edit_categories_dialog.php16
-rw-r--r--apps/contacts/templates/part.importaddressbook.php38
-rw-r--r--apps/contacts/templates/part.selectaddressbook.php27
-rw-r--r--apps/contacts/thumbnail.php27
-rw-r--r--apps/contacts/tmpphoto.php2
-rw-r--r--apps/files/ajax/newfile.php58
-rw-r--r--apps/files/ajax/scan.php5
-rw-r--r--apps/files/appinfo/update.php11
-rw-r--r--apps/files/appinfo/version2
-rw-r--r--apps/files/js/files.js38
-rwxr-xr-xapps/files/share.php38
-rw-r--r--apps/files_encryption/lib/cryptstream.php3
-rwxr-xr-xapps/files_external/lib/dropbox.php2
-rw-r--r--apps/files_external/lib/dropboxtest.php71
-rw-r--r--apps/files_external/lib/google.php9
-rw-r--r--apps/files_external/lib/googletest.php8
-rw-r--r--apps/files_external/lib/smb.php2
-rw-r--r--apps/files_external/lib/webdav.php15
-rw-r--r--apps/files_external/tests/ftp.php1
-rw-r--r--apps/files_external/tests/google.php1
-rw-r--r--apps/files_external/tests/smb.php1
-rw-r--r--apps/files_external/tests/swift.php1
-rw-r--r--apps/files_external/tests/test.php7
-rw-r--r--apps/files_external/tests/webdav.php1
-rw-r--r--apps/files_sharing/appinfo/version2
-rw-r--r--apps/files_sharing/sharedstorage.php11
-rwxr-xr-xapps/files_sharing/sharetest.php7
-rwxr-xr-xapps/files_sharing/templates/get.php11
-rw-r--r--apps/files_sharing_log/appinfo/database.xml2
-rw-r--r--apps/files_versions/ajax/expireAll.php1
-rw-r--r--apps/files_versions/ajax/rollbackVersion.php1
-rw-r--r--apps/files_versions/ajax/togglesettings.php1
-rw-r--r--apps/files_versions/appinfo/api.php36
-rw-r--r--apps/files_versions/lib/versions.php6
-rw-r--r--apps/gallery/appinfo/version2
-rw-r--r--apps/gallery/l10n/de.php8
-rw-r--r--apps/gallery/lib/album.php2
-rw-r--r--apps/gallery/lib/managers.php1
-rw-r--r--apps/gallery/lib/share.php42
-rw-r--r--apps/gallery/lib/tiles.php2
-rw-r--r--apps/journal/.gitignore51
-rw-r--r--apps/journal/COPYING-AGPL661
-rw-r--r--apps/journal/COPYING-README10
-rw-r--r--apps/journal/Changelog5
-rw-r--r--apps/journal/README.md38
-rw-r--r--apps/journal/TODO13
-rw-r--r--apps/journal/ajax/categories/categoriesfor.php28
-rw-r--r--apps/journal/ajax/categories/delete.php54
-rw-r--r--apps/journal/ajax/categories/list.php17
-rw-r--r--apps/journal/ajax/categories/rescan.php42
-rw-r--r--apps/journal/ajax/delete.php37
-rw-r--r--apps/journal/ajax/entries.php36
-rw-r--r--apps/journal/ajax/saveproperty.php138
-rw-r--r--apps/journal/ajax/setdefaultcalendar.php (renamed from apps/contacts/ajax/loadphoto.php)36
-rw-r--r--apps/journal/ajax/util.php40
-rw-r--r--apps/journal/appinfo/app.php18
-rw-r--r--apps/journal/appinfo/info.xml11
-rw-r--r--apps/journal/css/journal.css19
-rw-r--r--apps/journal/css/rte.css7
-rw-r--r--apps/journal/img/bold.svg11
-rw-r--r--apps/journal/img/indent-less.svg300
-rw-r--r--apps/journal/img/indent-more.svg295
-rw-r--r--apps/journal/img/italic.svg8
-rw-r--r--apps/journal/img/journal.pngbin0 -> 581 bytes
-rw-r--r--apps/journal/img/journal.svg104
-rw-r--r--apps/journal/img/justify-center.svg269
-rw-r--r--apps/journal/img/justify-fill.svg269
-rw-r--r--apps/journal/img/justify-left.svg199
-rw-r--r--apps/journal/img/justify-right.svg269
-rw-r--r--apps/journal/img/list.svg12
-rw-r--r--apps/journal/img/strikethrough.svg10
-rw-r--r--apps/journal/img/underline.svg10
-rw-r--r--apps/journal/index.php72
-rw-r--r--apps/journal/js/expanding.js118
-rw-r--r--apps/journal/js/journal.js432
-rw-r--r--apps/journal/js/jquery.rte.js244
-rw-r--r--apps/journal/js/jquery.textchange.js76
-rw-r--r--apps/journal/js/settings.js15
-rw-r--r--apps/journal/lib/app.php135
-rw-r--r--apps/journal/lib/hooks.php53
-rw-r--r--apps/journal/lib/search.php45
-rw-r--r--apps/journal/lib/vjournal.php27
-rw-r--r--apps/journal/settings.php45
-rw-r--r--apps/journal/templates/index.pt101
-rw-r--r--apps/journal/templates/rtfm.php7
-rw-r--r--apps/journal/templates/settings.pt15
-rw-r--r--apps/media/appinfo/version2
-rw-r--r--apps/media/lib/share/album.php0
-rw-r--r--apps/media/lib/share/artist.php65
-rw-r--r--apps/media/lib/share/song.php65
-rw-r--r--apps/media/lib_ampache.php1
-rw-r--r--apps/media/lib_collection.php3
-rw-r--r--apps/media/lib_media.php6
-rw-r--r--apps/news/.gitignore5
-rw-r--r--apps/news/ajax/createfeed.php37
-rw-r--r--apps/news/ajax/createfolder.php41
-rw-r--r--apps/news/ajax/deletefeed.php33
-rw-r--r--apps/news/ajax/deletefolder.php33
-rw-r--r--apps/news/ajax/feeddialog.php7
-rw-r--r--apps/news/ajax/folderdialog.php7
-rw-r--r--apps/news/ajax/markitem.php35
-rw-r--r--apps/news/ajax/populateroot.php18
-rw-r--r--apps/news/appinfo/app.php42
-rw-r--r--apps/news/appinfo/database.xml186
-rw-r--r--apps/news/appinfo/info.xml10
-rw-r--r--apps/news/css/news.css52
-rw-r--r--apps/news/index.php44
-rw-r--r--apps/news/js/news.js196
-rw-r--r--apps/news/js/settings.js15
-rw-r--r--apps/news/lib/collection.php32
-rw-r--r--apps/news/lib/feed.php46
-rw-r--r--apps/news/lib/feedmapper.php182
-rw-r--r--apps/news/lib/folder.php56
-rw-r--r--apps/news/lib/foldermapper.php148
-rw-r--r--apps/news/lib/item.php122
-rw-r--r--apps/news/lib/itemmapper.php171
-rw-r--r--apps/news/lib/utils.php46
-rw-r--r--apps/news/settings.php8
-rw-r--r--apps/news/templates/main.php36
-rw-r--r--apps/news/templates/part.addfeed.php22
-rw-r--r--apps/news/templates/part.addfolder.php22
-rw-r--r--apps/news/templates/part.feeds.php31
-rw-r--r--apps/news/templates/part.folderlist.php12
-rw-r--r--apps/news/templates/part.items.php23
-rw-r--r--apps/news/templates/part.nofeeds.php3
-rw-r--r--apps/news/templates/settings.php8
-rw-r--r--apps/news/templates/test.php85
-rw-r--r--apps/remoteStorage/appinfo/info.xml3
-rw-r--r--apps/remoteStorage/appinfo/version2
-rw-r--r--apps/remoteStorage/appinfo/webfinger.php6
-rw-r--r--apps/remoteStorage/lib_remoteStorage.php13
-rw-r--r--apps/remoteStorage/oauth_ro_auth.php20
-rw-r--r--apps/remoteStorage/webdav.php (renamed from apps/remoteStorage/WebDAV.php)36
-rw-r--r--apps/shorty/.htaccess45
-rw-r--r--apps/shorty/3rdparty/js/jquery.tinysort.min.js1
-rw-r--r--apps/shorty/3rdparty/php/phpqrcode.php3312
-rw-r--r--apps/shorty/ajax/add.php101
-rw-r--r--apps/shorty/ajax/click.php67
-rw-r--r--apps/shorty/ajax/count.php54
-rw-r--r--apps/shorty/ajax/del.php63
-rw-r--r--apps/shorty/ajax/edit.php88
-rw-r--r--apps/shorty/ajax/list.php77
-rw-r--r--apps/shorty/ajax/meta.php56
-rw-r--r--apps/shorty/ajax/preferences.php119
-rw-r--r--apps/shorty/ajax/settings.php109
-rw-r--r--apps/shorty/ajax/status.php67
-rw-r--r--apps/shorty/appinfo/app.php53
-rw-r--r--apps/shorty/appinfo/database.xml148
-rw-r--r--apps/shorty/appinfo/info.xml56
-rw-r--r--apps/shorty/appinfo/migrate.php101
-rw-r--r--apps/shorty/css/preferences.css109
-rw-r--r--apps/shorty/css/settings.css59
-rw-r--r--apps/shorty/css/shorty.css488
-rw-r--r--apps/shorty/doc/CHANGELOG59
-rw-r--r--apps/shorty/doc/CONFIGURATION113
-rw-r--r--apps/shorty/doc/COPYRIGHT23
-rw-r--r--apps/shorty/doc/CREDITS32
-rw-r--r--apps/shorty/doc/INSTALLATION53
-rw-r--r--apps/shorty/doc/ISSUES58
-rw-r--r--apps/shorty/doc/README42
-rw-r--r--apps/shorty/doc/ROADMAP96
-rw-r--r--apps/shorty/doc/STATUS42
-rw-r--r--apps/shorty/doc/USAGE64
-rw-r--r--apps/shorty/img/actions/down.pngbin0 -> 250 bytes
-rw-r--r--apps/shorty/img/actions/down.svg9
-rw-r--r--apps/shorty/img/actions/external.pngbin0 -> 421 bytes
-rw-r--r--apps/shorty/img/actions/external.svg11
-rw-r--r--apps/shorty/img/actions/info.pngbin0 -> 297 bytes
-rw-r--r--apps/shorty/img/actions/info.svg70
-rw-r--r--apps/shorty/img/actions/left.pngbin0 -> 221 bytes
-rw-r--r--apps/shorty/img/actions/left.svg9
-rw-r--r--apps/shorty/img/actions/minus.pngbin0 -> 288 bytes
-rw-r--r--apps/shorty/img/actions/open.pngbin0 -> 320 bytes
-rw-r--r--apps/shorty/img/actions/open.svg10
-rw-r--r--apps/shorty/img/actions/plus.pngbin0 -> 297 bytes
-rw-r--r--apps/shorty/img/actions/recycle.pngbin0 -> 751 bytes
-rw-r--r--apps/shorty/img/actions/recycle.svg93
-rw-r--r--apps/shorty/img/actions/reload.pngbin0 -> 1083 bytes
-rw-r--r--apps/shorty/img/actions/right.pngbin0 -> 220 bytes
-rw-r--r--apps/shorty/img/actions/right.svg9
-rw-r--r--apps/shorty/img/actions/shade.pngbin0 -> 434 bytes
-rw-r--r--apps/shorty/img/actions/shade.svg78
-rw-r--r--apps/shorty/img/actions/up.pngbin0 -> 235 bytes
-rw-r--r--apps/shorty/img/actions/up.svg9
-rw-r--r--apps/shorty/img/blank.pngbin0 -> 219 bytes
-rw-r--r--apps/shorty/img/calendar.pngbin0 -> 659 bytes
-rw-r--r--apps/shorty/img/drag_me.pngbin0 -> 5189 bytes
-rw-r--r--apps/shorty/img/drag_me.svg135
-rw-r--r--apps/shorty/img/loading-bar.gifbin0 -> 1190 bytes
-rw-r--r--apps/shorty/img/loading-disk.gifbin0 -> 6894 bytes
-rw-r--r--apps/shorty/img/loading-led.gifbin0 -> 684 bytes
-rw-r--r--apps/shorty/img/scheme/A.pngbin0 -> 973 bytes
-rw-r--r--apps/shorty/img/scheme/B.pngbin0 -> 1111 bytes
-rw-r--r--apps/shorty/img/scheme/C.pngbin0 -> 978 bytes
-rw-r--r--apps/shorty/img/scheme/D.pngbin0 -> 1035 bytes
-rw-r--r--apps/shorty/img/scheme/E.pngbin0 -> 988 bytes
-rw-r--r--apps/shorty/img/scheme/F.pngbin0 -> 969 bytes
-rw-r--r--apps/shorty/img/scheme/G.pngbin0 -> 995 bytes
-rw-r--r--apps/shorty/img/scheme/H.pngbin0 -> 991 bytes
-rw-r--r--apps/shorty/img/scheme/J.pngbin0 -> 935 bytes
-rw-r--r--apps/shorty/img/scheme/K.pngbin0 -> 980 bytes
-rw-r--r--apps/shorty/img/scheme/L.pngbin0 -> 910 bytes
-rw-r--r--apps/shorty/img/scheme/M.pngbin0 -> 985 bytes
-rw-r--r--apps/shorty/img/scheme/N.pngbin0 -> 996 bytes
-rw-r--r--apps/shorty/img/scheme/O.pngbin0 -> 1016 bytes
-rw-r--r--apps/shorty/img/scheme/P.pngbin0 -> 950 bytes
-rw-r--r--apps/shorty/img/scheme/Q.pngbin0 -> 1041 bytes
-rw-r--r--apps/shorty/img/scheme/R.pngbin0 -> 983 bytes
-rw-r--r--apps/shorty/img/scheme/S.pngbin0 -> 950 bytes
-rw-r--r--apps/shorty/img/scheme/T.pngbin0 -> 904 bytes
-rw-r--r--apps/shorty/img/scheme/U.pngbin0 -> 977 bytes
-rw-r--r--apps/shorty/img/scheme/V.pngbin0 -> 967 bytes
-rw-r--r--apps/shorty/img/scheme/W.pngbin0 -> 1026 bytes
-rw-r--r--apps/shorty/img/scheme/X.pngbin0 -> 959 bytes
-rw-r--r--apps/shorty/img/scheme/Y.pngbin0 -> 927 bytes
-rw-r--r--apps/shorty/img/scheme/Z.pngbin0 -> 956 bytes
-rw-r--r--apps/shorty/img/shorty.pngbin0 -> 746 bytes
-rw-r--r--apps/shorty/img/shorty.svg11
-rw-r--r--apps/shorty/img/status/active.pngbin0 -> 485 bytes
-rw-r--r--apps/shorty/img/status/active.svg9
-rw-r--r--apps/shorty/img/status/bad.pngbin0 -> 795 bytes
-rw-r--r--apps/shorty/img/status/good.pngbin0 -> 794 bytes
-rw-r--r--apps/shorty/img/status/neutral.pngbin0 -> 763 bytes
-rw-r--r--apps/shorty/img/status/strike.pngbin0 -> 355 bytes
-rw-r--r--apps/shorty/img/usage/64/arrow.pngbin0 -> 3309 bytes
-rw-r--r--apps/shorty/img/usage/64/clipboard.pngbin0 -> 3402 bytes
-rw-r--r--apps/shorty/img/usage/64/email.pngbin0 -> 4302 bytes
-rw-r--r--apps/shorty/img/usage/64/globe.pngbin0 -> 5242 bytes
-rw-r--r--apps/shorty/img/usage/64/home.pngbin0 -> 4009 bytes
-rw-r--r--apps/shorty/img/usage/64/info.pngbin0 -> 2817 bytes
-rw-r--r--apps/shorty/img/usage/64/locked.pngbin0 -> 3091 bytes
-rw-r--r--apps/shorty/img/usage/64/network.pngbin0 -> 3451 bytes
-rw-r--r--apps/shorty/img/usage/64/qrcode.pngbin0 -> 6373 bytes
-rw-r--r--apps/shorty/img/usage/64/sms.pngbin0 -> 3700 bytes
-rw-r--r--apps/shorty/img/usage/64/team.pngbin0 -> 3539 bytes
-rw-r--r--apps/shorty/img/usage/64/unlocked.pngbin0 -> 3115 bytes
-rw-r--r--apps/shorty/img/usage/64/workstation.pngbin0 -> 3218 bytes
-rw-r--r--apps/shorty/img/usage/_.svg19
-rw-r--r--apps/shorty/img/usage/arrow.svg22
-rw-r--r--apps/shorty/img/usage/clipboard.svg34
-rw-r--r--apps/shorty/img/usage/email.svg21
-rw-r--r--apps/shorty/img/usage/globe.svg19
-rw-r--r--apps/shorty/img/usage/home.svg23
-rw-r--r--apps/shorty/img/usage/info.svg21
-rw-r--r--apps/shorty/img/usage/locked.svg23
-rw-r--r--apps/shorty/img/usage/network.svg34
-rw-r--r--apps/shorty/img/usage/qrcode.svg271
-rw-r--r--apps/shorty/img/usage/sms.svg19
-rw-r--r--apps/shorty/img/usage/team.svg20
-rw-r--r--apps/shorty/img/usage/unlocked.svg23
-rw-r--r--apps/shorty/img/usage/workstation.svg22
-rw-r--r--apps/shorty/index.php149
-rw-r--r--apps/shorty/js/add.js51
-rw-r--r--apps/shorty/js/debug.js71
-rw-r--r--apps/shorty/js/init.js92
-rw-r--r--apps/shorty/js/list.js39
-rw-r--r--apps/shorty/js/preferences.js69
-rw-r--r--apps/shorty/js/settings.js53
-rw-r--r--apps/shorty/js/shorty.js1489
-rw-r--r--apps/shorty/l10n/de.php157
-rw-r--r--apps/shorty/lib/backend.php286
-rw-r--r--apps/shorty/lib/exception.php162
-rw-r--r--apps/shorty/lib/hooks.php64
-rw-r--r--apps/shorty/lib/l10n.php93
-rw-r--r--apps/shorty/lib/meta.php213
-rw-r--r--apps/shorty/lib/query.php57
-rw-r--r--apps/shorty/lib/tools.php235
-rw-r--r--apps/shorty/lib/type.php291
-rw-r--r--apps/shorty/preferences.php68
-rw-r--r--apps/shorty/qrcode.php100
-rw-r--r--apps/shorty/relay.php140
-rw-r--r--apps/shorty/settings.php52
-rw-r--r--apps/shorty/templates/tmpl_dlg_qrcode.php52
-rw-r--r--apps/shorty/templates/tmpl_dlg_verify.php70
-rw-r--r--apps/shorty/templates/tmpl_http_status.php41
-rw-r--r--apps/shorty/templates/tmpl_index.php66
-rw-r--r--apps/shorty/templates/tmpl_preferences.php257
-rw-r--r--apps/shorty/templates/tmpl_settings.php70
-rw-r--r--apps/shorty/templates/tmpl_url_add.php86
-rw-r--r--apps/shorty/templates/tmpl_url_edit.php100
-rw-r--r--apps/shorty/templates/tmpl_url_list.php167
-rw-r--r--apps/shorty/templates/tmpl_url_share.php89
-rw-r--r--apps/shorty/templates/tmpl_url_show.php85
-rw-r--r--apps/storage_charts/ajax/config.php44
-rw-r--r--apps/storage_charts/ajax/data.php37
-rw-r--r--apps/storage_charts/appinfo/app.php51
-rw-r--r--apps/storage_charts/appinfo/app_admin.php58
-rw-r--r--apps/storage_charts/appinfo/database.xml99
-rw-r--r--apps/storage_charts/appinfo/info.xml11
-rw-r--r--apps/storage_charts/charts.php46
-rw-r--r--apps/storage_charts/css/styles.css32
-rw-r--r--apps/storage_charts/img/chart.pngbin0 -> 441 bytes
-rw-r--r--apps/storage_charts/img/loader.gifbin0 -> 1849 bytes
-rw-r--r--apps/storage_charts/img/move.pngbin0 -> 457 bytes
-rw-r--r--apps/storage_charts/js/highCharts-2.2.1/highcharts.min.js202
-rw-r--r--apps/storage_charts/js/units.min.js23
-rw-r--r--apps/storage_charts/l10n/de.php58
-rw-r--r--apps/storage_charts/l10n/en.php58
-rw-r--r--apps/storage_charts/l10n/fr.php58
-rw-r--r--apps/storage_charts/l10n/pt_BR.php59
-rw-r--r--apps/storage_charts/lib/db.class.php290
-rw-r--r--apps/storage_charts/lib/loader.class.php102
-rw-r--r--apps/storage_charts/settings.php43
-rw-r--r--apps/storage_charts/templates/charts.tpl.php77
-rw-r--r--apps/storage_charts/templates/settings.tpl.php40
-rw-r--r--apps/tal/.gitignore51
-rw-r--r--apps/tal/COPYING-AGPL661
-rw-r--r--apps/tal/COPYING-README10
-rw-r--r--apps/tal/README.md26
-rw-r--r--apps/tal/TODO2
-rw-r--r--apps/tal/ajax/loadpage.php23
-rw-r--r--apps/tal/appinfo/app.php11
-rw-r--r--apps/tal/appinfo/info.xml11
-rw-r--r--apps/tal/css/tal.css25
-rw-r--r--apps/tal/index.php30
-rw-r--r--apps/tal/js/modernizr.js1265
-rw-r--r--apps/tal/js/tal.js32
-rw-r--r--apps/tal/lib/paths.php13
-rw-r--r--apps/tal/lib/tall10n.php105
-rw-r--r--apps/tal/lib/taltemplate.php372
-rw-r--r--apps/tal/settings.php16
-rw-r--r--apps/tal/templates/layout.guest.pt43
-rw-r--r--apps/tal/templates/layout.user.pt78
-rw-r--r--apps/tal/templates/macros.util.pt56
-rw-r--r--apps/tal/templates/manual.pt23
-rw-r--r--apps/tal/templates/sections.pt142
-rw-r--r--apps/tal/templates/settings.pt10
-rw-r--r--apps/tanghus-journal-2c506aa.tar.gzbin0 -> 41637 bytes
-rw-r--r--apps/tanghus-tal-7f8befe.tar.gzbin0 -> 37994 bytes
-rw-r--r--apps/tasks/ajax/addtask.php3
-rw-r--r--apps/tasks/ajax/addtaskform.php20
-rw-r--r--apps/tasks/ajax/delete.php1
-rw-r--r--apps/tasks/ajax/edittask.php31
-rw-r--r--apps/tasks/ajax/edittaskform.php24
-rw-r--r--apps/tasks/ajax/getdetails.php24
-rw-r--r--apps/tasks/ajax/gettasks.php2
-rw-r--r--apps/tasks/ajax/update_property.php5
-rw-r--r--apps/tasks/index.php4
-rw-r--r--apps/tasks/js/tasks.js62
-rw-r--r--apps/tasks/lib/app.php18
-rw-r--r--apps/tasks/templates/part.addtaskform.php15
-rw-r--r--apps/tasks/templates/part.details.php42
-rw-r--r--apps/tasks/templates/part.edittaskform.php5
-rw-r--r--apps/tasks/templates/part.property.php22
-rw-r--r--apps/tasks/templates/part.taskform.php36
-rw-r--r--apps/tasks/templates/part.tasks.php3
-rw-r--r--apps/user_external/appinfo/app.php4
-rw-r--r--apps/user_external/appinfo/info.xml13
-rw-r--r--apps/user_external/appinfo/version1
-rw-r--r--apps/user_external/lib/ftp.php45
-rw-r--r--apps/user_external/lib/imap.php40
-rw-r--r--apps/user_external/lib/smb.php43
-rw-r--r--apps/user_external/tests/config.php28
-rw-r--r--apps/user_external/tests/ftp.php34
-rw-r--r--apps/user_external/tests/imap.php34
-rw-r--r--apps/user_external/tests/smb.php34
-rw-r--r--apps/user_ldap/appinfo/app.php14
-rw-r--r--apps/user_ldap/group_ldap.php94
-rw-r--r--apps/user_ldap/lib/access.php597
-rw-r--r--apps/user_ldap/lib/connection.php255
-rw-r--r--apps/user_ldap/lib_ldap.php721
-rw-r--r--apps/user_ldap/tests/group_ldap.php4
-rw-r--r--apps/user_ldap/user_ldap.php72
-rw-r--r--apps/user_migrate/templates/settings.php2
-rw-r--r--apps/user_openid/user.php2
-rw-r--r--apps/user_openid/user_openid.php2
-rw-r--r--apps/user_webfinger/.htaccess5
-rw-r--r--apps/user_webfinger/appinfo/app.php2
-rw-r--r--apps/user_webfinger/appinfo/info.xml4
-rw-r--r--apps/user_webfinger/appinfo/version2
-rw-r--r--apps/user_webfinger/host-meta.php19
-rwxr-xr-xautotest.sh110
-rw-r--r--config/mount.sample.php13
-rw-r--r--core/css/styles.css2
-rw-r--r--core/js/eventsource.js1
-rw-r--r--core/templates/exception.php30
-rw-r--r--core/templates/layout.user.php1
-rw-r--r--core/templates/login.php2
-rw-r--r--l10n/de/calendar.po411
-rw-r--r--l10n/de/gallery.po66
-rw-r--r--l10n/templates/bookmarks.pot4
-rw-r--r--l10n/templates/calendar.pot375
-rw-r--r--l10n/templates/contacts.pot561
-rw-r--r--l10n/templates/core.pot34
-rw-r--r--l10n/templates/files.pot40
-rw-r--r--l10n/templates/gallery.pot50
-rw-r--r--l10n/templates/media.pot4
-rw-r--r--l10n/templates/settings.pot44
-rw-r--r--lib/MDB2/Driver/sqlite3.php2
-rwxr-xr-xlib/app.php9
-rw-r--r--lib/archive/zip.php4
-rw-r--r--lib/base.php5
-rw-r--r--lib/cache.php108
-rw-r--r--lib/cache/apc.php5
-rw-r--r--lib/cache/broker.php6
-rw-r--r--lib/cache/file.php5
-rw-r--r--lib/cache/xcache.php5
-rw-r--r--lib/connector/sabre/auth.php17
-rw-r--r--lib/connector/sabre/client.php22
-rw-r--r--lib/connector/sabre/directory.php24
-rw-r--r--lib/connector/sabre/file.php27
-rw-r--r--lib/connector/sabre/locks.php2
-rw-r--r--lib/connector/sabre/node.php29
-rw-r--r--lib/db.php15
-rw-r--r--lib/eventsource.php5
-rw-r--r--lib/exception.php93
-rw-r--r--lib/filecache.php36
-rw-r--r--lib/filecache/update.php1
-rw-r--r--lib/filestorage.php4
-rw-r--r--lib/filestorage/common.php4
-rw-r--r--lib/filestorage/local.php14
-rw-r--r--lib/filesystem.php229
-rw-r--r--lib/filesystemview.php449
-rw-r--r--lib/group.php8
-rw-r--r--lib/group/backend.php2
-rw-r--r--lib/group/database.php5
-rw-r--r--lib/group/dummy.php3
-rw-r--r--lib/group/example.php18
-rw-r--r--lib/group/interface.php76
-rw-r--r--lib/helper.php63
-rw-r--r--lib/image.php18
-rw-r--r--lib/installer.php30
-rw-r--r--lib/json.php14
-rw-r--r--lib/l10n.php7
-rw-r--r--lib/migrate.php2
-rw-r--r--lib/mimetypes.fixlist.php22
-rw-r--r--lib/mimetypes.list.php13
-rw-r--r--lib/minimizer.php10
-rw-r--r--lib/ocs.php1002
-rw-r--r--lib/ocsclient.php2
-rw-r--r--lib/preferences.php8
-rw-r--r--lib/public/app.php22
-rw-r--r--lib/public/groupinterface.php31
-rw-r--r--lib/public/json.php105
-rw-r--r--lib/public/userinterface.php31
-rw-r--r--lib/public/util.php11
-rw-r--r--lib/search/provider.php10
-rw-r--r--lib/setup.php3
-rw-r--r--lib/streamwrappers.php5
-rw-r--r--lib/template.php1
-rw-r--r--lib/templatelayout.php2
-rw-r--r--lib/user.php50
-rw-r--r--lib/user/backend.php2
-rw-r--r--lib/user/database.php8
-rw-r--r--lib/user/example.php12
-rw-r--r--lib/user/interface.php60
-rwxr-xr-xlib/util.php61
-rw-r--r--ocs/providers.php4
-rw-r--r--public.php1
-rw-r--r--remote.php10
-rw-r--r--remote/.gitignore2
-rw-r--r--settings/js/users.js33
-rw-r--r--sharing.patch63
-rw-r--r--tests/index.php57
-rw-r--r--tests/lib/cache.php21
-rw-r--r--tests/lib/cache/file.php25
-rw-r--r--tests/lib/share/backend.php66
-rw-r--r--tests/lib/share/share.php161
-rw-r--r--tests/lib/user/database.php1
-rw-r--r--webapps.php54
756 files changed, 31471 insertions, 4182 deletions
diff --git a/3rdparty/Sabre.includes.php b/3rdparty/Sabre.includes.php
index c1334373663..c1334373663 100644..100755
--- a/3rdparty/Sabre.includes.php
+++ b/3rdparty/Sabre.includes.php
diff --git a/3rdparty/Sabre/CalDAV/Backend/Abstract.php b/3rdparty/Sabre/CalDAV/Backend/Abstract.php
index 7aba1d69ffe..7aba1d69ffe 100644..100755
--- a/3rdparty/Sabre/CalDAV/Backend/Abstract.php
+++ b/3rdparty/Sabre/CalDAV/Backend/Abstract.php
diff --git a/3rdparty/Sabre/CalDAV/Backend/PDO.php b/3rdparty/Sabre/CalDAV/Backend/PDO.php
index ddacf940c74..ddacf940c74 100644..100755
--- a/3rdparty/Sabre/CalDAV/Backend/PDO.php
+++ b/3rdparty/Sabre/CalDAV/Backend/PDO.php
diff --git a/3rdparty/Sabre/CalDAV/Calendar.php b/3rdparty/Sabre/CalDAV/Calendar.php
index 623df2dd1b8..623df2dd1b8 100644..100755
--- a/3rdparty/Sabre/CalDAV/Calendar.php
+++ b/3rdparty/Sabre/CalDAV/Calendar.php
diff --git a/3rdparty/Sabre/CalDAV/CalendarObject.php b/3rdparty/Sabre/CalDAV/CalendarObject.php
index 72f0a578d16..72f0a578d16 100644..100755
--- a/3rdparty/Sabre/CalDAV/CalendarObject.php
+++ b/3rdparty/Sabre/CalDAV/CalendarObject.php
diff --git a/3rdparty/Sabre/CalDAV/CalendarQueryParser.php b/3rdparty/Sabre/CalDAV/CalendarQueryParser.php
index bd0d343382f..bd0d343382f 100644..100755
--- a/3rdparty/Sabre/CalDAV/CalendarQueryParser.php
+++ b/3rdparty/Sabre/CalDAV/CalendarQueryParser.php
diff --git a/3rdparty/Sabre/CalDAV/CalendarQueryValidator.php b/3rdparty/Sabre/CalDAV/CalendarQueryValidator.php
index 1bb6b5d53fa..4bcd32cdf88 100644..100755
--- a/3rdparty/Sabre/CalDAV/CalendarQueryValidator.php
+++ b/3rdparty/Sabre/CalDAV/CalendarQueryValidator.php
@@ -294,6 +294,7 @@ class Sabre_CalDAV_CalendarQueryValidator {
// in the VALARM component code, so this is a hack, and an
// expensive one too.
if ($component->parent->name === 'VEVENT' && $component->parent->RRULE) {
+
// Fire up the iterator!
$it = new Sabre_VObject_RecurrenceIterator($component->parent->parent, (string)$component->parent->UID);
while($it->valid()) {
@@ -304,14 +305,35 @@ class Sabre_CalDAV_CalendarQueryValidator {
// determine if we can 'give up' expanding events.
$firstAlarm = null;
foreach($expandedEvent->VALARM as $expandedAlarm) {
+
$effectiveTrigger = $expandedAlarm->getEffectiveTriggerTime();
- if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
- $firstAlarm = $effectiveTrigger;
- }
if ($expandedAlarm->isInTimeRange($start, $end)) {
return true;
}
+ if ((string)$expandedAlarm->TRIGGER['VALUE'] === 'DATE-TIME') {
+ // This is an alarm with a non-relative trigger
+ // time, likely created by a buggy client. The
+ // implication is that every alarm in this
+ // recurring event trigger at the exact same
+ // time. It doesn't make sense to traverse
+ // further.
+ } else {
+ // We store the first alarm as a means to
+ // figure out when we can stop traversing.
+ if (!$firstAlarm || $effectiveTrigger < $firstAlarm) {
+ $firstAlarm = $effectiveTrigger;
+ }
+ }
+
+ }
+ if (is_null($firstAlarm)) {
+ // No alarm was found.
+ //
+ // Or technically: No alarm that will change for
+ // every instance of the recurrence was found,
+ // which means we can assume there was no match.
+ return false;
}
if ($firstAlarm > $end) {
return false;
diff --git a/3rdparty/Sabre/CalDAV/CalendarRootNode.php b/3rdparty/Sabre/CalDAV/CalendarRootNode.php
index 3907913cc78..3907913cc78 100644..100755
--- a/3rdparty/Sabre/CalDAV/CalendarRootNode.php
+++ b/3rdparty/Sabre/CalDAV/CalendarRootNode.php
diff --git a/3rdparty/Sabre/CalDAV/ICSExportPlugin.php b/3rdparty/Sabre/CalDAV/ICSExportPlugin.php
index ec42b406b2f..ec42b406b2f 100644..100755
--- a/3rdparty/Sabre/CalDAV/ICSExportPlugin.php
+++ b/3rdparty/Sabre/CalDAV/ICSExportPlugin.php
diff --git a/3rdparty/Sabre/CalDAV/ICalendar.php b/3rdparty/Sabre/CalDAV/ICalendar.php
index 15d51ebcf79..15d51ebcf79 100644..100755
--- a/3rdparty/Sabre/CalDAV/ICalendar.php
+++ b/3rdparty/Sabre/CalDAV/ICalendar.php
diff --git a/3rdparty/Sabre/CalDAV/ICalendarObject.php b/3rdparty/Sabre/CalDAV/ICalendarObject.php
index 280f982a310..280f982a310 100644..100755
--- a/3rdparty/Sabre/CalDAV/ICalendarObject.php
+++ b/3rdparty/Sabre/CalDAV/ICalendarObject.php
diff --git a/3rdparty/Sabre/CalDAV/Plugin.php b/3rdparty/Sabre/CalDAV/Plugin.php
index d7d1d970518..5903968c003 100644..100755
--- a/3rdparty/Sabre/CalDAV/Plugin.php
+++ b/3rdparty/Sabre/CalDAV/Plugin.php
@@ -672,6 +672,42 @@ class Sabre_CalDAV_Plugin extends Sabre_DAV_ServerPlugin {
}
+ if ($vobj->name !== 'VCALENDAR') {
+ throw new Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support iCalendar objects.');
+ }
+
+ $foundType = null;
+ $foundUID = null;
+ foreach($vobj->getComponents() as $component) {
+ switch($component->name) {
+ case 'VTIMEZONE' :
+ continue 2;
+ case 'VEVENT' :
+ case 'VTODO' :
+ case 'VJOURNAL' :
+ if (is_null($foundType)) {
+ $foundType = $component->name;
+ if (!isset($component->UID)) {
+ throw new Sabre_DAV_Exception_BadRequest('Every ' . $component->name . ' component must have an UID');
+ }
+ $foundUID = (string)$component->UID;
+ } else {
+ if ($foundType !== $component->name) {
+ throw new Sabre_DAV_Exception_BadRequest('A calendar object must only contain 1 component. We found a ' . $component->name . ' as well as a ' . $foundType);
+ }
+ if ($foundUID !== (string)$component->UID) {
+ throw new Sabre_DAV_Exception_BadRequest('Every ' . $component->name . ' in this object must have identical UIDs');
+ }
+ }
+ break;
+ default :
+ throw new Sabre_DAV_Exception_BadRequest('You are not allowed to create components of type: ' . $component->name . ' here');
+
+ }
+ }
+ if (!$foundType)
+ throw new Sabre_DAV_Exception_BadRequest('iCalendar object must contain at least 1 of VEVENT, VTODO or VJOURNAL');
+
}
/**
diff --git a/3rdparty/Sabre/CalDAV/Principal/Collection.php b/3rdparty/Sabre/CalDAV/Principal/Collection.php
index abbefa5567a..abbefa5567a 100644..100755
--- a/3rdparty/Sabre/CalDAV/Principal/Collection.php
+++ b/3rdparty/Sabre/CalDAV/Principal/Collection.php
diff --git a/3rdparty/Sabre/CalDAV/Principal/ProxyRead.php b/3rdparty/Sabre/CalDAV/Principal/ProxyRead.php
index 4b3f035634a..4b3f035634a 100644..100755
--- a/3rdparty/Sabre/CalDAV/Principal/ProxyRead.php
+++ b/3rdparty/Sabre/CalDAV/Principal/ProxyRead.php
diff --git a/3rdparty/Sabre/CalDAV/Principal/ProxyWrite.php b/3rdparty/Sabre/CalDAV/Principal/ProxyWrite.php
index dd0c2e86edd..dd0c2e86edd 100644..100755
--- a/3rdparty/Sabre/CalDAV/Principal/ProxyWrite.php
+++ b/3rdparty/Sabre/CalDAV/Principal/ProxyWrite.php
diff --git a/3rdparty/Sabre/CalDAV/Principal/User.php b/3rdparty/Sabre/CalDAV/Principal/User.php
index 8453b877a73..8453b877a73 100644..100755
--- a/3rdparty/Sabre/CalDAV/Principal/User.php
+++ b/3rdparty/Sabre/CalDAV/Principal/User.php
diff --git a/3rdparty/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php b/3rdparty/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php
index 2ea078d7dac..2ea078d7dac 100644..100755
--- a/3rdparty/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php
+++ b/3rdparty/Sabre/CalDAV/Property/SupportedCalendarComponentSet.php
diff --git a/3rdparty/Sabre/CalDAV/Property/SupportedCalendarData.php b/3rdparty/Sabre/CalDAV/Property/SupportedCalendarData.php
index 1d848dd5cf6..1d848dd5cf6 100644..100755
--- a/3rdparty/Sabre/CalDAV/Property/SupportedCalendarData.php
+++ b/3rdparty/Sabre/CalDAV/Property/SupportedCalendarData.php
diff --git a/3rdparty/Sabre/CalDAV/Property/SupportedCollationSet.php b/3rdparty/Sabre/CalDAV/Property/SupportedCollationSet.php
index 24e84d4c17d..24e84d4c17d 100644..100755
--- a/3rdparty/Sabre/CalDAV/Property/SupportedCollationSet.php
+++ b/3rdparty/Sabre/CalDAV/Property/SupportedCollationSet.php
diff --git a/3rdparty/Sabre/CalDAV/Schedule/IMip.php b/3rdparty/Sabre/CalDAV/Schedule/IMip.php
index 37e75fcc4a7..37e75fcc4a7 100644..100755
--- a/3rdparty/Sabre/CalDAV/Schedule/IMip.php
+++ b/3rdparty/Sabre/CalDAV/Schedule/IMip.php
diff --git a/3rdparty/Sabre/CalDAV/Schedule/IOutbox.php b/3rdparty/Sabre/CalDAV/Schedule/IOutbox.php
index 46d77514bc0..46d77514bc0 100644..100755
--- a/3rdparty/Sabre/CalDAV/Schedule/IOutbox.php
+++ b/3rdparty/Sabre/CalDAV/Schedule/IOutbox.php
diff --git a/3rdparty/Sabre/CalDAV/Schedule/Outbox.php b/3rdparty/Sabre/CalDAV/Schedule/Outbox.php
index 014c37230d1..014c37230d1 100644..100755
--- a/3rdparty/Sabre/CalDAV/Schedule/Outbox.php
+++ b/3rdparty/Sabre/CalDAV/Schedule/Outbox.php
diff --git a/3rdparty/Sabre/CalDAV/Server.php b/3rdparty/Sabre/CalDAV/Server.php
index 325e3d80a7f..325e3d80a7f 100644..100755
--- a/3rdparty/Sabre/CalDAV/Server.php
+++ b/3rdparty/Sabre/CalDAV/Server.php
diff --git a/3rdparty/Sabre/CalDAV/UserCalendars.php b/3rdparty/Sabre/CalDAV/UserCalendars.php
index b8d3f0573fa..b8d3f0573fa 100644..100755
--- a/3rdparty/Sabre/CalDAV/UserCalendars.php
+++ b/3rdparty/Sabre/CalDAV/UserCalendars.php
diff --git a/3rdparty/Sabre/CalDAV/Version.php b/3rdparty/Sabre/CalDAV/Version.php
index 939e903c89f..289a0c83a34 100644..100755
--- a/3rdparty/Sabre/CalDAV/Version.php
+++ b/3rdparty/Sabre/CalDAV/Version.php
@@ -14,7 +14,7 @@ class Sabre_CalDAV_Version {
/**
* Full version number
*/
- const VERSION = '1.6.2';
+ const VERSION = '1.6.3';
/**
* Stability : alpha, beta, stable
diff --git a/3rdparty/Sabre/CalDAV/includes.php b/3rdparty/Sabre/CalDAV/includes.php
index 1ecb870a0e1..1ecb870a0e1 100644..100755
--- a/3rdparty/Sabre/CalDAV/includes.php
+++ b/3rdparty/Sabre/CalDAV/includes.php
diff --git a/3rdparty/Sabre/CardDAV/AddressBook.php b/3rdparty/Sabre/CardDAV/AddressBook.php
index 3b381e1eea3..12297175a85 100644..100755
--- a/3rdparty/Sabre/CardDAV/AddressBook.php
+++ b/3rdparty/Sabre/CardDAV/AddressBook.php
@@ -108,7 +108,9 @@ class Sabre_CardDAV_AddressBook extends Sabre_DAV_Collection implements Sabre_Ca
*/
public function createFile($name,$vcardData = null) {
- $vcardData = stream_get_contents($vcardData);
+ if (is_resource($vcardData)) {
+ $vcardData = stream_get_contents($vcardData);
+ }
// Converting to UTF-8, if needed
$vcardData = Sabre_DAV_StringUtil::ensureUTF8($vcardData);
diff --git a/3rdparty/Sabre/CardDAV/AddressBookQueryParser.php b/3rdparty/Sabre/CardDAV/AddressBookQueryParser.php
index 85a4963127b..46bb8ff18dd 100644..100755
--- a/3rdparty/Sabre/CardDAV/AddressBookQueryParser.php
+++ b/3rdparty/Sabre/CardDAV/AddressBookQueryParser.php
@@ -9,7 +9,7 @@
* @package Sabre
* @subpackage CardDAV
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_CardDAV_AddressBookQueryParser {
@@ -88,12 +88,22 @@ class Sabre_CardDAV_AddressBookQueryParser {
if (is_nan($limit)) $limit = null;
$filter = $this->xpath->query('/card:addressbook-query/card:filter');
- if ($filter->length !== 1) {
+
+ // According to the CardDAV spec there needs to be exactly 1 filter
+ // element. However, KDE 4.8.2 contains a bug that will encode 0 filter
+ // elements, so this is a workaround for that.
+ //
+ // See: https://bugs.kde.org/show_bug.cgi?id=300047
+ if ($filter->length === 0) {
+ $test = null;
+ $filter = null;
+ } elseif ($filter->length === 1) {
+ $filter = $filter->item(0);
+ $test = $this->xpath->evaluate('string(@test)', $filter);
+ } else {
throw new Sabre_DAV_Exception_BadRequest('Only one filter element is allowed');
}
- $filter = $filter->item(0);
- $test = $this->xpath->evaluate('string(@test)', $filter);
if (!$test) $test = self::TEST_ANYOF;
if ($test !== self::TEST_ANYOF && $test !== self::TEST_ALLOF) {
throw new Sabre_DAV_Exception_BadRequest('The test attribute must either hold "anyof" or "allof"');
diff --git a/3rdparty/Sabre/CardDAV/AddressBookRoot.php b/3rdparty/Sabre/CardDAV/AddressBookRoot.php
index 9d37b15f08e..9d37b15f08e 100644..100755
--- a/3rdparty/Sabre/CardDAV/AddressBookRoot.php
+++ b/3rdparty/Sabre/CardDAV/AddressBookRoot.php
diff --git a/3rdparty/Sabre/CardDAV/Backend/Abstract.php b/3rdparty/Sabre/CardDAV/Backend/Abstract.php
index e4806b7161f..e4806b7161f 100644..100755
--- a/3rdparty/Sabre/CardDAV/Backend/Abstract.php
+++ b/3rdparty/Sabre/CardDAV/Backend/Abstract.php
diff --git a/3rdparty/Sabre/CardDAV/Backend/PDO.php b/3rdparty/Sabre/CardDAV/Backend/PDO.php
index 413a77f3bcc..413a77f3bcc 100644..100755
--- a/3rdparty/Sabre/CardDAV/Backend/PDO.php
+++ b/3rdparty/Sabre/CardDAV/Backend/PDO.php
diff --git a/3rdparty/Sabre/CardDAV/Card.php b/3rdparty/Sabre/CardDAV/Card.php
index d7c66333837..d7c66333837 100644..100755
--- a/3rdparty/Sabre/CardDAV/Card.php
+++ b/3rdparty/Sabre/CardDAV/Card.php
diff --git a/3rdparty/Sabre/CardDAV/IAddressBook.php b/3rdparty/Sabre/CardDAV/IAddressBook.php
index 2bc275bcf74..2bc275bcf74 100644..100755
--- a/3rdparty/Sabre/CardDAV/IAddressBook.php
+++ b/3rdparty/Sabre/CardDAV/IAddressBook.php
diff --git a/3rdparty/Sabre/CardDAV/ICard.php b/3rdparty/Sabre/CardDAV/ICard.php
index a17299316c1..a17299316c1 100644..100755
--- a/3rdparty/Sabre/CardDAV/ICard.php
+++ b/3rdparty/Sabre/CardDAV/ICard.php
diff --git a/3rdparty/Sabre/CardDAV/IDirectory.php b/3rdparty/Sabre/CardDAV/IDirectory.php
index 22d4afeb24a..22d4afeb24a 100644..100755
--- a/3rdparty/Sabre/CardDAV/IDirectory.php
+++ b/3rdparty/Sabre/CardDAV/IDirectory.php
diff --git a/3rdparty/Sabre/CardDAV/Plugin.php b/3rdparty/Sabre/CardDAV/Plugin.php
index 9ebec243eb0..ca20e468497 100644..100755
--- a/3rdparty/Sabre/CardDAV/Plugin.php
+++ b/3rdparty/Sabre/CardDAV/Plugin.php
@@ -52,6 +52,8 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
$server->subscribeEvent('report', array($this,'report'));
$server->subscribeEvent('onHTMLActionsPanel', array($this,'htmlActionsPanel'));
$server->subscribeEvent('onBrowserPostAction', array($this,'browserPostAction'));
+ $server->subscribeEvent('beforeWriteContent', array($this, 'beforeWriteContent'));
+ $server->subscribeEvent('beforeCreateFile', array($this, 'beforeCreateFile'));
/* Namespaces */
$server->xmlNamespaces[self::NS_CARDDAV] = 'card';
@@ -284,6 +286,81 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
}
/**
+ * This method is triggered before a file gets updated with new content.
+ *
+ * This plugin uses this method to ensure that Card nodes receive valid
+ * vcard data.
+ *
+ * @param string $path
+ * @param Sabre_DAV_IFile $node
+ * @param resource $data
+ * @return void
+ */
+ public function beforeWriteContent($path, Sabre_DAV_IFile $node, &$data) {
+
+ if (!$node instanceof Sabre_CardDAV_ICard)
+ return;
+
+ $this->validateVCard($data);
+
+ }
+
+ /**
+ * This method is triggered before a new file is created.
+ *
+ * This plugin uses this method to ensure that Card nodes receive valid
+ * vcard data.
+ *
+ * @param string $path
+ * @param resource $data
+ * @param Sabre_DAV_ICollection $parentNode
+ * @return void
+ */
+ public function beforeCreateFile($path, &$data, Sabre_DAV_ICollection $parentNode) {
+
+ if (!$parentNode instanceof Sabre_CardDAV_IAddressBook)
+ return;
+
+ $this->validateVCard($data);
+
+ }
+
+ /**
+ * Checks if the submitted iCalendar data is in fact, valid.
+ *
+ * An exception is thrown if it's not.
+ *
+ * @param resource|string $data
+ * @return void
+ */
+ protected function validateVCard(&$data) {
+
+ // If it's a stream, we convert it to a string first.
+ if (is_resource($data)) {
+ $data = stream_get_contents($data);
+ }
+
+ // Converting the data to unicode, if needed.
+ $data = Sabre_DAV_StringUtil::ensureUTF8($data);
+
+ try {
+
+ $vobj = Sabre_VObject_Reader::read($data);
+
+ } catch (Sabre_VObject_ParseException $e) {
+
+ throw new Sabre_DAV_Exception_UnsupportedMediaType('This resource only supports valid vcard data. Parse error: ' . $e->getMessage());
+
+ }
+
+ if ($vobj->name !== 'VCARD') {
+ throw new Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support vcard objects.');
+ }
+
+ }
+
+
+ /**
* This function handles the addressbook-query REPORT
*
* This report is used by the client to filter an addressbook based on a
@@ -362,6 +439,8 @@ class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
$vcard = Sabre_VObject_Reader::read($vcardData);
+ if (!$filters) return true;
+
foreach($filters as $filter) {
$isDefined = isset($vcard->{$filter['name']});
diff --git a/3rdparty/Sabre/CardDAV/Property/SupportedAddressData.php b/3rdparty/Sabre/CardDAV/Property/SupportedAddressData.php
index 36d9306e7aa..36d9306e7aa 100644..100755
--- a/3rdparty/Sabre/CardDAV/Property/SupportedAddressData.php
+++ b/3rdparty/Sabre/CardDAV/Property/SupportedAddressData.php
diff --git a/3rdparty/Sabre/CardDAV/UserAddressBooks.php b/3rdparty/Sabre/CardDAV/UserAddressBooks.php
index 3f11fb11238..3f11fb11238 100644..100755
--- a/3rdparty/Sabre/CardDAV/UserAddressBooks.php
+++ b/3rdparty/Sabre/CardDAV/UserAddressBooks.php
diff --git a/3rdparty/Sabre/CardDAV/Version.php b/3rdparty/Sabre/CardDAV/Version.php
index 811b929e397..d0623f0d3e8 100644..100755
--- a/3rdparty/Sabre/CardDAV/Version.php
+++ b/3rdparty/Sabre/CardDAV/Version.php
@@ -16,7 +16,7 @@ class Sabre_CardDAV_Version {
/**
* Full version number
*/
- const VERSION = '1.6.1';
+ const VERSION = '1.6.3';
/**
* Stability : alpha, beta, stable
diff --git a/3rdparty/Sabre/CardDAV/includes.php b/3rdparty/Sabre/CardDAV/includes.php
index c3b8c04b077..c3b8c04b077 100644..100755
--- a/3rdparty/Sabre/CardDAV/includes.php
+++ b/3rdparty/Sabre/CardDAV/includes.php
diff --git a/3rdparty/Sabre/DAV/Auth/Backend/AbstractBasic.php b/3rdparty/Sabre/DAV/Auth/Backend/AbstractBasic.php
index 1e89b84f9a1..1e89b84f9a1 100644..100755
--- a/3rdparty/Sabre/DAV/Auth/Backend/AbstractBasic.php
+++ b/3rdparty/Sabre/DAV/Auth/Backend/AbstractBasic.php
diff --git a/3rdparty/Sabre/DAV/Auth/Backend/AbstractDigest.php b/3rdparty/Sabre/DAV/Auth/Backend/AbstractDigest.php
index 9833928b976..9833928b976 100644..100755
--- a/3rdparty/Sabre/DAV/Auth/Backend/AbstractDigest.php
+++ b/3rdparty/Sabre/DAV/Auth/Backend/AbstractDigest.php
diff --git a/3rdparty/Sabre/DAV/Auth/Backend/Apache.php b/3rdparty/Sabre/DAV/Auth/Backend/Apache.php
index d4294ea4d86..d4294ea4d86 100644..100755
--- a/3rdparty/Sabre/DAV/Auth/Backend/Apache.php
+++ b/3rdparty/Sabre/DAV/Auth/Backend/Apache.php
diff --git a/3rdparty/Sabre/DAV/Auth/Backend/File.php b/3rdparty/Sabre/DAV/Auth/Backend/File.php
index de308d64a67..de308d64a67 100644..100755
--- a/3rdparty/Sabre/DAV/Auth/Backend/File.php
+++ b/3rdparty/Sabre/DAV/Auth/Backend/File.php
diff --git a/3rdparty/Sabre/DAV/Auth/Backend/PDO.php b/3rdparty/Sabre/DAV/Auth/Backend/PDO.php
index eac18a23fbb..eac18a23fbb 100644..100755
--- a/3rdparty/Sabre/DAV/Auth/Backend/PDO.php
+++ b/3rdparty/Sabre/DAV/Auth/Backend/PDO.php
diff --git a/3rdparty/Sabre/DAV/Auth/IBackend.php b/3rdparty/Sabre/DAV/Auth/IBackend.php
index 5be5d1bc93d..5be5d1bc93d 100644..100755
--- a/3rdparty/Sabre/DAV/Auth/IBackend.php
+++ b/3rdparty/Sabre/DAV/Auth/IBackend.php
diff --git a/3rdparty/Sabre/DAV/Auth/Plugin.php b/3rdparty/Sabre/DAV/Auth/Plugin.php
index 55a4e391674..55a4e391674 100644..100755
--- a/3rdparty/Sabre/DAV/Auth/Plugin.php
+++ b/3rdparty/Sabre/DAV/Auth/Plugin.php
diff --git a/3rdparty/Sabre/DAV/Browser/GuessContentType.php b/3rdparty/Sabre/DAV/Browser/GuessContentType.php
index b6c00d461cb..b6c00d461cb 100644..100755
--- a/3rdparty/Sabre/DAV/Browser/GuessContentType.php
+++ b/3rdparty/Sabre/DAV/Browser/GuessContentType.php
diff --git a/3rdparty/Sabre/DAV/Browser/MapGetToPropFind.php b/3rdparty/Sabre/DAV/Browser/MapGetToPropFind.php
index 15884887641..15884887641 100644..100755
--- a/3rdparty/Sabre/DAV/Browser/MapGetToPropFind.php
+++ b/3rdparty/Sabre/DAV/Browser/MapGetToPropFind.php
diff --git a/3rdparty/Sabre/DAV/Browser/Plugin.php b/3rdparty/Sabre/DAV/Browser/Plugin.php
index 09bbdd2ae02..09bbdd2ae02 100644..100755
--- a/3rdparty/Sabre/DAV/Browser/Plugin.php
+++ b/3rdparty/Sabre/DAV/Browser/Plugin.php
diff --git a/3rdparty/Sabre/DAV/Browser/assets/favicon.ico b/3rdparty/Sabre/DAV/Browser/assets/favicon.ico
index 2b2c10a22cc..2b2c10a22cc 100644..100755
--- a/3rdparty/Sabre/DAV/Browser/assets/favicon.ico
+++ b/3rdparty/Sabre/DAV/Browser/assets/favicon.ico
Binary files differ
diff --git a/3rdparty/Sabre/DAV/Browser/assets/icons/addressbook.png b/3rdparty/Sabre/DAV/Browser/assets/icons/addressbook.png
index c9acc84172d..c9acc84172d 100644..100755
--- a/3rdparty/Sabre/DAV/Browser/assets/icons/addressbook.png
+++ b/3rdparty/Sabre/DAV/Browser/assets/icons/addressbook.png
Binary files differ
diff --git a/3rdparty/Sabre/DAV/Browser/assets/icons/calendar.png b/3rdparty/Sabre/DAV/Browser/assets/icons/calendar.png
index 3ecd6a800a0..3ecd6a800a0 100644..100755
--- a/3rdparty/Sabre/DAV/Browser/assets/icons/calendar.png
+++ b/3rdparty/Sabre/DAV/Browser/assets/icons/calendar.png
Binary files differ
diff --git a/3rdparty/Sabre/DAV/Browser/assets/icons/card.png b/3rdparty/Sabre/DAV/Browser/assets/icons/card.png
index 2ce954866d8..2ce954866d8 100644..100755
--- a/3rdparty/Sabre/DAV/Browser/assets/icons/card.png
+++ b/3rdparty/Sabre/DAV/Browser/assets/icons/card.png
Binary files differ
diff --git a/3rdparty/Sabre/DAV/Browser/assets/icons/collection.png b/3rdparty/Sabre/DAV/Browser/assets/icons/collection.png
index 156fa64fd50..156fa64fd50 100644..100755
--- a/3rdparty/Sabre/DAV/Browser/assets/icons/collection.png
+++ b/3rdparty/Sabre/DAV/Browser/assets/icons/collection.png
Binary files differ
diff --git a/3rdparty/Sabre/DAV/Browser/assets/icons/file.png b/3rdparty/Sabre/DAV/Browser/assets/icons/file.png
index 3b98551cec3..3b98551cec3 100644..100755
--- a/3rdparty/Sabre/DAV/Browser/assets/icons/file.png
+++ b/3rdparty/Sabre/DAV/Browser/assets/icons/file.png
Binary files differ
diff --git a/3rdparty/Sabre/DAV/Browser/assets/icons/parent.png b/3rdparty/Sabre/DAV/Browser/assets/icons/parent.png
index 156fa64fd50..156fa64fd50 100644..100755
--- a/3rdparty/Sabre/DAV/Browser/assets/icons/parent.png
+++ b/3rdparty/Sabre/DAV/Browser/assets/icons/parent.png
Binary files differ
diff --git a/3rdparty/Sabre/DAV/Browser/assets/icons/principal.png b/3rdparty/Sabre/DAV/Browser/assets/icons/principal.png
index f8988f828e6..f8988f828e6 100644..100755
--- a/3rdparty/Sabre/DAV/Browser/assets/icons/principal.png
+++ b/3rdparty/Sabre/DAV/Browser/assets/icons/principal.png
Binary files differ
diff --git a/3rdparty/Sabre/DAV/Client.php b/3rdparty/Sabre/DAV/Client.php
index 075e84caa1d..9a428765e90 100644..100755
--- a/3rdparty/Sabre/DAV/Client.php
+++ b/3rdparty/Sabre/DAV/Client.php
@@ -11,7 +11,7 @@
* @package Sabre
* @subpackage DAVClient
* @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
- * @author Evert Pot (http://www.rooftopsolutions.nl/)
+ * @author Evert Pot (http://www.rooftopsolutions.nl/)
* @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
*/
class Sabre_DAV_Client {
@@ -24,6 +24,28 @@ class Sabre_DAV_Client {
protected $proxy;
/**
+ * Basic authentication
+ */
+ const AUTH_BASIC = 1;
+
+ /**
+ * Digest authentication
+ */
+ const AUTH_DIGEST = 2;
+
+ /**
+ * The authentication type we're using.
+ *
+ * This is a bitmask of AUTH_BASIC and AUTH_DIGEST.
+ *
+ * If DIGEST is used, the client makes 1 extra request per request, to get
+ * the authentication tokens.
+ *
+ * @var int
+ */
+ protected $authType;
+
+ /**
* Constructor
*
* Settings are provided through the 'settings' argument. The following
@@ -46,16 +68,21 @@ class Sabre_DAV_Client {
'baseUri',
'userName',
'password',
- 'proxy'
+ 'proxy',
);
-
foreach($validSettings as $validSetting) {
if (isset($settings[$validSetting])) {
$this->$validSetting = $settings[$validSetting];
}
}
+ if (isset($settings['authType'])) {
+ $this->authType = $settings['authType'];
+ } else {
+ $this->authType = self::AUTH_BASIC | self::AUTH_DIGEST;
+ }
+
$this->propertyMap['{DAV:}resourcetype'] = 'Sabre_DAV_Property_ResourceType';
}
@@ -250,14 +277,9 @@ class Sabre_DAV_Client {
// Automatically follow redirects
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
- CURLOPT_SSL_VERIFYPEER => true,
- //CURLOPT_SSL_VERIFYPEER => false,
);
switch ($method) {
- case 'PUT':
- $curlSettings[CURLOPT_PUT] = true;
- break;
case 'HEAD' :
// do not read body with HEAD requests (this is neccessary because cURL does not ignore the body with HEAD
@@ -288,8 +310,15 @@ class Sabre_DAV_Client {
$curlSettings[CURLOPT_PROXY] = $this->proxy;
}
- if ($this->userName) {
- $curlSettings[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC | CURLAUTH_DIGEST;
+ if ($this->userName && $this->authType) {
+ $curlType = 0;
+ if ($this->authType & self::AUTH_BASIC) {
+ $curlType |= CURLAUTH_BASIC;
+ }
+ if ($this->authType & self::AUTH_DIGEST) {
+ $curlType |= CURLAUTH_DIGEST;
+ }
+ $curlSettings[CURLOPT_HTTPAUTH] = $curlType;
$curlSettings[CURLOPT_USERPWD] = $this->userName . ':' . $this->password;
}
diff --git a/3rdparty/Sabre/DAV/Collection.php b/3rdparty/Sabre/DAV/Collection.php
index 776c22531b2..776c22531b2 100644..100755
--- a/3rdparty/Sabre/DAV/Collection.php
+++ b/3rdparty/Sabre/DAV/Collection.php
diff --git a/3rdparty/Sabre/DAV/Directory.php b/3rdparty/Sabre/DAV/Directory.php
index 6db8febc02e..6db8febc02e 100644..100755
--- a/3rdparty/Sabre/DAV/Directory.php
+++ b/3rdparty/Sabre/DAV/Directory.php
diff --git a/3rdparty/Sabre/DAV/Exception.php b/3rdparty/Sabre/DAV/Exception.php
index a2cd6cf5820..a2cd6cf5820 100644..100755
--- a/3rdparty/Sabre/DAV/Exception.php
+++ b/3rdparty/Sabre/DAV/Exception.php
diff --git a/3rdparty/Sabre/DAV/Exception/BadRequest.php b/3rdparty/Sabre/DAV/Exception/BadRequest.php
index b198648a754..b198648a754 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/BadRequest.php
+++ b/3rdparty/Sabre/DAV/Exception/BadRequest.php
diff --git a/3rdparty/Sabre/DAV/Exception/Conflict.php b/3rdparty/Sabre/DAV/Exception/Conflict.php
index 6b0bd1fad73..6b0bd1fad73 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/Conflict.php
+++ b/3rdparty/Sabre/DAV/Exception/Conflict.php
diff --git a/3rdparty/Sabre/DAV/Exception/ConflictingLock.php b/3rdparty/Sabre/DAV/Exception/ConflictingLock.php
index 6121868e69e..6121868e69e 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/ConflictingLock.php
+++ b/3rdparty/Sabre/DAV/Exception/ConflictingLock.php
diff --git a/3rdparty/Sabre/DAV/Exception/FileNotFound.php b/3rdparty/Sabre/DAV/Exception/FileNotFound.php
index d76e400c93b..d76e400c93b 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/FileNotFound.php
+++ b/3rdparty/Sabre/DAV/Exception/FileNotFound.php
diff --git a/3rdparty/Sabre/DAV/Exception/Forbidden.php b/3rdparty/Sabre/DAV/Exception/Forbidden.php
index 20b1056e31b..20b1056e31b 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/Forbidden.php
+++ b/3rdparty/Sabre/DAV/Exception/Forbidden.php
diff --git a/3rdparty/Sabre/DAV/Exception/InsufficientStorage.php b/3rdparty/Sabre/DAV/Exception/InsufficientStorage.php
index 1a15089b0a3..1a15089b0a3 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/InsufficientStorage.php
+++ b/3rdparty/Sabre/DAV/Exception/InsufficientStorage.php
diff --git a/3rdparty/Sabre/DAV/Exception/InvalidResourceType.php b/3rdparty/Sabre/DAV/Exception/InvalidResourceType.php
index 2230f1d0811..2230f1d0811 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/InvalidResourceType.php
+++ b/3rdparty/Sabre/DAV/Exception/InvalidResourceType.php
diff --git a/3rdparty/Sabre/DAV/Exception/LockTokenMatchesRequestUri.php b/3rdparty/Sabre/DAV/Exception/LockTokenMatchesRequestUri.php
index 80ab7aff65a..80ab7aff65a 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/LockTokenMatchesRequestUri.php
+++ b/3rdparty/Sabre/DAV/Exception/LockTokenMatchesRequestUri.php
diff --git a/3rdparty/Sabre/DAV/Exception/Locked.php b/3rdparty/Sabre/DAV/Exception/Locked.php
index 976365ac1f8..976365ac1f8 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/Locked.php
+++ b/3rdparty/Sabre/DAV/Exception/Locked.php
diff --git a/3rdparty/Sabre/DAV/Exception/MethodNotAllowed.php b/3rdparty/Sabre/DAV/Exception/MethodNotAllowed.php
index 31875751505..31875751505 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/MethodNotAllowed.php
+++ b/3rdparty/Sabre/DAV/Exception/MethodNotAllowed.php
diff --git a/3rdparty/Sabre/DAV/Exception/NotAuthenticated.php b/3rdparty/Sabre/DAV/Exception/NotAuthenticated.php
index 87ca624429f..87ca624429f 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/NotAuthenticated.php
+++ b/3rdparty/Sabre/DAV/Exception/NotAuthenticated.php
diff --git a/3rdparty/Sabre/DAV/Exception/NotFound.php b/3rdparty/Sabre/DAV/Exception/NotFound.php
index 2b9da560d23..2b9da560d23 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/NotFound.php
+++ b/3rdparty/Sabre/DAV/Exception/NotFound.php
diff --git a/3rdparty/Sabre/DAV/Exception/NotImplemented.php b/3rdparty/Sabre/DAV/Exception/NotImplemented.php
index d017a19f559..d017a19f559 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/NotImplemented.php
+++ b/3rdparty/Sabre/DAV/Exception/NotImplemented.php
diff --git a/3rdparty/Sabre/DAV/Exception/PaymentRequired.php b/3rdparty/Sabre/DAV/Exception/PaymentRequired.php
index 4982f45a4b5..4982f45a4b5 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/PaymentRequired.php
+++ b/3rdparty/Sabre/DAV/Exception/PaymentRequired.php
diff --git a/3rdparty/Sabre/DAV/Exception/PreconditionFailed.php b/3rdparty/Sabre/DAV/Exception/PreconditionFailed.php
index 213e9c52317..213e9c52317 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/PreconditionFailed.php
+++ b/3rdparty/Sabre/DAV/Exception/PreconditionFailed.php
diff --git a/3rdparty/Sabre/DAV/Exception/ReportNotImplemented.php b/3rdparty/Sabre/DAV/Exception/ReportNotImplemented.php
index e86800f3038..e86800f3038 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/ReportNotImplemented.php
+++ b/3rdparty/Sabre/DAV/Exception/ReportNotImplemented.php
diff --git a/3rdparty/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php b/3rdparty/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php
index 29ee3654a7e..29ee3654a7e 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php
+++ b/3rdparty/Sabre/DAV/Exception/RequestedRangeNotSatisfiable.php
diff --git a/3rdparty/Sabre/DAV/Exception/UnsupportedMediaType.php b/3rdparty/Sabre/DAV/Exception/UnsupportedMediaType.php
index 9a4693b21a8..9a4693b21a8 100644..100755
--- a/3rdparty/Sabre/DAV/Exception/UnsupportedMediaType.php
+++ b/3rdparty/Sabre/DAV/Exception/UnsupportedMediaType.php
diff --git a/3rdparty/Sabre/DAV/FS/Directory.php b/3rdparty/Sabre/DAV/FS/Directory.php
index 3af2d755583..3af2d755583 100644..100755
--- a/3rdparty/Sabre/DAV/FS/Directory.php
+++ b/3rdparty/Sabre/DAV/FS/Directory.php
diff --git a/3rdparty/Sabre/DAV/FS/File.php b/3rdparty/Sabre/DAV/FS/File.php
index 6a8039fe303..6a8039fe303 100644..100755
--- a/3rdparty/Sabre/DAV/FS/File.php
+++ b/3rdparty/Sabre/DAV/FS/File.php
diff --git a/3rdparty/Sabre/DAV/FS/Node.php b/3rdparty/Sabre/DAV/FS/Node.php
index 1283e9d0fdc..1283e9d0fdc 100644..100755
--- a/3rdparty/Sabre/DAV/FS/Node.php
+++ b/3rdparty/Sabre/DAV/FS/Node.php
diff --git a/3rdparty/Sabre/DAV/FSExt/Directory.php b/3rdparty/Sabre/DAV/FSExt/Directory.php
index 540057183b3..540057183b3 100644..100755
--- a/3rdparty/Sabre/DAV/FSExt/Directory.php
+++ b/3rdparty/Sabre/DAV/FSExt/Directory.php
diff --git a/3rdparty/Sabre/DAV/FSExt/File.php b/3rdparty/Sabre/DAV/FSExt/File.php
index b93ce5aee21..b93ce5aee21 100644..100755
--- a/3rdparty/Sabre/DAV/FSExt/File.php
+++ b/3rdparty/Sabre/DAV/FSExt/File.php
diff --git a/3rdparty/Sabre/DAV/FSExt/Node.php b/3rdparty/Sabre/DAV/FSExt/Node.php
index 68ca06beb7e..68ca06beb7e 100644..100755
--- a/3rdparty/Sabre/DAV/FSExt/Node.php
+++ b/3rdparty/Sabre/DAV/FSExt/Node.php
diff --git a/3rdparty/Sabre/DAV/File.php b/3rdparty/Sabre/DAV/File.php
index 3126bd8d364..3126bd8d364 100644..100755
--- a/3rdparty/Sabre/DAV/File.php
+++ b/3rdparty/Sabre/DAV/File.php
diff --git a/3rdparty/Sabre/DAV/ICollection.php b/3rdparty/Sabre/DAV/ICollection.php
index 4626038a66e..4626038a66e 100644..100755
--- a/3rdparty/Sabre/DAV/ICollection.php
+++ b/3rdparty/Sabre/DAV/ICollection.php
diff --git a/3rdparty/Sabre/DAV/IExtendedCollection.php b/3rdparty/Sabre/DAV/IExtendedCollection.php
index 6ec345f9a62..6ec345f9a62 100644..100755
--- a/3rdparty/Sabre/DAV/IExtendedCollection.php
+++ b/3rdparty/Sabre/DAV/IExtendedCollection.php
diff --git a/3rdparty/Sabre/DAV/IFile.php b/3rdparty/Sabre/DAV/IFile.php
index 478f822ae71..478f822ae71 100644..100755
--- a/3rdparty/Sabre/DAV/IFile.php
+++ b/3rdparty/Sabre/DAV/IFile.php
diff --git a/3rdparty/Sabre/DAV/INode.php b/3rdparty/Sabre/DAV/INode.php
index c57d3923105..c57d3923105 100644..100755
--- a/3rdparty/Sabre/DAV/INode.php
+++ b/3rdparty/Sabre/DAV/INode.php
diff --git a/3rdparty/Sabre/DAV/IProperties.php b/3rdparty/Sabre/DAV/IProperties.php
index 38eaab16dad..38eaab16dad 100644..100755
--- a/3rdparty/Sabre/DAV/IProperties.php
+++ b/3rdparty/Sabre/DAV/IProperties.php
diff --git a/3rdparty/Sabre/DAV/IQuota.php b/3rdparty/Sabre/DAV/IQuota.php
index 3fe4c4eced4..3fe4c4eced4 100644..100755
--- a/3rdparty/Sabre/DAV/IQuota.php
+++ b/3rdparty/Sabre/DAV/IQuota.php
diff --git a/3rdparty/Sabre/DAV/Locks/Backend/Abstract.php b/3rdparty/Sabre/DAV/Locks/Backend/Abstract.php
index 127e643a2b9..127e643a2b9 100644..100755
--- a/3rdparty/Sabre/DAV/Locks/Backend/Abstract.php
+++ b/3rdparty/Sabre/DAV/Locks/Backend/Abstract.php
diff --git a/3rdparty/Sabre/DAV/Locks/Backend/FS.php b/3rdparty/Sabre/DAV/Locks/Backend/FS.php
index 02cab87fc82..02cab87fc82 100644..100755
--- a/3rdparty/Sabre/DAV/Locks/Backend/FS.php
+++ b/3rdparty/Sabre/DAV/Locks/Backend/FS.php
diff --git a/3rdparty/Sabre/DAV/Locks/Backend/File.php b/3rdparty/Sabre/DAV/Locks/Backend/File.php
index c33f963514b..c33f963514b 100644..100755
--- a/3rdparty/Sabre/DAV/Locks/Backend/File.php
+++ b/3rdparty/Sabre/DAV/Locks/Backend/File.php
diff --git a/3rdparty/Sabre/DAV/Locks/Backend/PDO.php b/3rdparty/Sabre/DAV/Locks/Backend/PDO.php
index acce80638ec..acce80638ec 100644..100755
--- a/3rdparty/Sabre/DAV/Locks/Backend/PDO.php
+++ b/3rdparty/Sabre/DAV/Locks/Backend/PDO.php
diff --git a/3rdparty/Sabre/DAV/Locks/LockInfo.php b/3rdparty/Sabre/DAV/Locks/LockInfo.php
index 9df014a4281..9df014a4281 100644..100755
--- a/3rdparty/Sabre/DAV/Locks/LockInfo.php
+++ b/3rdparty/Sabre/DAV/Locks/LockInfo.php
diff --git a/3rdparty/Sabre/DAV/Locks/Plugin.php b/3rdparty/Sabre/DAV/Locks/Plugin.php
index fd956950b8a..fd956950b8a 100644..100755
--- a/3rdparty/Sabre/DAV/Locks/Plugin.php
+++ b/3rdparty/Sabre/DAV/Locks/Plugin.php
diff --git a/3rdparty/Sabre/DAV/Mount/Plugin.php b/3rdparty/Sabre/DAV/Mount/Plugin.php
index b37a90ae993..b37a90ae993 100644..100755
--- a/3rdparty/Sabre/DAV/Mount/Plugin.php
+++ b/3rdparty/Sabre/DAV/Mount/Plugin.php
diff --git a/3rdparty/Sabre/DAV/Node.php b/3rdparty/Sabre/DAV/Node.php
index 070b7176afd..070b7176afd 100644..100755
--- a/3rdparty/Sabre/DAV/Node.php
+++ b/3rdparty/Sabre/DAV/Node.php
diff --git a/3rdparty/Sabre/DAV/ObjectTree.php b/3rdparty/Sabre/DAV/ObjectTree.php
index bce51463900..bce51463900 100644..100755
--- a/3rdparty/Sabre/DAV/ObjectTree.php
+++ b/3rdparty/Sabre/DAV/ObjectTree.php
diff --git a/3rdparty/Sabre/DAV/Property.php b/3rdparty/Sabre/DAV/Property.php
index 1cfada3236c..1cfada3236c 100644..100755
--- a/3rdparty/Sabre/DAV/Property.php
+++ b/3rdparty/Sabre/DAV/Property.php
diff --git a/3rdparty/Sabre/DAV/Property/GetLastModified.php b/3rdparty/Sabre/DAV/Property/GetLastModified.php
index bd63f573140..bd63f573140 100644..100755
--- a/3rdparty/Sabre/DAV/Property/GetLastModified.php
+++ b/3rdparty/Sabre/DAV/Property/GetLastModified.php
diff --git a/3rdparty/Sabre/DAV/Property/Href.php b/3rdparty/Sabre/DAV/Property/Href.php
index dac564f24d7..dac564f24d7 100644..100755
--- a/3rdparty/Sabre/DAV/Property/Href.php
+++ b/3rdparty/Sabre/DAV/Property/Href.php
diff --git a/3rdparty/Sabre/DAV/Property/HrefList.php b/3rdparty/Sabre/DAV/Property/HrefList.php
index 7a52272e885..7a52272e885 100644..100755
--- a/3rdparty/Sabre/DAV/Property/HrefList.php
+++ b/3rdparty/Sabre/DAV/Property/HrefList.php
diff --git a/3rdparty/Sabre/DAV/Property/IHref.php b/3rdparty/Sabre/DAV/Property/IHref.php
index 5c0409064cb..5c0409064cb 100644..100755
--- a/3rdparty/Sabre/DAV/Property/IHref.php
+++ b/3rdparty/Sabre/DAV/Property/IHref.php
diff --git a/3rdparty/Sabre/DAV/Property/LockDiscovery.php b/3rdparty/Sabre/DAV/Property/LockDiscovery.php
index 2ded5649a44..2ded5649a44 100644..100755
--- a/3rdparty/Sabre/DAV/Property/LockDiscovery.php
+++ b/3rdparty/Sabre/DAV/Property/LockDiscovery.php
diff --git a/3rdparty/Sabre/DAV/Property/ResourceType.php b/3rdparty/Sabre/DAV/Property/ResourceType.php
index f6269611e54..f6269611e54 100644..100755
--- a/3rdparty/Sabre/DAV/Property/ResourceType.php
+++ b/3rdparty/Sabre/DAV/Property/ResourceType.php
diff --git a/3rdparty/Sabre/DAV/Property/Response.php b/3rdparty/Sabre/DAV/Property/Response.php
index 88afbcfb26d..88afbcfb26d 100644..100755
--- a/3rdparty/Sabre/DAV/Property/Response.php
+++ b/3rdparty/Sabre/DAV/Property/Response.php
diff --git a/3rdparty/Sabre/DAV/Property/ResponseList.php b/3rdparty/Sabre/DAV/Property/ResponseList.php
index cae923afbf9..cae923afbf9 100644..100755
--- a/3rdparty/Sabre/DAV/Property/ResponseList.php
+++ b/3rdparty/Sabre/DAV/Property/ResponseList.php
diff --git a/3rdparty/Sabre/DAV/Property/SupportedLock.php b/3rdparty/Sabre/DAV/Property/SupportedLock.php
index 4e3aaf23a1a..4e3aaf23a1a 100644..100755
--- a/3rdparty/Sabre/DAV/Property/SupportedLock.php
+++ b/3rdparty/Sabre/DAV/Property/SupportedLock.php
diff --git a/3rdparty/Sabre/DAV/Property/SupportedReportSet.php b/3rdparty/Sabre/DAV/Property/SupportedReportSet.php
index e62699f3b5a..e62699f3b5a 100644..100755
--- a/3rdparty/Sabre/DAV/Property/SupportedReportSet.php
+++ b/3rdparty/Sabre/DAV/Property/SupportedReportSet.php
diff --git a/3rdparty/Sabre/DAV/Server.php b/3rdparty/Sabre/DAV/Server.php
index 50b190e8fab..0dfac8b0c71 100644..100755
--- a/3rdparty/Sabre/DAV/Server.php
+++ b/3rdparty/Sabre/DAV/Server.php
@@ -215,7 +215,7 @@ class Sabre_DAV_Server {
$DOM->appendChild($error);
$error->appendChild($DOM->createElement('s:exception',get_class($e)));
- $error->appendChild($DOM->createElement('s:message',htmlentities($e->getMessage())));
+ $error->appendChild($DOM->createElement('s:message',$e->getMessage()));
if ($this->debugExceptions) {
$error->appendChild($DOM->createElement('s:file',$e->getFile()));
$error->appendChild($DOM->createElement('s:line',$e->getLine()));
@@ -1784,7 +1784,14 @@ class Sabre_DAV_Server {
$etag = $node->getETag();
if ($etag===$ifMatchItem) {
$haveMatch = true;
+ } else {
+ // Evolution has a bug where it sometimes prepends the "
+ // with a \. This is our workaround.
+ if (str_replace('\\"','"', $ifMatchItem) === $etag) {
+ $haveMatch = true;
+ }
}
+
}
if (!$haveMatch) {
throw new Sabre_DAV_Exception_PreconditionFailed('An If-Match header was specified, but none of the specified the ETags matched.','If-Match');
diff --git a/3rdparty/Sabre/DAV/ServerPlugin.php b/3rdparty/Sabre/DAV/ServerPlugin.php
index 131863d13fb..131863d13fb 100644..100755
--- a/3rdparty/Sabre/DAV/ServerPlugin.php
+++ b/3rdparty/Sabre/DAV/ServerPlugin.php
diff --git a/3rdparty/Sabre/DAV/SimpleCollection.php b/3rdparty/Sabre/DAV/SimpleCollection.php
index 4acf971caa5..4acf971caa5 100644..100755
--- a/3rdparty/Sabre/DAV/SimpleCollection.php
+++ b/3rdparty/Sabre/DAV/SimpleCollection.php
diff --git a/3rdparty/Sabre/DAV/SimpleDirectory.php b/3rdparty/Sabre/DAV/SimpleDirectory.php
index 621222ebc53..621222ebc53 100644..100755
--- a/3rdparty/Sabre/DAV/SimpleDirectory.php
+++ b/3rdparty/Sabre/DAV/SimpleDirectory.php
diff --git a/3rdparty/Sabre/DAV/SimpleFile.php b/3rdparty/Sabre/DAV/SimpleFile.php
index 58330d6861d..58330d6861d 100644..100755
--- a/3rdparty/Sabre/DAV/SimpleFile.php
+++ b/3rdparty/Sabre/DAV/SimpleFile.php
diff --git a/3rdparty/Sabre/DAV/StringUtil.php b/3rdparty/Sabre/DAV/StringUtil.php
index b126a94c825..b126a94c825 100644..100755
--- a/3rdparty/Sabre/DAV/StringUtil.php
+++ b/3rdparty/Sabre/DAV/StringUtil.php
diff --git a/3rdparty/Sabre/DAV/TemporaryFileFilterPlugin.php b/3rdparty/Sabre/DAV/TemporaryFileFilterPlugin.php
index 36096e67775..36096e67775 100644..100755
--- a/3rdparty/Sabre/DAV/TemporaryFileFilterPlugin.php
+++ b/3rdparty/Sabre/DAV/TemporaryFileFilterPlugin.php
diff --git a/3rdparty/Sabre/DAV/Tree.php b/3rdparty/Sabre/DAV/Tree.php
index 50216394155..50216394155 100644..100755
--- a/3rdparty/Sabre/DAV/Tree.php
+++ b/3rdparty/Sabre/DAV/Tree.php
diff --git a/3rdparty/Sabre/DAV/Tree/Filesystem.php b/3rdparty/Sabre/DAV/Tree/Filesystem.php
index 85a9ee317be..40580ae366f 100644..100755
--- a/3rdparty/Sabre/DAV/Tree/Filesystem.php
+++ b/3rdparty/Sabre/DAV/Tree/Filesystem.php
@@ -42,9 +42,9 @@ class Sabre_DAV_Tree_Filesystem extends Sabre_DAV_Tree {
$realPath = $this->getRealPath($path);
if (!file_exists($realPath)) throw new Sabre_DAV_Exception_NotFound('File at location ' . $realPath . ' not found');
if (is_dir($realPath)) {
- return new Sabre_DAV_FS_Directory($path);
+ return new Sabre_DAV_FS_Directory($realPath);
} else {
- return new Sabre_DAV_FS_File($path);
+ return new Sabre_DAV_FS_File($realPath);
}
}
diff --git a/3rdparty/Sabre/DAV/URLUtil.php b/3rdparty/Sabre/DAV/URLUtil.php
index 794665a44f6..794665a44f6 100644..100755
--- a/3rdparty/Sabre/DAV/URLUtil.php
+++ b/3rdparty/Sabre/DAV/URLUtil.php
diff --git a/3rdparty/Sabre/DAV/UUIDUtil.php b/3rdparty/Sabre/DAV/UUIDUtil.php
index f0eebe598e5..f0eebe598e5 100644..100755
--- a/3rdparty/Sabre/DAV/UUIDUtil.php
+++ b/3rdparty/Sabre/DAV/UUIDUtil.php
diff --git a/3rdparty/Sabre/DAV/Version.php b/3rdparty/Sabre/DAV/Version.php
index 5e5d15e4039..40cfe81b34f 100644..100755
--- a/3rdparty/Sabre/DAV/Version.php
+++ b/3rdparty/Sabre/DAV/Version.php
@@ -14,7 +14,7 @@ class Sabre_DAV_Version {
/**
* Full version number
*/
- const VERSION = '1.6.2';
+ const VERSION = '1.6.3';
/**
* Stability : alpha, beta, stable
diff --git a/3rdparty/Sabre/DAV/XMLUtil.php b/3rdparty/Sabre/DAV/XMLUtil.php
index 60eff3b159a..60eff3b159a 100644..100755
--- a/3rdparty/Sabre/DAV/XMLUtil.php
+++ b/3rdparty/Sabre/DAV/XMLUtil.php
diff --git a/3rdparty/Sabre/DAV/includes.php b/3rdparty/Sabre/DAV/includes.php
index 6a4890677ea..6a4890677ea 100644..100755
--- a/3rdparty/Sabre/DAV/includes.php
+++ b/3rdparty/Sabre/DAV/includes.php
diff --git a/3rdparty/Sabre/DAVACL/AbstractPrincipalCollection.php b/3rdparty/Sabre/DAVACL/AbstractPrincipalCollection.php
index e05b7749805..e05b7749805 100644..100755
--- a/3rdparty/Sabre/DAVACL/AbstractPrincipalCollection.php
+++ b/3rdparty/Sabre/DAVACL/AbstractPrincipalCollection.php
diff --git a/3rdparty/Sabre/DAVACL/Exception/AceConflict.php b/3rdparty/Sabre/DAVACL/Exception/AceConflict.php
index 4b9f93b0036..4b9f93b0036 100644..100755
--- a/3rdparty/Sabre/DAVACL/Exception/AceConflict.php
+++ b/3rdparty/Sabre/DAVACL/Exception/AceConflict.php
diff --git a/3rdparty/Sabre/DAVACL/Exception/NeedPrivileges.php b/3rdparty/Sabre/DAVACL/Exception/NeedPrivileges.php
index 9b055dd9709..9b055dd9709 100644..100755
--- a/3rdparty/Sabre/DAVACL/Exception/NeedPrivileges.php
+++ b/3rdparty/Sabre/DAVACL/Exception/NeedPrivileges.php
diff --git a/3rdparty/Sabre/DAVACL/Exception/NoAbstract.php b/3rdparty/Sabre/DAVACL/Exception/NoAbstract.php
index f44e3e32281..f44e3e32281 100644..100755
--- a/3rdparty/Sabre/DAVACL/Exception/NoAbstract.php
+++ b/3rdparty/Sabre/DAVACL/Exception/NoAbstract.php
diff --git a/3rdparty/Sabre/DAVACL/Exception/NotRecognizedPrincipal.php b/3rdparty/Sabre/DAVACL/Exception/NotRecognizedPrincipal.php
index 8d1e38ca1b4..8d1e38ca1b4 100644..100755
--- a/3rdparty/Sabre/DAVACL/Exception/NotRecognizedPrincipal.php
+++ b/3rdparty/Sabre/DAVACL/Exception/NotRecognizedPrincipal.php
diff --git a/3rdparty/Sabre/DAVACL/Exception/NotSupportedPrivilege.php b/3rdparty/Sabre/DAVACL/Exception/NotSupportedPrivilege.php
index 3b5d012d7fa..3b5d012d7fa 100644..100755
--- a/3rdparty/Sabre/DAVACL/Exception/NotSupportedPrivilege.php
+++ b/3rdparty/Sabre/DAVACL/Exception/NotSupportedPrivilege.php
diff --git a/3rdparty/Sabre/DAVACL/IACL.php b/3rdparty/Sabre/DAVACL/IACL.php
index 003e6993483..003e6993483 100644..100755
--- a/3rdparty/Sabre/DAVACL/IACL.php
+++ b/3rdparty/Sabre/DAVACL/IACL.php
diff --git a/3rdparty/Sabre/DAVACL/IPrincipal.php b/3rdparty/Sabre/DAVACL/IPrincipal.php
index fc7605bf625..fc7605bf625 100644..100755
--- a/3rdparty/Sabre/DAVACL/IPrincipal.php
+++ b/3rdparty/Sabre/DAVACL/IPrincipal.php
diff --git a/3rdparty/Sabre/DAVACL/IPrincipalBackend.php b/3rdparty/Sabre/DAVACL/IPrincipalBackend.php
index e798bf890c0..e798bf890c0 100644..100755
--- a/3rdparty/Sabre/DAVACL/IPrincipalBackend.php
+++ b/3rdparty/Sabre/DAVACL/IPrincipalBackend.php
diff --git a/3rdparty/Sabre/DAVACL/Plugin.php b/3rdparty/Sabre/DAVACL/Plugin.php
index 5c828c6d97b..5c828c6d97b 100644..100755
--- a/3rdparty/Sabre/DAVACL/Plugin.php
+++ b/3rdparty/Sabre/DAVACL/Plugin.php
diff --git a/3rdparty/Sabre/DAVACL/Principal.php b/3rdparty/Sabre/DAVACL/Principal.php
index 51c6658afd6..51c6658afd6 100644..100755
--- a/3rdparty/Sabre/DAVACL/Principal.php
+++ b/3rdparty/Sabre/DAVACL/Principal.php
diff --git a/3rdparty/Sabre/DAVACL/PrincipalBackend/PDO.php b/3rdparty/Sabre/DAVACL/PrincipalBackend/PDO.php
index a76b4a9d727..a76b4a9d727 100644..100755
--- a/3rdparty/Sabre/DAVACL/PrincipalBackend/PDO.php
+++ b/3rdparty/Sabre/DAVACL/PrincipalBackend/PDO.php
diff --git a/3rdparty/Sabre/DAVACL/PrincipalCollection.php b/3rdparty/Sabre/DAVACL/PrincipalCollection.php
index c3e4cb83f23..c3e4cb83f23 100644..100755
--- a/3rdparty/Sabre/DAVACL/PrincipalCollection.php
+++ b/3rdparty/Sabre/DAVACL/PrincipalCollection.php
diff --git a/3rdparty/Sabre/DAVACL/Property/Acl.php b/3rdparty/Sabre/DAVACL/Property/Acl.php
index 05e1a690b3c..05e1a690b3c 100644..100755
--- a/3rdparty/Sabre/DAVACL/Property/Acl.php
+++ b/3rdparty/Sabre/DAVACL/Property/Acl.php
diff --git a/3rdparty/Sabre/DAVACL/Property/AclRestrictions.php b/3rdparty/Sabre/DAVACL/Property/AclRestrictions.php
index a8b054956dd..a8b054956dd 100644..100755
--- a/3rdparty/Sabre/DAVACL/Property/AclRestrictions.php
+++ b/3rdparty/Sabre/DAVACL/Property/AclRestrictions.php
diff --git a/3rdparty/Sabre/DAVACL/Property/CurrentUserPrivilegeSet.php b/3rdparty/Sabre/DAVACL/Property/CurrentUserPrivilegeSet.php
index 94a29640615..94a29640615 100644..100755
--- a/3rdparty/Sabre/DAVACL/Property/CurrentUserPrivilegeSet.php
+++ b/3rdparty/Sabre/DAVACL/Property/CurrentUserPrivilegeSet.php
diff --git a/3rdparty/Sabre/DAVACL/Property/Principal.php b/3rdparty/Sabre/DAVACL/Property/Principal.php
index c36328a58e0..c36328a58e0 100644..100755
--- a/3rdparty/Sabre/DAVACL/Property/Principal.php
+++ b/3rdparty/Sabre/DAVACL/Property/Principal.php
diff --git a/3rdparty/Sabre/DAVACL/Property/SupportedPrivilegeSet.php b/3rdparty/Sabre/DAVACL/Property/SupportedPrivilegeSet.php
index 276d57ae093..276d57ae093 100644..100755
--- a/3rdparty/Sabre/DAVACL/Property/SupportedPrivilegeSet.php
+++ b/3rdparty/Sabre/DAVACL/Property/SupportedPrivilegeSet.php
diff --git a/3rdparty/Sabre/DAVACL/Version.php b/3rdparty/Sabre/DAVACL/Version.php
index 9950f748741..9950f748741 100644..100755
--- a/3rdparty/Sabre/DAVACL/Version.php
+++ b/3rdparty/Sabre/DAVACL/Version.php
diff --git a/3rdparty/Sabre/DAVACL/includes.php b/3rdparty/Sabre/DAVACL/includes.php
index 28fa3eed225..28fa3eed225 100644..100755
--- a/3rdparty/Sabre/DAVACL/includes.php
+++ b/3rdparty/Sabre/DAVACL/includes.php
diff --git a/3rdparty/Sabre/HTTP/AWSAuth.php b/3rdparty/Sabre/HTTP/AWSAuth.php
index fb8245c8cbf..fb8245c8cbf 100644..100755
--- a/3rdparty/Sabre/HTTP/AWSAuth.php
+++ b/3rdparty/Sabre/HTTP/AWSAuth.php
diff --git a/3rdparty/Sabre/HTTP/AbstractAuth.php b/3rdparty/Sabre/HTTP/AbstractAuth.php
index 3bccabcd1c1..3bccabcd1c1 100644..100755
--- a/3rdparty/Sabre/HTTP/AbstractAuth.php
+++ b/3rdparty/Sabre/HTTP/AbstractAuth.php
diff --git a/3rdparty/Sabre/HTTP/BasicAuth.php b/3rdparty/Sabre/HTTP/BasicAuth.php
index a747cc6a31b..a747cc6a31b 100644..100755
--- a/3rdparty/Sabre/HTTP/BasicAuth.php
+++ b/3rdparty/Sabre/HTTP/BasicAuth.php
diff --git a/3rdparty/Sabre/HTTP/DigestAuth.php b/3rdparty/Sabre/HTTP/DigestAuth.php
index ee7f05c08ed..ee7f05c08ed 100644..100755
--- a/3rdparty/Sabre/HTTP/DigestAuth.php
+++ b/3rdparty/Sabre/HTTP/DigestAuth.php
diff --git a/3rdparty/Sabre/HTTP/Request.php b/3rdparty/Sabre/HTTP/Request.php
index 4746ef77704..4746ef77704 100644..100755
--- a/3rdparty/Sabre/HTTP/Request.php
+++ b/3rdparty/Sabre/HTTP/Request.php
diff --git a/3rdparty/Sabre/HTTP/Response.php b/3rdparty/Sabre/HTTP/Response.php
index ffe9bda2082..ffe9bda2082 100644..100755
--- a/3rdparty/Sabre/HTTP/Response.php
+++ b/3rdparty/Sabre/HTTP/Response.php
diff --git a/3rdparty/Sabre/HTTP/Util.php b/3rdparty/Sabre/HTTP/Util.php
index 67bdd489e1e..67bdd489e1e 100644..100755
--- a/3rdparty/Sabre/HTTP/Util.php
+++ b/3rdparty/Sabre/HTTP/Util.php
diff --git a/3rdparty/Sabre/HTTP/Version.php b/3rdparty/Sabre/HTTP/Version.php
index 23dc7f8a7a1..23dc7f8a7a1 100644..100755
--- a/3rdparty/Sabre/HTTP/Version.php
+++ b/3rdparty/Sabre/HTTP/Version.php
diff --git a/3rdparty/Sabre/HTTP/includes.php b/3rdparty/Sabre/HTTP/includes.php
index 9d34bf3a8be..9d34bf3a8be 100644..100755
--- a/3rdparty/Sabre/HTTP/includes.php
+++ b/3rdparty/Sabre/HTTP/includes.php
diff --git a/3rdparty/Sabre/VObject/Component.php b/3rdparty/Sabre/VObject/Component.php
index b78a26133fa..b78a26133fa 100644..100755
--- a/3rdparty/Sabre/VObject/Component.php
+++ b/3rdparty/Sabre/VObject/Component.php
diff --git a/3rdparty/Sabre/VObject/Component/VAlarm.php b/3rdparty/Sabre/VObject/Component/VAlarm.php
index ebb4a9b18f6..ebb4a9b18f6 100644..100755
--- a/3rdparty/Sabre/VObject/Component/VAlarm.php
+++ b/3rdparty/Sabre/VObject/Component/VAlarm.php
diff --git a/3rdparty/Sabre/VObject/Component/VCalendar.php b/3rdparty/Sabre/VObject/Component/VCalendar.php
index f3be29afdbb..f3be29afdbb 100644..100755
--- a/3rdparty/Sabre/VObject/Component/VCalendar.php
+++ b/3rdparty/Sabre/VObject/Component/VCalendar.php
diff --git a/3rdparty/Sabre/VObject/Component/VEvent.php b/3rdparty/Sabre/VObject/Component/VEvent.php
index 4cc1e36d7d6..4cc1e36d7d6 100644..100755
--- a/3rdparty/Sabre/VObject/Component/VEvent.php
+++ b/3rdparty/Sabre/VObject/Component/VEvent.php
diff --git a/3rdparty/Sabre/VObject/Component/VJournal.php b/3rdparty/Sabre/VObject/Component/VJournal.php
index 22b3ec921e5..22b3ec921e5 100644..100755
--- a/3rdparty/Sabre/VObject/Component/VJournal.php
+++ b/3rdparty/Sabre/VObject/Component/VJournal.php
diff --git a/3rdparty/Sabre/VObject/Component/VTodo.php b/3rdparty/Sabre/VObject/Component/VTodo.php
index 79d06298d7f..79d06298d7f 100644..100755
--- a/3rdparty/Sabre/VObject/Component/VTodo.php
+++ b/3rdparty/Sabre/VObject/Component/VTodo.php
diff --git a/3rdparty/Sabre/VObject/DateTimeParser.php b/3rdparty/Sabre/VObject/DateTimeParser.php
index 1e2d54ef3a9..23a4bb69916 100644..100755
--- a/3rdparty/Sabre/VObject/DateTimeParser.php
+++ b/3rdparty/Sabre/VObject/DateTimeParser.php
@@ -125,6 +125,9 @@ class Sabre_VObject_DateTimeParser {
}
+ if ($duration==='P') {
+ $duration = 'PT0S';
+ }
$iv = new DateInterval($duration);
if ($invert) $iv->invert = true;
@@ -150,6 +153,7 @@ class Sabre_VObject_DateTimeParser {
}
$newDur = ($matches['plusminus']==='-'?'-':'+') . trim($newDur);
+ if ($newDur === '+') { $newDur = '+0 seconds'; };
return $newDur;
}
diff --git a/3rdparty/Sabre/VObject/Element.php b/3rdparty/Sabre/VObject/Element.php
index e20ff0b353c..e20ff0b353c 100644..100755
--- a/3rdparty/Sabre/VObject/Element.php
+++ b/3rdparty/Sabre/VObject/Element.php
diff --git a/3rdparty/Sabre/VObject/Element/DateTime.php b/3rdparty/Sabre/VObject/Element/DateTime.php
index 5e5eb7ab6f2..5e5eb7ab6f2 100644..100755
--- a/3rdparty/Sabre/VObject/Element/DateTime.php
+++ b/3rdparty/Sabre/VObject/Element/DateTime.php
diff --git a/3rdparty/Sabre/VObject/Element/MultiDateTime.php b/3rdparty/Sabre/VObject/Element/MultiDateTime.php
index 8a12ced94a8..8a12ced94a8 100644..100755
--- a/3rdparty/Sabre/VObject/Element/MultiDateTime.php
+++ b/3rdparty/Sabre/VObject/Element/MultiDateTime.php
diff --git a/3rdparty/Sabre/VObject/ElementList.php b/3rdparty/Sabre/VObject/ElementList.php
index 7e508db20f0..7e508db20f0 100644..100755
--- a/3rdparty/Sabre/VObject/ElementList.php
+++ b/3rdparty/Sabre/VObject/ElementList.php
diff --git a/3rdparty/Sabre/VObject/FreeBusyGenerator.php b/3rdparty/Sabre/VObject/FreeBusyGenerator.php
index 1c96a64a004..1c96a64a004 100644..100755
--- a/3rdparty/Sabre/VObject/FreeBusyGenerator.php
+++ b/3rdparty/Sabre/VObject/FreeBusyGenerator.php
diff --git a/3rdparty/Sabre/VObject/Node.php b/3rdparty/Sabre/VObject/Node.php
index d89e01b56c6..d89e01b56c6 100644..100755
--- a/3rdparty/Sabre/VObject/Node.php
+++ b/3rdparty/Sabre/VObject/Node.php
diff --git a/3rdparty/Sabre/VObject/Parameter.php b/3rdparty/Sabre/VObject/Parameter.php
index 2e39af5f78a..2e39af5f78a 100644..100755
--- a/3rdparty/Sabre/VObject/Parameter.php
+++ b/3rdparty/Sabre/VObject/Parameter.php
diff --git a/3rdparty/Sabre/VObject/ParseException.php b/3rdparty/Sabre/VObject/ParseException.php
index 1b5e95bf16e..1b5e95bf16e 100644..100755
--- a/3rdparty/Sabre/VObject/ParseException.php
+++ b/3rdparty/Sabre/VObject/ParseException.php
diff --git a/3rdparty/Sabre/VObject/Property.php b/3rdparty/Sabre/VObject/Property.php
index ce74fe3865b..ce74fe3865b 100644..100755
--- a/3rdparty/Sabre/VObject/Property.php
+++ b/3rdparty/Sabre/VObject/Property.php
diff --git a/3rdparty/Sabre/VObject/Property/DateTime.php b/3rdparty/Sabre/VObject/Property/DateTime.php
index fe2372caa81..fe2372caa81 100644..100755
--- a/3rdparty/Sabre/VObject/Property/DateTime.php
+++ b/3rdparty/Sabre/VObject/Property/DateTime.php
diff --git a/3rdparty/Sabre/VObject/Property/MultiDateTime.php b/3rdparty/Sabre/VObject/Property/MultiDateTime.php
index ae53ab6a617..ae53ab6a617 100644..100755
--- a/3rdparty/Sabre/VObject/Property/MultiDateTime.php
+++ b/3rdparty/Sabre/VObject/Property/MultiDateTime.php
diff --git a/3rdparty/Sabre/VObject/Reader.php b/3rdparty/Sabre/VObject/Reader.php
index eea73fa3dce..eea73fa3dce 100644..100755
--- a/3rdparty/Sabre/VObject/Reader.php
+++ b/3rdparty/Sabre/VObject/Reader.php
diff --git a/3rdparty/Sabre/VObject/RecurrenceIterator.php b/3rdparty/Sabre/VObject/RecurrenceIterator.php
index 833aa091ab7..833aa091ab7 100644..100755
--- a/3rdparty/Sabre/VObject/RecurrenceIterator.php
+++ b/3rdparty/Sabre/VObject/RecurrenceIterator.php
diff --git a/3rdparty/Sabre/VObject/Version.php b/3rdparty/Sabre/VObject/Version.php
index 00110febc07..2617c7b129d 100644..100755
--- a/3rdparty/Sabre/VObject/Version.php
+++ b/3rdparty/Sabre/VObject/Version.php
@@ -14,7 +14,7 @@ class Sabre_VObject_Version {
/**
* Full version number
*/
- const VERSION = '1.3.2';
+ const VERSION = '1.3.3';
/**
* Stability : alpha, beta, stable
diff --git a/3rdparty/Sabre/VObject/WindowsTimezoneMap.php b/3rdparty/Sabre/VObject/WindowsTimezoneMap.php
index 5e1cc5d479b..5e1cc5d479b 100644..100755
--- a/3rdparty/Sabre/VObject/WindowsTimezoneMap.php
+++ b/3rdparty/Sabre/VObject/WindowsTimezoneMap.php
diff --git a/3rdparty/Sabre/VObject/includes.php b/3rdparty/Sabre/VObject/includes.php
index 0177a8f1ba6..0177a8f1ba6 100644..100755
--- a/3rdparty/Sabre/VObject/includes.php
+++ b/3rdparty/Sabre/VObject/includes.php
diff --git a/3rdparty/Sabre/autoload.php b/3rdparty/Sabre/autoload.php
index c7b537d83d3..c7b537d83d3 100644..100755
--- a/3rdparty/Sabre/autoload.php
+++ b/3rdparty/Sabre/autoload.php
diff --git a/3rdparty/miniColors/GPL-LICENSE.txt b/3rdparty/miniColors/GPL-LICENSE.txt
new file mode 100644
index 00000000000..11dddd00ef0
--- /dev/null
+++ b/3rdparty/miniColors/GPL-LICENSE.txt
@@ -0,0 +1,278 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
diff --git a/3rdparty/miniColors/MIT-LICENSE.txt b/3rdparty/miniColors/MIT-LICENSE.txt
new file mode 100644
index 00000000000..ec6f7581576
--- /dev/null
+++ b/3rdparty/miniColors/MIT-LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright (c) Cory LaViska
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/3rdparty/miniColors/css/images/colors.png b/3rdparty/miniColors/css/images/colors.png
new file mode 100755
index 00000000000..1b4f819d8d9
--- /dev/null
+++ b/3rdparty/miniColors/css/images/colors.png
Binary files differ
diff --git a/3rdparty/miniColors/css/images/trigger.png b/3rdparty/miniColors/css/images/trigger.png
new file mode 100755
index 00000000000..8c169fd6053
--- /dev/null
+++ b/3rdparty/miniColors/css/images/trigger.png
Binary files differ
diff --git a/3rdparty/miniColors/css/jquery.miniColors.css b/3rdparty/miniColors/css/jquery.miniColors.css
new file mode 100755
index 00000000000..381bc1dc065
--- /dev/null
+++ b/3rdparty/miniColors/css/jquery.miniColors.css
@@ -0,0 +1,81 @@
+.miniColors-trigger {
+ height: 22px;
+ width: 22px;
+ background: url(images/trigger.png) center no-repeat;
+ vertical-align: middle;
+ margin: 0 .25em;
+ display: inline-block;
+ outline: none;
+}
+
+.miniColors-selector {
+ position: absolute;
+ width: 175px;
+ height: 150px;
+ background: #FFF;
+ border: solid 1px #BBB;
+ -moz-box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+ -webkit-box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+ box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+ padding: 5px;
+ z-index: 999999;
+}
+
+.miniColors-selector.black {
+ background: #000;
+ border-color: #000;
+}
+
+.miniColors-colors {
+ position: absolute;
+ top: 5px;
+ left: 5px;
+ width: 150px;
+ height: 150px;
+ background: url(images/colors.png) right no-repeat;
+ cursor: crosshair;
+}
+
+.miniColors-hues {
+ position: absolute;
+ top: 5px;
+ left: 160px;
+ width: 20px;
+ height: 150px;
+ background: url(images/colors.png) left no-repeat;
+ cursor: crosshair;
+}
+
+.miniColors-colorPicker {
+ position: absolute;
+ width: 9px;
+ height: 9px;
+ border: 1px solid #fff;
+ -moz-border-radius: 11px;
+ -webkit-border-radius: 11px;
+ border-radius: 11px;
+}
+.miniColors-colorPicker-inner {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 7px;
+ height: 7px;
+ border: 1px solid #000;
+ -moz-border-radius: 9px;
+ -webkit-border-radius: 9px;
+ border-radius: 9px;
+}
+
+.miniColors-huePicker {
+ position: absolute;
+ left: -3px;
+ width: 24px;
+ height: 1px;
+ border: 1px solid #fff;
+ border-radius: 2px;
+ background: #000;
+} \ No newline at end of file
diff --git a/3rdparty/miniColors/js/jquery.miniColors.js b/3rdparty/miniColors/js/jquery.miniColors.js
new file mode 100755
index 00000000000..187db3fa84e
--- /dev/null
+++ b/3rdparty/miniColors/js/jquery.miniColors.js
@@ -0,0 +1,580 @@
+/*
+ * jQuery miniColors: A small color selector
+ *
+ * Copyright 2011 Cory LaViska for A Beautiful Site, LLC. (http://abeautifulsite.net/)
+ *
+ * Dual licensed under the MIT or GPL Version 2 licenses
+ *
+*/
+if(jQuery) (function($) {
+
+ $.extend($.fn, {
+
+ miniColors: function(o, data) {
+
+ var create = function(input, o, data) {
+ //
+ // Creates a new instance of the miniColors selector
+ //
+
+ // Determine initial color (defaults to white)
+ var color = expandHex(input.val());
+ if( !color ) color = 'ffffff';
+ var hsb = hex2hsb(color);
+
+ // Create trigger
+ var trigger = $('<a class="miniColors-trigger" style="background-color: #' + color + '" href="#"></a>');
+ trigger.insertAfter(input);
+
+ // Set input data and update attributes
+ input
+ .addClass('miniColors')
+ .data('original-maxlength', input.attr('maxlength') || null)
+ .data('original-autocomplete', input.attr('autocomplete') || null)
+ .data('letterCase', 'uppercase')
+ .data('trigger', trigger)
+ .data('hsb', hsb)
+ .data('change', o.change ? o.change : null)
+ .data('close', o.close ? o.close : null)
+ .data('open', o.open ? o.open : null)
+ .attr('maxlength', 7)
+ .attr('autocomplete', 'off')
+ .val('#' + convertCase(color, o.letterCase));
+
+ // Handle options
+ if( o.readonly ) input.prop('readonly', true);
+ if( o.disabled ) disable(input);
+
+ // Show selector when trigger is clicked
+ trigger.bind('click.miniColors', function(event) {
+ event.preventDefault();
+ if( input.val() === '' ) input.val('#');
+ show(input);
+
+ });
+
+ // Show selector when input receives focus
+ input.bind('focus.miniColors', function(event) {
+ if( input.val() === '' ) input.val('#');
+ show(input);
+ });
+
+ // Hide on blur
+ input.bind('blur.miniColors', function(event) {
+ var hex = expandHex( hsb2hex(input.data('hsb')) );
+ input.val( hex ? '#' + convertCase(hex, input.data('letterCase')) : '' );
+ });
+
+ // Hide when tabbing out of the input
+ input.bind('keydown.miniColors', function(event) {
+ if( event.keyCode === 9 ) hide(input);
+ });
+
+ // Update when color is typed in
+ input.bind('keyup.miniColors', function(event) {
+ setColorFromInput(input);
+ });
+
+ // Handle pasting
+ input.bind('paste.miniColors', function(event) {
+ // Short pause to wait for paste to complete
+ setTimeout( function() {
+ setColorFromInput(input);
+ }, 5);
+ });
+
+ };
+
+ var destroy = function(input) {
+ //
+ // Destroys an active instance of the miniColors selector
+ //
+
+ hide();
+ input = $(input);
+
+ // Restore to original state
+ input.data('trigger').remove();
+ input
+ .attr('autocomplete', input.data('original-autocomplete'))
+ .attr('maxlength', input.data('original-maxlength'))
+ .removeData()
+ .removeClass('miniColors')
+ .unbind('.miniColors');
+ $(document).unbind('.miniColors');
+ };
+
+ var enable = function(input) {
+ //
+ // Enables the input control and the selector
+ //
+ input
+ .prop('disabled', false)
+ .data('trigger')
+ .css('opacity', 1);
+ };
+
+ var disable = function(input) {
+ //
+ // Disables the input control and the selector
+ //
+ hide(input);
+ input
+ .prop('disabled', true)
+ .data('trigger')
+ .css('opacity', 0.5);
+ };
+
+ var show = function(input) {
+ //
+ // Shows the miniColors selector
+ //
+ if( input.prop('disabled') ) return false;
+
+ // Hide all other instances
+ hide();
+
+ // Generate the selector
+ var selector = $('<div class="miniColors-selector"></div>');
+ selector
+ .append('<div class="miniColors-colors" style="background-color: #FFF;"><div class="miniColors-colorPicker"><div class="miniColors-colorPicker-inner"></div></div>')
+ .append('<div class="miniColors-hues"><div class="miniColors-huePicker"></div></div>')
+ .css({
+ top: input.is(':visible') ? input.offset().top + input.outerHeight() : input.data('trigger').offset().top + input.data('trigger').outerHeight(),
+ left: input.is(':visible') ? input.offset().left : input.data('trigger').offset().left,
+ display: 'none'
+ })
+ .addClass( input.attr('class') );
+
+ // Set background for colors
+ var hsb = input.data('hsb');
+ selector
+ .find('.miniColors-colors')
+ .css('backgroundColor', '#' + hsb2hex({ h: hsb.h, s: 100, b: 100 }));
+
+ // Set colorPicker position
+ var colorPosition = input.data('colorPosition');
+ if( !colorPosition ) colorPosition = getColorPositionFromHSB(hsb);
+ selector.find('.miniColors-colorPicker')
+ .css('top', colorPosition.y + 'px')
+ .css('left', colorPosition.x + 'px');
+
+ // Set huePicker position
+ var huePosition = input.data('huePosition');
+ if( !huePosition ) huePosition = getHuePositionFromHSB(hsb);
+ selector.find('.miniColors-huePicker').css('top', huePosition.y + 'px');
+
+ // Set input data
+ input
+ .data('selector', selector)
+ .data('huePicker', selector.find('.miniColors-huePicker'))
+ .data('colorPicker', selector.find('.miniColors-colorPicker'))
+ .data('mousebutton', 0);
+
+ $('BODY').append(selector);
+ selector.fadeIn(100);
+
+ // Prevent text selection in IE
+ selector.bind('selectstart', function() { return false; });
+
+ $(document).bind('mousedown.miniColors touchstart.miniColors', function(event) {
+
+ input.data('mousebutton', 1);
+ var testSubject = $(event.target).parents().andSelf();
+
+ if( testSubject.hasClass('miniColors-colors') ) {
+ event.preventDefault();
+ input.data('moving', 'colors');
+ moveColor(input, event);
+ }
+
+ if( testSubject.hasClass('miniColors-hues') ) {
+ event.preventDefault();
+ input.data('moving', 'hues');
+ moveHue(input, event);
+ }
+
+ if( testSubject.hasClass('miniColors-selector') ) {
+ event.preventDefault();
+ return;
+ }
+
+ if( testSubject.hasClass('miniColors') ) return;
+
+ hide(input);
+ });
+
+ $(document)
+ .bind('mouseup.miniColors touchend.miniColors', function(event) {
+ event.preventDefault();
+ input.data('mousebutton', 0).removeData('moving');
+ })
+ .bind('mousemove.miniColors touchmove.miniColors', function(event) {
+ event.preventDefault();
+ if( input.data('mousebutton') === 1 ) {
+ if( input.data('moving') === 'colors' ) moveColor(input, event);
+ if( input.data('moving') === 'hues' ) moveHue(input, event);
+ }
+ });
+
+ // Fire open callback
+ if( input.data('open') ) {
+ input.data('open').call(input.get(0), '#' + hsb2hex(hsb), hsb2rgb(hsb));
+ }
+
+ };
+
+ var hide = function(input) {
+
+ //
+ // Hides one or more miniColors selectors
+ //
+
+ // Hide all other instances if input isn't specified
+ if( !input ) input = '.miniColors';
+
+ $(input).each( function() {
+ var selector = $(this).data('selector');
+ $(this).removeData('selector');
+ $(selector).fadeOut(100, function() {
+ // Fire close callback
+ if( input.data('close') ) {
+ var hsb = input.data('hsb'), hex = hsb2hex(hsb);
+ input.data('close').call(input.get(0), '#' + hex, hsb2rgb(hsb));
+ }
+ $(this).remove();
+ });
+ });
+
+ $(document).unbind('.miniColors');
+
+ };
+
+ var moveColor = function(input, event) {
+
+ var colorPicker = input.data('colorPicker');
+
+ colorPicker.hide();
+
+ var position = {
+ x: event.pageX,
+ y: event.pageY
+ };
+
+ // Touch support
+ if( event.originalEvent.changedTouches ) {
+ position.x = event.originalEvent.changedTouches[0].pageX;
+ position.y = event.originalEvent.changedTouches[0].pageY;
+ }
+ position.x = position.x - input.data('selector').find('.miniColors-colors').offset().left - 5;
+ position.y = position.y - input.data('selector').find('.miniColors-colors').offset().top - 5;
+ if( position.x <= -5 ) position.x = -5;
+ if( position.x >= 144 ) position.x = 144;
+ if( position.y <= -5 ) position.y = -5;
+ if( position.y >= 144 ) position.y = 144;
+
+ input.data('colorPosition', position);
+ colorPicker.css('left', position.x).css('top', position.y).show();
+
+ // Calculate saturation
+ var s = Math.round((position.x + 5) * 0.67);
+ if( s < 0 ) s = 0;
+ if( s > 100 ) s = 100;
+
+ // Calculate brightness
+ var b = 100 - Math.round((position.y + 5) * 0.67);
+ if( b < 0 ) b = 0;
+ if( b > 100 ) b = 100;
+
+ // Update HSB values
+ var hsb = input.data('hsb');
+ hsb.s = s;
+ hsb.b = b;
+
+ // Set color
+ setColor(input, hsb, true);
+ };
+
+ var moveHue = function(input, event) {
+
+ var huePicker = input.data('huePicker');
+
+ huePicker.hide();
+
+ var position = {
+ y: event.pageY
+ };
+
+ // Touch support
+ if( event.originalEvent.changedTouches ) {
+ position.y = event.originalEvent.changedTouches[0].pageY;
+ }
+
+ position.y = position.y - input.data('selector').find('.miniColors-colors').offset().top - 1;
+ if( position.y <= -1 ) position.y = -1;
+ if( position.y >= 149 ) position.y = 149;
+ input.data('huePosition', position);
+ huePicker.css('top', position.y).show();
+
+ // Calculate hue
+ var h = Math.round((150 - position.y - 1) * 2.4);
+ if( h < 0 ) h = 0;
+ if( h > 360 ) h = 360;
+
+ // Update HSB values
+ var hsb = input.data('hsb');
+ hsb.h = h;
+
+ // Set color
+ setColor(input, hsb, true);
+
+ };
+
+ var setColor = function(input, hsb, updateInput) {
+ input.data('hsb', hsb);
+ var hex = hsb2hex(hsb);
+ if( updateInput ) input.val( '#' + convertCase(hex, input.data('letterCase')) );
+ input.data('trigger').css('backgroundColor', '#' + hex);
+ if( input.data('selector') ) input.data('selector').find('.miniColors-colors').css('backgroundColor', '#' + hsb2hex({ h: hsb.h, s: 100, b: 100 }));
+
+ // Fire change callback
+ if( input.data('change') ) {
+ if( hex === input.data('lastChange') ) return;
+ input.data('change').call(input.get(0), '#' + hex, hsb2rgb(hsb));
+ input.data('lastChange', hex);
+ }
+
+ };
+
+ var setColorFromInput = function(input) {
+
+ input.val('#' + cleanHex(input.val()));
+ var hex = expandHex(input.val());
+ if( !hex ) return false;
+
+ // Get HSB equivalent
+ var hsb = hex2hsb(hex);
+
+ // If color is the same, no change required
+ var currentHSB = input.data('hsb');
+ if( hsb.h === currentHSB.h && hsb.s === currentHSB.s && hsb.b === currentHSB.b ) return true;
+
+ // Set colorPicker position
+ var colorPosition = getColorPositionFromHSB(hsb);
+ var colorPicker = $(input.data('colorPicker'));
+ colorPicker.css('top', colorPosition.y + 'px').css('left', colorPosition.x + 'px');
+ input.data('colorPosition', colorPosition);
+
+ // Set huePosition position
+ var huePosition = getHuePositionFromHSB(hsb);
+ var huePicker = $(input.data('huePicker'));
+ huePicker.css('top', huePosition.y + 'px');
+ input.data('huePosition', huePosition);
+
+ setColor(input, hsb);
+
+ return true;
+
+ };
+
+ var convertCase = function(string, letterCase) {
+ if( letterCase === 'lowercase' ) return string.toLowerCase();
+ if( letterCase === 'uppercase' ) return string.toUpperCase();
+ return string;
+ };
+
+ var getColorPositionFromHSB = function(hsb) {
+ var x = Math.ceil(hsb.s / 0.67);
+ if( x < 0 ) x = 0;
+ if( x > 150 ) x = 150;
+ var y = 150 - Math.ceil(hsb.b / 0.67);
+ if( y < 0 ) y = 0;
+ if( y > 150 ) y = 150;
+ return { x: x - 5, y: y - 5 };
+ };
+
+ var getHuePositionFromHSB = function(hsb) {
+ var y = 150 - (hsb.h / 2.4);
+ if( y < 0 ) h = 0;
+ if( y > 150 ) h = 150;
+ return { y: y - 1 };
+ };
+
+ var cleanHex = function(hex) {
+ return hex.replace(/[^A-F0-9]/ig, '');
+ };
+
+ var expandHex = function(hex) {
+ hex = cleanHex(hex);
+ if( !hex ) return null;
+ if( hex.length === 3 ) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
+ return hex.length === 6 ? hex : null;
+ };
+
+ var hsb2rgb = function(hsb) {
+ var rgb = {};
+ var h = Math.round(hsb.h);
+ var s = Math.round(hsb.s*255/100);
+ var v = Math.round(hsb.b*255/100);
+ if(s === 0) {
+ rgb.r = rgb.g = rgb.b = v;
+ } else {
+ var t1 = v;
+ var t2 = (255 - s) * v / 255;
+ var t3 = (t1 - t2) * (h % 60) / 60;
+ if( h === 360 ) h = 0;
+ if( h < 60 ) { rgb.r = t1; rgb.b = t2; rgb.g = t2 + t3; }
+ else if( h < 120 ) {rgb.g = t1; rgb.b = t2; rgb.r = t1 - t3; }
+ else if( h < 180 ) {rgb.g = t1; rgb.r = t2; rgb.b = t2 + t3; }
+ else if( h < 240 ) {rgb.b = t1; rgb.r = t2; rgb.g = t1 - t3; }
+ else if( h < 300 ) {rgb.b = t1; rgb.g = t2; rgb.r = t2 + t3; }
+ else if( h < 360 ) {rgb.r = t1; rgb.g = t2; rgb.b = t1 - t3; }
+ else { rgb.r = 0; rgb.g = 0; rgb.b = 0; }
+ }
+ return {
+ r: Math.round(rgb.r),
+ g: Math.round(rgb.g),
+ b: Math.round(rgb.b)
+ };
+ };
+
+ var rgb2hex = function(rgb) {
+ var hex = [
+ rgb.r.toString(16),
+ rgb.g.toString(16),
+ rgb.b.toString(16)
+ ];
+ $.each(hex, function(nr, val) {
+ if (val.length === 1) hex[nr] = '0' + val;
+ });
+ return hex.join('');
+ };
+
+ var hex2rgb = function(hex) {
+ hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
+
+ return {
+ r: hex >> 16,
+ g: (hex & 0x00FF00) >> 8,
+ b: (hex & 0x0000FF)
+ };
+ };
+
+ var rgb2hsb = function(rgb) {
+ var hsb = { h: 0, s: 0, b: 0 };
+ var min = Math.min(rgb.r, rgb.g, rgb.b);
+ var max = Math.max(rgb.r, rgb.g, rgb.b);
+ var delta = max - min;
+ hsb.b = max;
+ hsb.s = max !== 0 ? 255 * delta / max : 0;
+ if( hsb.s !== 0 ) {
+ if( rgb.r === max ) {
+ hsb.h = (rgb.g - rgb.b) / delta;
+ } else if( rgb.g === max ) {
+ hsb.h = 2 + (rgb.b - rgb.r) / delta;
+ } else {
+ hsb.h = 4 + (rgb.r - rgb.g) / delta;
+ }
+ } else {
+ hsb.h = -1;
+ }
+ hsb.h *= 60;
+ if( hsb.h < 0 ) {
+ hsb.h += 360;
+ }
+ hsb.s *= 100/255;
+ hsb.b *= 100/255;
+ return hsb;
+ };
+
+ var hex2hsb = function(hex) {
+ var hsb = rgb2hsb(hex2rgb(hex));
+ // Zero out hue marker for black, white, and grays (saturation === 0)
+ if( hsb.s === 0 ) hsb.h = 360;
+ return hsb;
+ };
+
+ var hsb2hex = function(hsb) {
+ return rgb2hex(hsb2rgb(hsb));
+ };
+
+
+ // Handle calls to $([selector]).miniColors()
+ switch(o) {
+
+ case 'readonly':
+
+ $(this).each( function() {
+ if( !$(this).hasClass('miniColors') ) return;
+ $(this).prop('readonly', data);
+ });
+
+ return $(this);
+
+ case 'disabled':
+
+ $(this).each( function() {
+ if( !$(this).hasClass('miniColors') ) return;
+ if( data ) {
+ disable($(this));
+ } else {
+ enable($(this));
+ }
+ });
+
+ return $(this);
+
+ case 'value':
+
+ // Getter
+ if( data === undefined ) {
+ if( !$(this).hasClass('miniColors') ) return;
+ var input = $(this),
+ hex = expandHex(input.val());
+ return hex ? '#' + convertCase(hex, input.data('letterCase')) : null;
+ }
+
+ // Setter
+ $(this).each( function() {
+ if( !$(this).hasClass('miniColors') ) return;
+ $(this).val(data);
+ setColorFromInput($(this));
+ });
+
+ return $(this);
+
+ case 'destroy':
+
+ $(this).each( function() {
+ if( !$(this).hasClass('miniColors') ) return;
+ destroy($(this));
+ });
+
+ return $(this);
+
+ default:
+
+ if( !o ) o = {};
+
+ $(this).each( function() {
+
+ // Must be called on an input element
+ if( $(this)[0].tagName.toLowerCase() !== 'input' ) return;
+
+ // If a trigger is present, the control was already created
+ if( $(this).data('trigger') ) return;
+
+ // Create the control
+ create($(this), o, data);
+
+ });
+
+ return $(this);
+
+ }
+
+ }
+
+ });
+
+})(jQuery); \ No newline at end of file
diff --git a/3rdparty/miniColors/js/jquery.miniColors.min.js b/3rdparty/miniColors/js/jquery.miniColors.min.js
new file mode 100755
index 00000000000..c00e0ace6b5
--- /dev/null
+++ b/3rdparty/miniColors/js/jquery.miniColors.min.js
@@ -0,0 +1,9 @@
+/*
+ * jQuery miniColors: A small color selector
+ *
+ * Copyright 2011 Cory LaViska for A Beautiful Site, LLC. (http://abeautifulsite.net/)
+ *
+ * Dual licensed under the MIT or GPL Version 2 licenses
+ *
+*/
+if(jQuery)(function($){$.extend($.fn,{miniColors:function(o,data){var create=function(input,o,data){var color=expandHex(input.val());if(!color)color='ffffff';var hsb=hex2hsb(color);var trigger=$('<a class="miniColors-trigger" style="background-color: #'+color+'" href="#"></a>');trigger.insertAfter(input);input.addClass('miniColors').data('original-maxlength',input.attr('maxlength')||null).data('original-autocomplete',input.attr('autocomplete')||null).data('letterCase','uppercase').data('trigger',trigger).data('hsb',hsb).data('change',o.change?o.change:null).data('close',o.close?o.close:null).data('open',o.open?o.open:null).attr('maxlength',7).attr('autocomplete','off').val('#'+convertCase(color,o.letterCase));if(o.readonly)input.prop('readonly',true);if(o.disabled)disable(input);trigger.bind('click.miniColors',function(event){event.preventDefault();if(input.val()==='')input.val('#');show(input)});input.bind('focus.miniColors',function(event){if(input.val()==='')input.val('#');show(input)});input.bind('blur.miniColors',function(event){var hex=expandHex(hsb2hex(input.data('hsb')));input.val(hex?'#'+convertCase(hex,input.data('letterCase')):'')});input.bind('keydown.miniColors',function(event){if(event.keyCode===9)hide(input)});input.bind('keyup.miniColors',function(event){setColorFromInput(input)});input.bind('paste.miniColors',function(event){setTimeout(function(){setColorFromInput(input)},5)})};var destroy=function(input){hide();input=$(input);input.data('trigger').remove();input.attr('autocomplete',input.data('original-autocomplete')).attr('maxlength',input.data('original-maxlength')).removeData().removeClass('miniColors').unbind('.miniColors');$(document).unbind('.miniColors')};var enable=function(input){input.prop('disabled',false).data('trigger').css('opacity',1)};var disable=function(input){hide(input);input.prop('disabled',true).data('trigger').css('opacity',0.5)};var show=function(input){if(input.prop('disabled'))return false;hide();var selector=$('<div class="miniColors-selector"></div>');selector.append('<div class="miniColors-colors" style="background-color: #FFF;"><div class="miniColors-colorPicker"><div class="miniColors-colorPicker-inner"></div></div>').append('<div class="miniColors-hues"><div class="miniColors-huePicker"></div></div>').css({top:input.is(':visible')?input.offset().top+input.outerHeight():input.data('trigger').offset().top+input.data('trigger').outerHeight(),left:input.is(':visible')?input.offset().left:input.data('trigger').offset().left,display:'none'}).addClass(input.attr('class'));var hsb=input.data('hsb');selector.find('.miniColors-colors').css('backgroundColor','#'+hsb2hex({h:hsb.h,s:100,b:100}));var colorPosition=input.data('colorPosition');if(!colorPosition)colorPosition=getColorPositionFromHSB(hsb);selector.find('.miniColors-colorPicker').css('top',colorPosition.y+'px').css('left',colorPosition.x+'px');var huePosition=input.data('huePosition');if(!huePosition)huePosition=getHuePositionFromHSB(hsb);selector.find('.miniColors-huePicker').css('top',huePosition.y+'px');input.data('selector',selector).data('huePicker',selector.find('.miniColors-huePicker')).data('colorPicker',selector.find('.miniColors-colorPicker')).data('mousebutton',0);$('BODY').append(selector);selector.fadeIn(100);selector.bind('selectstart',function(){return false});$(document).bind('mousedown.miniColors touchstart.miniColors',function(event){input.data('mousebutton',1);var testSubject=$(event.target).parents().andSelf();if(testSubject.hasClass('miniColors-colors')){event.preventDefault();input.data('moving','colors');moveColor(input,event)}if(testSubject.hasClass('miniColors-hues')){event.preventDefault();input.data('moving','hues');moveHue(input,event)}if(testSubject.hasClass('miniColors-selector')){event.preventDefault();return}if(testSubject.hasClass('miniColors'))return;hide(input)});$(document).bind('mouseup.miniColors touchend.miniColors',function(event){event.preventDefault();input.data('mousebutton',0).removeData('moving')}).bind('mousemove.miniColors touchmove.miniColors',function(event){event.preventDefault();if(input.data('mousebutton')===1){if(input.data('moving')==='colors')moveColor(input,event);if(input.data('moving')==='hues')moveHue(input,event)}});if(input.data('open')){input.data('open').call(input.get(0),'#'+hsb2hex(hsb),hsb2rgb(hsb))}};var hide=function(input){if(!input)input='.miniColors';$(input).each(function(){var selector=$(this).data('selector');$(this).removeData('selector');$(selector).fadeOut(100,function(){if(input.data('close')){var hsb=input.data('hsb'),hex=hsb2hex(hsb);input.data('close').call(input.get(0),'#'+hex,hsb2rgb(hsb))}$(this).remove()})});$(document).unbind('.miniColors')};var moveColor=function(input,event){var colorPicker=input.data('colorPicker');colorPicker.hide();var position={x:event.pageX,y:event.pageY};if(event.originalEvent.changedTouches){position.x=event.originalEvent.changedTouches[0].pageX;position.y=event.originalEvent.changedTouches[0].pageY}position.x=position.x-input.data('selector').find('.miniColors-colors').offset().left-5;position.y=position.y-input.data('selector').find('.miniColors-colors').offset().top-5;if(position.x<=-5)position.x=-5;if(position.x>=144)position.x=144;if(position.y<=-5)position.y=-5;if(position.y>=144)position.y=144;input.data('colorPosition',position);colorPicker.css('left',position.x).css('top',position.y).show();var s=Math.round((position.x+5)*0.67);if(s<0)s=0;if(s>100)s=100;var b=100-Math.round((position.y+5)*0.67);if(b<0)b=0;if(b>100)b=100;var hsb=input.data('hsb');hsb.s=s;hsb.b=b;setColor(input,hsb,true)};var moveHue=function(input,event){var huePicker=input.data('huePicker');huePicker.hide();var position={y:event.pageY};if(event.originalEvent.changedTouches){position.y=event.originalEvent.changedTouches[0].pageY}position.y=position.y-input.data('selector').find('.miniColors-colors').offset().top-1;if(position.y<=-1)position.y=-1;if(position.y>=149)position.y=149;input.data('huePosition',position);huePicker.css('top',position.y).show();var h=Math.round((150-position.y-1)*2.4);if(h<0)h=0;if(h>360)h=360;var hsb=input.data('hsb');hsb.h=h;setColor(input,hsb,true)};var setColor=function(input,hsb,updateInput){input.data('hsb',hsb);var hex=hsb2hex(hsb);if(updateInput)input.val('#'+convertCase(hex,input.data('letterCase')));input.data('trigger').css('backgroundColor','#'+hex);if(input.data('selector'))input.data('selector').find('.miniColors-colors').css('backgroundColor','#'+hsb2hex({h:hsb.h,s:100,b:100}));if(input.data('change')){if(hex===input.data('lastChange'))return;input.data('change').call(input.get(0),'#'+hex,hsb2rgb(hsb));input.data('lastChange',hex)}};var setColorFromInput=function(input){input.val('#'+cleanHex(input.val()));var hex=expandHex(input.val());if(!hex)return false;var hsb=hex2hsb(hex);var currentHSB=input.data('hsb');if(hsb.h===currentHSB.h&&hsb.s===currentHSB.s&&hsb.b===currentHSB.b)return true;var colorPosition=getColorPositionFromHSB(hsb);var colorPicker=$(input.data('colorPicker'));colorPicker.css('top',colorPosition.y+'px').css('left',colorPosition.x+'px');input.data('colorPosition',colorPosition);var huePosition=getHuePositionFromHSB(hsb);var huePicker=$(input.data('huePicker'));huePicker.css('top',huePosition.y+'px');input.data('huePosition',huePosition);setColor(input,hsb);return true};var convertCase=function(string,letterCase){if(letterCase==='lowercase')return string.toLowerCase();if(letterCase==='uppercase')return string.toUpperCase();return string};var getColorPositionFromHSB=function(hsb){var x=Math.ceil(hsb.s/0.67);if(x<0)x=0;if(x>150)x=150;var y=150-Math.ceil(hsb.b/0.67);if(y<0)y=0;if(y>150)y=150;return{x:x-5,y:y-5}};var getHuePositionFromHSB=function(hsb){var y=150-(hsb.h/2.4);if(y<0)h=0;if(y>150)h=150;return{y:y-1}};var cleanHex=function(hex){return hex.replace(/[^A-F0-9]/ig,'')};var expandHex=function(hex){hex=cleanHex(hex);if(!hex)return null;if(hex.length===3)hex=hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];return hex.length===6?hex:null};var hsb2rgb=function(hsb){var rgb={};var h=Math.round(hsb.h);var s=Math.round(hsb.s*255/100);var v=Math.round(hsb.b*255/100);if(s===0){rgb.r=rgb.g=rgb.b=v}else{var t1=v;var t2=(255-s)*v/255;var t3=(t1-t2)*(h%60)/60;if(h===360)h=0;if(h<60){rgb.r=t1;rgb.b=t2;rgb.g=t2+t3}else if(h<120){rgb.g=t1;rgb.b=t2;rgb.r=t1-t3}else if(h<180){rgb.g=t1;rgb.r=t2;rgb.b=t2+t3}else if(h<240){rgb.b=t1;rgb.r=t2;rgb.g=t1-t3}else if(h<300){rgb.b=t1;rgb.g=t2;rgb.r=t2+t3}else if(h<360){rgb.r=t1;rgb.g=t2;rgb.b=t1-t3}else{rgb.r=0;rgb.g=0;rgb.b=0}}return{r:Math.round(rgb.r),g:Math.round(rgb.g),b:Math.round(rgb.b)}};var rgb2hex=function(rgb){var hex=[rgb.r.toString(16),rgb.g.toString(16),rgb.b.toString(16)];$.each(hex,function(nr,val){if(val.length===1)hex[nr]='0'+val});return hex.join('')};var hex2rgb=function(hex){hex=parseInt(((hex.indexOf('#')>-1)?hex.substring(1):hex),16);return{r:hex>>16,g:(hex&0x00FF00)>>8,b:(hex&0x0000FF)}};var rgb2hsb=function(rgb){var hsb={h:0,s:0,b:0};var min=Math.min(rgb.r,rgb.g,rgb.b);var max=Math.max(rgb.r,rgb.g,rgb.b);var delta=max-min;hsb.b=max;hsb.s=max!==0?255*delta/max:0;if(hsb.s!==0){if(rgb.r===max){hsb.h=(rgb.g-rgb.b)/delta}else if(rgb.g===max){hsb.h=2+(rgb.b-rgb.r)/delta}else{hsb.h=4+(rgb.r-rgb.g)/delta}}else{hsb.h=-1}hsb.h*=60;if(hsb.h<0){hsb.h+=360}hsb.s*=100/255;hsb.b*=100/255;return hsb};var hex2hsb=function(hex){var hsb=rgb2hsb(hex2rgb(hex));if(hsb.s===0)hsb.h=360;return hsb};var hsb2hex=function(hsb){return rgb2hex(hsb2rgb(hsb))};switch(o){case'readonly':$(this).each(function(){if(!$(this).hasClass('miniColors'))return;$(this).prop('readonly',data)});return $(this);case'disabled':$(this).each(function(){if(!$(this).hasClass('miniColors'))return;if(data){disable($(this))}else{enable($(this))}});return $(this);case'value':if(data===undefined){if(!$(this).hasClass('miniColors'))return;var input=$(this),hex=expandHex(input.val());return hex?'#'+convertCase(hex,input.data('letterCase')):null}$(this).each(function(){if(!$(this).hasClass('miniColors'))return;$(this).val(data);setColorFromInput($(this))});return $(this);case'destroy':$(this).each(function(){if(!$(this).hasClass('miniColors'))return;destroy($(this))});return $(this);default:if(!o)o={};$(this).each(function(){if($(this)[0].tagName.toLowerCase()!=='input')return;if($(this).data('trigger'))return;create($(this),o,data)});return $(this)}}})})(jQuery); \ No newline at end of file
diff --git a/apps/user_openid/class.openid.v3.php b/3rdparty/openid/class.openid.v3.php
index eeb31986659..eeb31986659 100644
--- a/apps/user_openid/class.openid.v3.php
+++ b/3rdparty/openid/class.openid.v3.php
diff --git a/apps/user_openid/phpmyid.php b/3rdparty/openid/phpmyid.php
index 13fd31c47ca..13fd31c47ca 100644
--- a/apps/user_openid/phpmyid.php
+++ b/3rdparty/openid/phpmyid.php
diff --git a/apps/atnotes/ajax/browse.php b/apps/atnotes/ajax/browse.php
new file mode 100644
index 00000000000..475b5e8614e
--- /dev/null
+++ b/apps/atnotes/ajax/browse.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+* ownCloud - ATNotes plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+require_once('../../../lib/base.php');
+require_once('../../../lib/template.php');
+
+OC_JSON::checkLoggedIn();
+OC_JSON::checkAppEnabled('atnotes');
+
+$p = trim($_POST['p']);
+$top = FALSE;
+if($p == '/'){
+ $p = OC::$CONFIG_DATADIRECTORY;
+ $top = TRUE;
+}
+
+$files = Array();
+foreach(OC_Files::getdirectorycontent($p) as $i){
+ $elt = '';
+
+ if($i['type'] == 'file'){
+ $fileinfo = pathinfo($i['name']);
+ $i['basename'] = $fileinfo['filename'];
+ if (!empty($fileinfo['extension'])){
+ $i['extention'] = '.'.$fileinfo['extension'];
+ }else{
+ $i['extention'] = '';
+ }
+ }
+ if($i['directory'] == '/'){
+ $i['directory'] = '';
+ }
+ if($i['extention'] == '.txt' || $i['type'] == 'dir'){
+ $i["date"] = OC_Util::formatDate($i["mtime"]);
+
+ $write = ($i['writeable'])?'true':'false';
+ $simple_file_size = simple_file_size($i['size']);
+ $simple_size_color = intval(200 - $i['size'] / (1024 * 1024) * 2);
+
+ if($simple_size_color < 0){
+ $simple_size_color = 0;
+ }
+
+ $relative_modified_date = relative_modified_date($i['mtime']);
+ $relative_date_color = round((time() - $i['mtime']) / 60 / 60 / 24 * 14);
+
+ if($relative_date_color > 200){
+ $relative_date_color = 200;
+ }
+
+ $name = str_replace('+', '%20', urlencode($i['name']));
+ $name = str_replace('%2F', '/', $name);
+ $directory = str_replace('+', '%20', urlencode($i['directory']));
+ $directory = str_replace('%2F', '/', $directory);
+
+ $elt .= '<tr data-file="'.$name.'" data-type="'.(($i['type'] == 'dir')?'dir':'file').'" data-mime="'.$i['mime'].'" data-size="'.$i['size'].'" data-write="'.$write.'">';
+ $elt .= '<td class="filename svg" data-rel="'.$directory.'/'.$name.'" style="background-image:url('.(($i['type'] == 'dir')?mimetype_icon('dir'):mimetype_icon($i['mime'])).')">';
+ $elt .= '<span class="nametext">';
+ if($i['type'] == 'dir'){
+ $elt .= htmlspecialchars($i['name']);
+ }else{
+ $elt .= htmlspecialchars($i['basename']).'<span class="extention">'.$i['extention'].'</span>';
+ }
+ $elt .= '</span>';
+ $elt .= '</td>';
+ $elt .= '<td class="filesize" title="'.human_file_size($i['size']).'" style="color:rgb('.$simple_size_color.','.$simple_size_color.','.$simple_size_color.')">'.$simple_file_size.'</td>';
+ $elt .= '<td class="date"><span class="modified" title="'.$i['date'].'" style="color:rgb('.$relative_date_color.','.$relative_date_color.','.$relative_date_color.')">'.$relative_modified_date.'</span></td>';
+ $elt .= '</tr>';
+
+ $files[] = $elt;
+ }
+}
+
+if(!$top){
+ $p = str_replace('+', '%20', urlencode($p));
+ $p = str_replace('%2F', '/', $p);
+ $p = substr($p,0,strrpos($p,'/'));
+ if(strlen($p) == 0){
+ $p = '/';
+ }
+
+ $elt = '<tr>';
+ $elt .= '<td class="filename svg" data-rel="'.$p.'" style="background-image:url('.mimetype_icon('dir').')">';
+ $elt .= '<span class="nametext">..</span>';
+ $elt .= '</td>';
+ $elt .= '<td class="filesize">&nbsp;</td>';
+ $elt .= '<td class="date">&nbsp;</td>';
+ $elt .= '</tr>';
+ array_unshift($files, $elt);
+}
+
+OC_JSON::encodedPrint($files); \ No newline at end of file
diff --git a/apps/atnotes/ajax/check.php b/apps/atnotes/ajax/check.php
new file mode 100644
index 00000000000..c2642b620f6
--- /dev/null
+++ b/apps/atnotes/ajax/check.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+* ownCloud - ATNotes plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('atnotes');
+
+$p = trim($_POST['p']);
+$t = trim($_POST['t']);
+
+$normalizeChars = array(
+ 'Á'=>'A', 'À'=>'A', 'Â'=>'A', 'Ã'=>'A', 'Å'=>'A', 'Ä'=>'A', 'Æ'=>'AE', 'Ç'=>'C',
+ 'É'=>'E', 'È'=>'E', 'Ê'=>'E', 'Ë'=>'E', 'Í'=>'I', 'Ì'=>'I', 'Î'=>'I', 'Ï'=>'I', 'Ð'=>'Eth',
+ 'Ñ'=>'N', 'Ó'=>'O', 'Ò'=>'O', 'Ô'=>'O', 'Õ'=>'O', 'Ö'=>'O', 'Ø'=>'O',
+ 'Ú'=>'U', 'Ù'=>'U', 'Û'=>'U', 'Ü'=>'U', 'Ý'=>'Y',
+ 'á'=>'a', 'à'=>'a', 'â'=>'a', 'ã'=>'a', 'å'=>'a', 'ä'=>'a', 'æ'=>'ae', 'ç'=>'c',
+ 'é'=>'e', 'è'=>'e', 'ê'=>'e', 'ë'=>'e', 'í'=>'i', 'ì'=>'i', 'î'=>'i', 'ï'=>'i', 'ð'=>'eth',
+ 'ñ'=>'n', 'ó'=>'o', 'ò'=>'o', 'ô'=>'o', 'õ'=>'o', 'ö'=>'o', 'ø'=>'o',
+ 'ú'=>'u', 'ù'=>'u', 'û'=>'u', 'ü'=>'u', 'ý'=>'y',
+ 'ß'=>'sz', 'þ'=>'thorn', 'ÿ'=>'y', ' ' => '_', '"' => '', "'" => ' ', '/' => '-'
+);
+
+$r = Array('e' => FALSE, 'p' => '');
+if(strlen($p) != 0 && strlen($t) != 0){
+ $fs = OCP\Files::getStorage('files');
+ if($fs->is_dir($p)){
+ $r['p'] = $p.'/'.strtr($t, $normalizeChars).'.txt';
+ }else{
+ $r['p'] = $p;
+ }
+
+ if($fs->file_exists($r['p'])){
+ $r['e'] = TRUE;
+ }
+}
+
+OCP\JSON::encodedPrint($r);
diff --git a/apps/atnotes/ajax/delete.php b/apps/atnotes/ajax/delete.php
new file mode 100644
index 00000000000..dfb9e116478
--- /dev/null
+++ b/apps/atnotes/ajax/delete.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+* ownCloud - ATNotes plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('atnotes');
+
+$i = trim($_POST['i']);
+
+$r = Array('e' => 1);
+if(is_numeric($i)){
+ $r['e'] = OC_ATNotes::deleteNote($i);
+}
+
+OCP\JSON::encodedPrint($r); \ No newline at end of file
diff --git a/apps/atnotes/ajax/export.php b/apps/atnotes/ajax/export.php
new file mode 100644
index 00000000000..d158f6b3c0c
--- /dev/null
+++ b/apps/atnotes/ajax/export.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+* ownCloud - ATNotes plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('atnotes');
+
+$p = trim($_POST['p']);
+$t = trim($_POST['t']);
+$c = trim($_POST['c']);
+
+$r = Array('e' => '');
+if(strlen($p) != 0 && strlen($t) != 0 && strlen($c) != 0){
+ $fs = OCP\Files::getStorage('files');
+ if(!$fp = $fs->fopen($p, 'w')){
+ $r['e'] = 'Can not open file '.$p;
+ }else{
+ if(fwrite($fp, $t."\n") === FALSE){
+ $r['e'] = 'Can not write to file '.$p;
+ }else{
+ $c = preg_replace('/<br[.*]{0,}>/',"\n",$c);
+ $c = preg_replace('/<u[.*]{0,}>/','',$c);$c = preg_replace('/<\/u>/','',$c);
+ $c = preg_replace('/<b[.*]{0,}>/','',$c);$c = preg_replace('/<\/b>/','',$c);
+ $c = preg_replace('/<i[.*]{0,}>/','',$c);$c = preg_replace('/<\/i>/','',$c);
+ $c = preg_replace('/<hr.*>/',"------------------------\n",$c);
+ $c = preg_replace('/<sup[.*]{0,}>/','(',$c);$c = preg_replace('/<\/sup>/',')',$c);
+ $c = preg_replace('/<sub[.*]{0,}>/','(',$c);$c = preg_replace('/<\/sub>/',')',$c);
+ if(fwrite($fp, $c) === FALSE){
+ $r['e'] = 'Can not write to file '.$p;
+ }else{
+ fclose($fp);
+ }
+ }
+ }
+}
+
+OCP\JSON::encodedPrint($r);
diff --git a/apps/atnotes/ajax/save.php b/apps/atnotes/ajax/save.php
new file mode 100644
index 00000000000..1212c9ea4c3
--- /dev/null
+++ b/apps/atnotes/ajax/save.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+* ownCloud - ATNotes plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('atnotes');
+
+$i = trim($_POST['i']);
+$t = trim($_POST['t']);
+$c = trim($_POST['c']);
+
+$r = Array('e' => '', 'i' => 0);
+if(strlen($t) != 0){
+ $r['i'] = OC_ATNotes::saveNote($i,$t,$c);
+}
+
+OCP\JSON::encodedPrint($r); \ No newline at end of file
diff --git a/apps/atnotes/appinfo/app.php b/apps/atnotes/appinfo/app.php
new file mode 100644
index 00000000000..4304e178714
--- /dev/null
+++ b/apps/atnotes/appinfo/app.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+* ownCloud - ATNotes plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+OCP\App::checkAppEnabled('atnotes');
+OC::$CLASSPATH['OC_ATNotes'] = 'apps/atnotes/lib/atnotes.class.php';
+
+OCP\App::register(Array(
+ 'order' => 29,
+ 'id' => 'atnotes',
+ 'name' => 'ATNotes'
+));
+
+OCP\App::addNavigationEntry(Array(
+ 'id' => 'atnotes_index',
+ 'order' => 29,
+ 'href' => OCP\Util::linkTo('atnotes', 'atnotes.php'),
+ 'icon' => OCP\Util::imagePath('atnotes', 'icon.png'),
+ 'name' => 'ATNotes'
+)); \ No newline at end of file
diff --git a/apps/atnotes/appinfo/database.xml b/apps/atnotes/appinfo/database.xml
new file mode 100644
index 00000000000..d075c90f5dc
--- /dev/null
+++ b/apps/atnotes/appinfo/database.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<database>
+ <name>*dbname*</name>
+ <create>true</create>
+ <overwrite>false</overwrite>
+ <charset>utf8</charset>
+ <table>
+ <name>*dbprefix*atnotes</name>
+ <declaration>
+ <field>
+ <name>note_id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <unsigned>true</unsigned>
+ <length>4</length>
+ </field>
+ <field>
+ <name>oc_uid</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>64</length>
+ </field>
+ <field>
+ <name>create_ts</name>
+ <type>integer</type>
+ <notnull>true</notnull>
+ <length>11</length>
+ </field>
+ <field>
+ <name>update_ts</name>
+ <type>integer</type>
+ <notnull>false</notnull>
+ <default>0</default>
+ <length>11</length>
+ </field>
+ <field>
+ <name>note_title</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>255</length>
+ </field>
+ <field>
+ <name>note_content</name>
+ <type>clob</type>
+ <notnull>false</notnull>
+ </field>
+ <field>
+ <name>note_file</name>
+ <type>text</type>
+ <notnull>false</notnull>
+ <length>255</length>
+ </field>
+ <field>
+ <name>is_deleted</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <length>1</length>
+ </field>
+ <field>
+ <name>is_shared</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <length>1</length>
+ </field>
+ <index>
+ <name>note_pk</name>
+ <primary>true</primary>
+ <field>
+ <name>note_id</name>
+ <sorting>ascending</sorting>
+ </field>
+ </index>
+ </declaration>
+ </table>
+</database> \ No newline at end of file
diff --git a/apps/atnotes/appinfo/info.xml b/apps/atnotes/appinfo/info.xml
new file mode 100644
index 00000000000..0ea9a74200d
--- /dev/null
+++ b/apps/atnotes/appinfo/info.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<info>
+ <id>atnotes</id>
+ <name>ATNotes</name>
+ <description>ATNotes for Always Take Notes. Create/Edit/Delete notes in the application. You can export notes to save them in your ownCloud filesystem and you can access them through the WebDAV protocol.</description>
+ <version>1.0</version>
+ <licence>AGPL</licence>
+ <author>Xavier Beurois (www.djazz-lab.net)</author>
+ <require>4</require>
+ <shipped>true</shipped>
+</info>
diff --git a/apps/atnotes/atnotes.php b/apps/atnotes/atnotes.php
new file mode 100644
index 00000000000..b028f0adcee
--- /dev/null
+++ b/apps/atnotes/atnotes.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+* ownCloud - ATNotes plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+OCP\User::checkLoggedIn();
+OCP\App::checkAppEnabled('atnotes');
+
+$tmpl = new OCP\Template('atnotes', 'main.tpl', 'user');
+$tmpl->assign('notes_list', OC_ATNotes::getNotesList());
+$tmpl->printPage();
diff --git a/apps/atnotes/css/atnotes.min.css b/apps/atnotes/css/atnotes.min.css
new file mode 100644
index 00000000000..b98a2c50a80
--- /dev/null
+++ b/apps/atnotes/css/atnotes.min.css
@@ -0,0 +1,22 @@
+/**
+* ownCloud - ATNotes plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+#atnotes_container{min-width:830px}#atnotes_container div#controls div.title{color:#BBB;font-style:italic;padding:.7em .5em .5em}#atnotes_container ul.atnotes-noteslist{background:none repeat scroll 0 0 #f8f8f8;border-right:1px solid #DDD;overflow:auto;position:fixed;top:6.3em;width:20em;box-shadow:-2px -3px 7px #000;z-index:2}#atnotes_container ul.atnotes-noteslist li.atnotes-elt{border-bottom:1px solid #CCC;padding:.3em .8em 1em;-moz-transition:background-color 500ms ease 0s;background:none repeat scroll 0 0 #f8f8f8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#atnotes_container ul.atnotes-noteslist li.atnotes-elt:hover,#atnotes_container ul.atnotes-noteslist li.atnotes-elt.active{background:none repeat scroll 0 0 #eee}#atnotes_container ul.atnotes-noteslist li.atnotes-elt:hover div{cursor:pointer}#atnotes_container ul.atnotes-noteslist li.atnotes-elt div.atnotes-elt-title{font-weight:bold;white-space:pre-wrap;width:18.4em}#atnotes_container ul.atnotes-noteslist li.atnotes-elt div.atnotes-elt-state{float:left;width:10em;font-style:italic;color:#AAA}#atnotes_container ul.atnotes-noteslist li.atnotes-elt div.atnotes-elt-date{color:#999;float:right;width:7em;text-align:right}#atnotes_container ul.atnotes-noteslist li.atnotes-elt div.atnotes-elt-prerender{display:none}#atnotes_container div.atnotes-notesedit{left:20em;position:absolute;top:2.9em}#atnotes_container div.atnotes-notesedit div.anotes-notesedit-states{box-shadow:0 4px 7px #AAA}#atnotes_container div.atnotes-notesedit div.anotes-notesedit-states div.atnotes-saved{float:right}#atnotes_container div.atnotes-notesedit div.anotes-notesedit-title,#atnotes_container div.atnotes-notesedit div.anotes-notesedit-states{padding:.5em 2em;background-color:#FFF;border-bottom:1px solid #CCC;height:1.8em;z-index:1;min-width:350px}#atnotes_container div.atnotes-notesedit div.anotes-notesedit-states{padding:.5em 2em .5em 22em}#atnotes_container div.atnotes-notesedit div.anotes-notesedit-title input#note_title{box-shadow:none;background:0;-moz-border-radius:0;-webkit-border-radius:0;padding:0;margin:0;border:0 none}#atnotes_container div.atnotes-notesedit div.anotes-notesedit-title div.atnotes-actions-btns{float:right;margin-top:.5em}#atnotes_container div.atnotes-notesedit div.anotes-notesedit-title div.atnotes-actions-btns img{cursor:pointer}#atnotes_container div.atnotes-notesedit div.anotes-notesedit-title div.atnotes-actions-btns img.atnotes-actions-list{margin-right:1em}#atnotes_container div.atnotes-notesedit div.anotes-notesedit-title div.atnotes-actions-btns div.atnotes-actions-ddmenu{display:none}#atnotes_container div.jqte{left:20em;position:absolute;top:5.8em;border:0 none;resize:none;min-width:350px}#save_dialog div.atnotes-explorer{background-color:#FFF;border:1px solid #AAA;height:100%;width:100%;overflow-x:auto;overflow-y:visible}#save_dialog div.atnotes-explorer table{width:100%}#save_dialog div.atnotes-explorer table tr td.filename{width:70%;background-position:0 .1em;background-repeat:no-repeat;padding-left:1.7em;cursor:pointer}#save_dialog div.atnotes-explorer table tr td.filename span{cursor:pointer}#save_dialog div.atnotes-explorer table tr td.filesize{width:10%;text-align:center}#save_dialog div.atnotes-explorer table tr td.date{width:20%;text-align:center}.ui-tooltip-content ul li:hover{color:#AAA;background-color:#1a1a1a}
diff --git a/apps/atnotes/css/jquery-te-1.0.3.min.css b/apps/atnotes/css/jquery-te-1.0.3.min.css
new file mode 100644
index 00000000000..b60e50b1590
--- /dev/null
+++ b/apps/atnotes/css/jquery-te-1.0.3.min.css
@@ -0,0 +1,11 @@
+/*!
+ * http://jqueryte.com
+ * jQuery Text Editor 1.0.2
+ * Copyright (C) 2012, Fatih Koca (fatihkoca@me.com), AUTHOR.txt (http://jqueryte.com/about)
+ * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+This program 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 General Public License for more details.
+You should have received a copy of the GNU General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+*/
+
+.jqte{overflow:hidden}.jqte *{font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#333}.jqte_Panel{height:25px;padding:5px 8px}.jqte_Panel a{display:block;float:left;width:22px;height:22px;border:#FFF 1px solid;text-align:center;background:url('/apps/atnotes/img/jquery-te-toolbar.png') no-repeat;font-weight:bold;cursor:pointer;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px}.jqte_Panel a.bold{background-position:0 0}.jqte_Panel a.italic{background-position:-22px 0}.jqte_Panel a.underline{background-position:-44px 0}.jqte_Panel a.orderedlist{background-position:-66px 0}.jqte_Panel a.unorderedlist{background-position:-88px 0}.jqte_Panel a.subscript{background-position:-110px 0}.jqte_Panel a.superscript{background-position:-132px 0}.jqte_Panel a.strike{background-position:-154px 0}.jqte_Panel a.remove{background-position:-176px 0}.jqte_Panel a.rule{background-position:-198px 0}.jqte_Panel a:hover,.jqte_Active{border:#CCC 1px solid!important;box-shadow:inset 0 0 2px #999;background-color:#FFF}.jqte_Panel a:active,.jqte_Active{background-color:#DDD!important}.jqte_Content{overflow-y:auto;position:absolute;top:3.3em;left:.7em;padding-right:.3em}.jqte_Content div,.jqte_Content p{margin:7px 0}.jqte_Content ul{list-style-type:disc}.jqte_Content ol{list-style-type:decimal}.jqte_Content ol,.jqte_Content ul{list-style-position:inside} \ No newline at end of file
diff --git a/apps/atnotes/css/jquery.qtip.min.css b/apps/atnotes/css/jquery.qtip.min.css
new file mode 100644
index 00000000000..3f5e39adc73
--- /dev/null
+++ b/apps/atnotes/css/jquery.qtip.min.css
@@ -0,0 +1 @@
+.ui-tooltip,.qtip{position:absolute;left:-28000px;top:-28000px;display:none;max-width:280px;min-width:50px;font-size:10.5px;line-height:12px;border-width:1px;border-style:solid;}.ui-tooltip-fluid{display:block;visibility:hidden;position:static!important;float:left!important;}.ui-tooltip-content{position:relative;padding:5px 9px;overflow:hidden;text-align:left;word-wrap:break-word;overflow:hidden;}.ui-tooltip-titlebar{position:relative;min-height:14px;padding:5px 35px 5px 10px;overflow:hidden;border-width:0 0 1px;font-weight:bold;}.ui-tooltip-titlebar+.ui-tooltip-content{border-top-width:0!important;}/*!Default close button class */ .ui-tooltip-titlebar .ui-state-default{position:absolute;right:4px;top:50%;margin-top:-9px;cursor:pointer;outline:medium none;border-width:1px;border-style:solid;}* html .ui-tooltip-titlebar .ui-state-default{top:16px;}.ui-tooltip-titlebar .ui-icon,.ui-tooltip-icon .ui-icon{display:block;text-indent:-1000em;}.ui-tooltip-icon,.ui-tooltip-icon .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;}.ui-tooltip-icon .ui-icon{width:18px;height:14px;text-align:center;text-indent:0;font:normal bold 10px/13px Tahoma,sans-serif;color:inherit;background:transparent none no-repeat -100em -100em;}/*!Default tooltip style */ .ui-tooltip-default{border-color:#F1D031;background-color:#FFFFA3;color:#555;}.ui-tooltip-default .ui-tooltip-titlebar{background-color:#FFEF93;}.ui-tooltip-default .ui-tooltip-icon{border-color:#CCC;background:#F1F1F1;color:#777;}.ui-tooltip-default .ui-tooltip-titlebar .ui-state-hover{border-color:#AAA;color:#111;}/*!Light tooltip style */ .ui-tooltip-light{background-color:white;border-color:#E2E2E2;color:#454545;}.ui-tooltip-light .ui-tooltip-titlebar{background-color:#f1f1f1;}/*!Dark tooltip style */ .ui-tooltip-dark{background-color:#505050;border-color:#303030;color:#f3f3f3;}.ui-tooltip-dark .ui-tooltip-titlebar{background-color:#404040;}.ui-tooltip-dark .ui-tooltip-icon{border-color:#444;}.ui-tooltip-dark .ui-tooltip-titlebar .ui-state-hover{border-color:#303030;}/*!Cream tooltip style */ .ui-tooltip-cream{background-color:#FBF7AA;border-color:#F9E98E;color:#A27D35;}.ui-tooltip-cream .ui-tooltip-titlebar{background-color:#F0DE7D;}.ui-tooltip-cream .ui-state-default .ui-tooltip-icon{background-position:-82px 0;}/*!Red tooltip style */ .ui-tooltip-red{background-color:#F78B83;border-color:#D95252;color:#912323;}.ui-tooltip-red .ui-tooltip-titlebar{background-color:#F06D65;}.ui-tooltip-red .ui-state-default .ui-tooltip-icon{background-position:-102px 0;}.ui-tooltip-red .ui-tooltip-icon{border-color:#D95252;}.ui-tooltip-red .ui-tooltip-titlebar .ui-state-hover{border-color:#D95252;}/*!Green tooltip style */ .ui-tooltip-green{background-color:#CAED9E;border-color:#90D93F;color:#3F6219;}.ui-tooltip-green .ui-tooltip-titlebar{background-color:#B0DE78;}.ui-tooltip-green .ui-state-default .ui-tooltip-icon{background-position:-42px 0;}/*!Blue tooltip style */ .ui-tooltip-blue{background-color:#E5F6FE;border-color:#ADD9ED;color:#5E99BD;}.ui-tooltip-blue .ui-tooltip-titlebar{background-color:#D0E9F5;}.ui-tooltip-blue .ui-state-default .ui-tooltip-icon{background-position:-2px 0;}/*!Add shadows to your tooltips in:FF3+,Chrome 2+,Opera 10.6+,IE6+,Safari 2+*/ .ui-tooltip-shadow{-webkit-box-shadow:1px 1px 3px 1px rgba(0,0,0,0.15);-moz-box-shadow:1px 1px 3px 1px rgba(0,0,0,0.15);box-shadow:1px 1px 3px 1px rgba(0,0,0,0.15);}.ui-tooltip-shadow .ui-tooltip-titlebar,.ui-tooltip-shadow .ui-tooltip-content{filter:progid:DXImageTransform.Microsoft.Shadow(Color='gray',Direction=135,Strength=3);-ms-filter:"progid:DXImageTransform.Microsoft.Shadow(Color='gray',Direction=135,Strength=3)";_margin-bottom:-3px;.margin-bottom:-3px;}/*!Add rounded corners to your tooltips in:FF3+,Chrome 2+,Opera 10.6+,IE9+,Safari 2+*/ .ui-tooltip-rounded,.ui-tooltip-tipsy,.ui-tooltip-youtube,.ui-tooltip-youtube>div,.ui-tooltip-bootstrap{-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;}/*!Youtube tooltip style */ .ui-tooltip-youtube{-webkit-box-shadow:0 0 3px #333;-moz-box-shadow:0 0 3px #333;box-shadow:0 0 3px #333;color:white;border-color:#CCC;background:transparent;background:rgba(0,0,0,0.85);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#D9000000,endColorstr=#D9000000);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#D9000000,endColorstr=#D9000000)";}.ui-tooltip-youtube .ui-tooltip-titlebar{background-color:transparent;}.ui-tooltip-youtube .ui-tooltip-icon{border-color:#222;}.ui-tooltip-youtube .ui-tooltip-titlebar .ui-state-hover{border-color:#303030;}.ui-tooltip-jtools{background:#232323;background:rgba(0,0,0,0.7);background-image:-moz-linear-gradient(top,#717171,#232323);background-image:-webkit-gradient(linear,left top,left bottom,from(#717171),to(#232323));border:2px solid #ddd;border:2px solid rgba(241,241,241,1);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-webkit-box-shadow:0 0 12px #333;-moz-box-shadow:0 0 12px #333;box-shadow:0 0 12px #333;}.ui-tooltip-jtools .ui-tooltip-titlebar{background-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171,endColorstr=#4A4A4A);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#717171,endColorstr=#4A4A4A)";}.ui-tooltip-jtools .ui-tooltip-content{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A,endColorstr=#232323);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#4A4A4A,endColorstr=#232323)";}.ui-tooltip-jtools .ui-tooltip-titlebar,.ui-tooltip-jtools .ui-tooltip-content{background:transparent;color:white;border:0 dashed transparent;}.ui-tooltip-jtools .ui-tooltip-icon{border-color:#555;}.ui-tooltip-jtools .ui-tooltip-titlebar .ui-state-hover{border-color:#333;}.ui-tooltip-cluetip{-webkit-box-shadow:4px 4px 5px rgba(0,0,0,0.4);-moz-box-shadow:4px 4px 5px rgba(0,0,0,0.4);box-shadow:4px 4px 5px rgba(0,0,0,0.4);background-color:#D9D9C2;color:#111;border:0 dashed transparent;}.ui-tooltip-cluetip .ui-tooltip-titlebar{background-color:#87876A;color:white;border:0 dashed transparent;}.ui-tooltip-cluetip .ui-tooltip-icon{border-color:#808064;}.ui-tooltip-cluetip .ui-tooltip-titlebar .ui-state-hover{border-color:#696952;color:#696952;}.ui-tooltip-tipsy{background:black;background:rgba(0,0,0,.87);color:white;border:0 solid transparent;font-size:11px;font-family:'Lucida Grande',sans-serif;font-weight:bold;line-height:16px;text-shadow:0 1px black;}.ui-tooltip-tipsy .ui-tooltip-titlebar{padding:6px 35px 0 10;background-color:transparent;}.ui-tooltip-tipsy .ui-tooltip-content{padding:6px 10;}.ui-tooltip-tipsy .ui-tooltip-icon{border-color:#222;text-shadow:none;}.ui-tooltip-tipsy .ui-tooltip-titlebar .ui-state-hover{border-color:#303030;}.ui-tooltip-tipped{border:3px solid #959FA9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-color:#F9F9F9;color:#454545;font-weight:normal;font-family:serif;}.ui-tooltip-tipped .ui-tooltip-titlebar{border-bottom-width:0;color:white;background:#3A79B8;background-image:-moz-linear-gradient(top,#3A79B8,#2E629D);background-image:-webkit-gradient(linear,left top,left bottom,from(#3A79B8),to(#2E629D));filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8,endColorstr=#2E629D);-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#3A79B8,endColorstr=#2E629D)";}.ui-tooltip-tipped .ui-tooltip-icon{border:2px solid #285589;background:#285589;}.ui-tooltip-tipped .ui-tooltip-icon .ui-icon{background-color:#FBFBFB;color:#555;}.ui-tooltip-bootstrap{font-size:13px;line-height:18px;color:#333;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;}.ui-tooltip-bootstrap .ui-tooltip-titlebar{font-size:18px;line-height:22px;border-bottom:1px solid #ccc;background-color:transparent;}.ui-tooltip-bootstrap .ui-tooltip-titlebar .ui-state-default{right:9px;top:49%;border-style:none;}.ui-tooltip-bootstrap .ui-tooltip-icon{background:white;}.ui-tooltip-bootstrap .ui-tooltip-icon .ui-icon{width:auto;height:auto;float:right;font-size:20px;font-weight:bold;line-height:18px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20);}.ui-tooltip-bootstrap .ui-tooltip-icon .ui-icon:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40);}.ui-tooltip:not(.ie9haxors) div.ui-tooltip-content,.ui-tooltip:not(.ie9haxors) div.ui-tooltip-titlebar{filter:none;-ms-filter:none;} \ No newline at end of file
diff --git a/apps/atnotes/img/action.png b/apps/atnotes/img/action.png
new file mode 100644
index 00000000000..5b24257a0e7
--- /dev/null
+++ b/apps/atnotes/img/action.png
Binary files differ
diff --git a/apps/atnotes/img/icon.png b/apps/atnotes/img/icon.png
new file mode 100644
index 00000000000..cce8f39255e
--- /dev/null
+++ b/apps/atnotes/img/icon.png
Binary files differ
diff --git a/apps/atnotes/img/jquery-te-toolbar.png b/apps/atnotes/img/jquery-te-toolbar.png
new file mode 100644
index 00000000000..a330302d9f1
--- /dev/null
+++ b/apps/atnotes/img/jquery-te-toolbar.png
Binary files differ
diff --git a/apps/atnotes/img/save.png b/apps/atnotes/img/save.png
new file mode 100644
index 00000000000..49581985227
--- /dev/null
+++ b/apps/atnotes/img/save.png
Binary files differ
diff --git a/apps/atnotes/js/atnotes.min.js b/apps/atnotes/js/atnotes.min.js
new file mode 100644
index 00000000000..007c53c6fdc
--- /dev/null
+++ b/apps/atnotes/js/atnotes.min.js
@@ -0,0 +1,22 @@
+/**
+* ownCloud - ATNotes plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+function atnotes(){if(typeof atnotes.initialized=="undefined"){atnotes.prototype.A=function(c,g){if(c.val().length!=0){if(c.attr("rel")==0){if($("ul.atnotes-noteslist li.atnotes-elt.active").length!=0){$("ul.atnotes-noteslist li.atnotes-elt.active div.atnotes-elt-title").html(c.val());$("ul.atnotes-noteslist li.atnotes-elt.active div.atnotes-elt-prerender").html(g.html())}else{this.N(1)}}else{$("ul.atnotes-noteslist li.atnotes-elt.active div.atnotes-elt-title").html(c.val());$("ul.atnotes-noteslist li.atnotes-elt.active div.atnotes-elt-state").html("Unsaved");$("ul.atnotes-noteslist li.atnotes-elt.active div.atnotes-elt-date").html(this.B());$("ul.atnotes-noteslist li.atnotes-elt.active div.atnotes-elt-prerender").html(g.html())}$(window).bind("beforeunload",function(){return false})}};atnotes.prototype.B=function(){var j=new Date(),k,b,c;k=j.getDate();if(k<10){k="0"+k}b=parseInt(j.getMonth()+1);if(b<10){b="0"+b}c=j.getFullYear();j=b+"/"+k+"/"+c;return j};atnotes.prototype.C=function(g,e){if(e.val().length==0){e.val("Untitled note")}this.A(e,g)};atnotes.prototype.D=function(){d=$(".atnotes-elt.active");a=this;if(parseInt(d.attr("rel"))>0){$.ajax({type:"POST",dataType:"json",url:OC.linkTo("atnotes","ajax/delete.php"),data:{i:parseInt(d.attr("rel"))},async:false,success:function(c){if(c.e==0){a.E(d,1)}}})}else{a.E(d,1)}};atnotes.prototype.E=function(g,e){if(e==1){g.remove()}$("#note_title").val("");$("#note_title").attr("rel","0");$("#note_path").val("");$(".jqte_Content").html("");$("#note_title").focus()};atnotes.prototype.F=function(c,g){if(c.val().length==0){c.val("Untitled note");this.A($("#note_title"),g)}$.ajax({type:"POST",dataType:"json",url:OC.linkTo("atnotes","ajax/save.php"),data:{i:c.attr("rel"),t:c.val(),c:g.html()},async:false,error:function(b){alert(b.responseText)},success:function(b){if(!b.e){$("ul.atnotes-noteslist li.atnotes-elt.active div.atnotes-elt-state").empty();$("ul.atnotes-noteslist li.atnotes-elt.active").attr("rel",b.i);$("ul.atnotes-noteslist li.atnotes-elt.active").removeClass("unsaved");c.attr("rel",b.i);$(".atnotes-saved").empty().fadeIn(400);$(".atnotes-saved").css("color","#009700");$(".atnotes-saved").html("Saved !").delay(1000).fadeOut(400);if($("ul.atnotes-noteslist li.atnotes-elt.unsaved").length==0){$(window).unbind("beforeunload")}}}})};atnotes.prototype.G=function(i,c,h){if(h.html().length==0){$(".atnotes-saved").empty().fadeIn(400);$(".atnotes-saved").css("color","#990000");$(".atnotes-saved").html("File not exported: empty content ...").delay(1000).fadeOut(400)}else{$.ajax({type:"POST",dataType:"json",url:OC.linkTo("atnotes","ajax/check.php"),data:{p:i.val(),t:c.val()},async:false,success:function(b){f=true;if(b.e){if(!confirm("The file already exists. Are you sure to override it ?")){f=false}}if(f){$.ajax({type:"POST",dataType:"json",url:OC.linkTo("atnotes","ajax/export.php"),data:{p:b.p,t:c.val(),c:h.html()},async:false,success:function(e){$("#save_dialog").dialog("close")}})}}})}};atnotes.prototype.H=function(c,b){c.css("height",$("#content").height()-b+"px")};atnotes.prototype.I=function(e,g){if(typeof g=="undefined"){g="/"}a=this;e=e.find("div.atnotes-explorer table");$.ajax({type:"POST",dataType:"json",url:OC.linkTo("atnotes","ajax/browse.php"),data:{p:g},async:false,success:function(b){e.empty();$.each(b,function(c,i){e.append(i)});t=e.find("td.filename");t.click(function(c){var i=this;setTimeout(function(){var h=parseInt($(this).data("double"),10);if(h>0){$(this).data("double",h-1)}else{$("#note_path").val($(this).attr("data-rel"))}},300)}).dblclick(function(c){$(this).data("double",2);$("#note_path").val($(this).attr("data-rel"));a.I($("#save_dialog"),$(this).attr("data-rel"))})}})};atnotes.prototype.J=function(c){if(c.val().length==0){c.val("Untitled note")}$("#save_dialog").dialog("open")};atnotes.prototype.K=function(i,c,h){c.val(i.find("div.atnotes-elt-title").text());c.attr("rel",i.attr("rel"));h.html(i.find("div.atnotes-elt-prerender").html())};atnotes.prototype.N=function(c){$(".active").removeClass("active");if(c==1){$("ul.atnotes-noteslist").prepend('<li class="atnotes-elt active unsaved" rel="0"><div class="atnotes-elt-title">'+$("#note_title").val()+'</div><div class="atnotes-elt-state">Unsaved</div><div class="atnotes-elt-date">'+this.B()+'</div><div class="atnotes-elt-prerender">'+$(".jqte_Content").html()+"</div></li>")}else{$("ul.atnotes-noteslist").prepend('<li class="atnotes-elt active unsaved" rel="0"><div class="atnotes-elt-title">New note</div><div class="atnotes-elt-state">Unsaved</div><div class="atnotes-elt-date">'+this.B()+'</div><div class="atnotes-elt-prerender"></div></li>');this.E(0,0)}$("ul.atnotes-noteslist li.atnotes-elt.active").bind("click",function(){$(".active").removeClass("active");$(this).addClass("active");A.K($(this),$("#note_title"),$(".jqte_Content"))});$(window).bind("beforeunload",function(){return false})};atnotes.prototype.W=function(c,b){c.css("width",$("#content").width()-$(".atnotes-noteslist").width()-b+"px")}}}$(document).ready(function(){A=new atnotes();$(".atnotes-editor").jqte();$(window).resize(function(){A.H($(".atnotes-noteslist"),36);A.W($(".atnotes-notesedit"),0);A.H($(".jqte"),75);A.W($(".jqte"),0);A.H($(".jqte_Content"),122);A.W($(".jqte_Content"),0);$("#note_title").css("width",$(".anotes-notesedit-title").width()-70+"px")});$(window).trigger("resize");$("#save_dialog").dialog({autoOpen:false,height:300,width:550,modal:true,resizable:false,buttons:{Cancel:function(){$("#save_dialog").dialog("close")},Ok:function(){if($("#note_path").val().length==0){$("#note_path").val("/")}A.G($("#note_path"),$("#note_title"),$(".jqte_Content"))}},open:function(g,e){A.I($(this))}});$("#note_title").change(function(){A.A($(this),$(".jqte_Content"))});$("#note_title").keyup(function(){$(this).change()});$(".jqte_Content").keyup(function(){A.C($(this),$("#note_title"))});$(".atnotes-actions-btns img.atnotes-save").bind("click",function(){A.F($("#note_title"),$(".jqte_Content"))});$(".atnotes-elt").bind("click",function(){$(".active").removeClass("active");$(this).addClass("active");A.K($(this),$("#note_title"),$(".jqte_Content"))});$(".atnotes-actions-list").qtip({prerender:true,overwrite:false,content:{text:$(".atnotes-actions-ddmenu").html()},position:{my:"top right",at:"bottom right"},events:{show:function(g,e){$(".atnotes-delete").bind("click",function(){A.D()});$(".atnotes-new").bind("click",function(){A.N(0)})},hide:function(g,e){$(".atnotes-delete").unbind("click");$(".atnotes-new").unbind("click")}},hide:{delay:100,event:"unfocus mouseleave",fixed:true},style:{classes:"ui-tooltip-shadow ui-tooltip-rounded ui-tooltip-youtube",tip:false,width:150}});$("#note_title").focus()}); \ No newline at end of file
diff --git a/apps/atnotes/js/jquery-te-1.0.3.min.js b/apps/atnotes/js/jquery-te-1.0.3.min.js
new file mode 100644
index 00000000000..b94db89e8ef
--- /dev/null
+++ b/apps/atnotes/js/jquery-te-1.0.3.min.js
@@ -0,0 +1,10 @@
+/*!
+ * http://jqueryte.com
+ * jQuery TE 1.0.3
+ * Copyright (C) 2012, Fatih Koca (fatihkoca@me.com), AUTHOR.txt (http://jqueryte.com/about)
+ * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+This program 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 General Public License for more details.
+You should have received a copy of the GNU General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+*/
+(function(a){a.fn.jqte=function(b){var c=a.extend({css:"jqte",b:true,i:true,u:true,ol:true,ul:true,sub:true,sup:true,strike:true,remove:true,rule:true},b);var d=a.extend({b:{cls:"bold",command:"Bold",key:"B",tags:["b","strong"]},i:{cls:"italic",command:"Italic",key:"I",tags:["i","em"]},u:{cls:"underline",command:"Underline",key:"U",tags:["u"]},ol:{cls:"orderedlist",command:"insertorderedlist",key:"O",tags:["ol"]},ul:{cls:"unorderedlist",command:"insertunorderedlist",key:"F",tags:["ul"]},sub:{cls:"subscript",command:"subscript",key:"(",tags:["sub"]},sup:{cls:"superscript",command:"superscript",key:"&",tags:["sup"]},strike:{cls:"strike",command:"strikeThrough",key:"S",tags:["strike"]},remove:{cls:"remove",command:"removeformat",key:".",css:false},hr:{cls:"rule",command:"inserthorizontalrule",key:"H",tags:["hr"]}},b);return this.each(function(){function i(a){var b,c,d=navigator.userAgent.toLowerCase();if(window.getSelection){c=window.getSelection();if(c.getRangeAt){b=c.getRangeAt(0)}if(b){c.removeAllRanges();c.addRange(b)}if(!d.match(/msie/))document.execCommand("StyleWithCSS",false,false);document.execCommand(a,false,null)}else if(document.selection&&document.selection.createRange&&document.selection.type!="None"){b=document.selection.createRange();b.execCommand(a,false,null)}}function k(b,c){var d=false,e=j(),f,g;a.each(b,function(b,c){f=e.prop("tagName").toLowerCase();g=e.attr("style");if(f==c)d=true;else{e.parents().each(function(){f=a(this).prop("tagName").toLowerCase();if(f==c)d=true})}});return d}function l(b){if(c.b)k(d.b.tags,d.b.css)?f.find("."+d.b.cls).addClass(h):a("."+d.b.cls).removeClass(h);if(c.i)k(d.i.tags,d.i.css)?f.find("."+d.i.cls).addClass(h):a("."+d.i.cls).removeClass(h);if(c.u)k(d.u.tags,d.u.css)?f.find("."+d.u.cls).addClass(h):a("."+d.u.cls).removeClass(h);if(c.ol)k(d.ol.tags,d.ol.css)?f.find("."+d.ol.cls).addClass(h):a("."+d.ol.cls).removeClass(h);if(c.ul)k(d.ul.tags,d.ul.css)?f.find("."+d.ul.cls).addClass(h):a("."+d.ul.cls).removeClass(h);if(c.sub)k(d.sub.tags,d.sub.css)?f.find("."+d.sub.cls).addClass(h):a("."+d.sub.cls).removeClass(h);if(c.sup)k(d.sup.tags,d.sup.css)?f.find("."+d.sup.cls).addClass(h):a("."+d.sup.cls).removeClass(h);if(c.strike)k(d.strike.tags,d.strike.css)?f.find("."+d.strike.cls).addClass(h):a("."+d.strike.cls).removeClass(h)}function m(){var a=g.html().replace(/<p><\/p>/g,"").replace(/ /g," ").replace(/<p> <\/p>/g,""),c,d;c=[/\<div>(.*?)\<\/div>/ig,/\<br>(.*?)\<br>/ig,/\<br\/>(.*?)\<br\/>/ig,/\<strong>(.*?)\<\/strong>/ig,/\<em>(.*?)\<\/em>/ig];d=["<p>$1</p>","<p>$1</p>","<p>$1</p>","<b>$1</b>","<i>$1</i>"];for(var e=0;e<c.length;e++){a=a.replace(c[e],d[e])}b.val(g.html())}var b=a(this);b.hide().before('<div class="'+c.css+'" ></div>');var e=b.prev("."+c.css);e.html('<div class="'+c.css+"_Panel"+'" unselectable="on"></div> <div class="'+c.css+"_Content"+'" contenteditable="true"></div>');var f=e.find("."+c.css+"_Panel");var g=e.find("."+c.css+"_Content");var h=c.css+"_Active";g.html(b.val());f.bind("selectstart mousedown",function(a){a.preventDefault()});if(c.b)f.append('<a class="'+d.b.cls+'" toggletag="'+d.b.command+'" unselectable="on"></a>');if(c.i)f.append('<a class="'+d.i.cls+'" toggletag="'+d.i.command+'" unselectable="on"></a>');if(c.u)f.append('<a class="'+d.u.cls+'" toggletag="'+d.u.command+'" unselectable="on"></a>');if(c.ol)f.append('<a class="'+d.ol.cls+'" toggletag="'+d.ol.command+'" unselectable="on"></a>');if(c.ul)f.append('<a class="'+d.ul.cls+'" toggletag="'+d.ul.command+'" unselectable="on"></a>');if(c.sub)f.append('<a class="'+d.sub.cls+'" toggletag="'+d.sub.command+'" unselectable="on"></a>');if(c.sup)f.append('<a class="'+d.sup.cls+'" toggletag="'+d.sup.command+'" unselectable="on"></a>');if(c.strike)f.append('<a class="'+d.strike.cls+'" toggletag="'+d.strike.command+'" unselectable="on"></a>');if(c.remove)f.append('<a class="'+d.remove.cls+'" toggletag="'+d.remove.command+'" unselectable="on"></a>');if(c.rule)f.append('<a class="'+d.hr.cls+'" toggletag="'+d.hr.command+'" unselectable="on"></a>');g.focusout(function(){f.find("a").removeClass(h)});var j=function(){var b,c;if(window.getSelection){c=getSelection();b=c.anchorNode}if(!b&&document.selection){c=document.selection;var d=c.getRangeAt?c.getRangeAt(0):c.createRange();b=d.commonAncestorContainer?d.commonAncestorContainer:d.parentElement?d.parentElement():d.item(0)}if(b){return b.nodeName=="#text"?a(b.parentNode):a(b)}};g.bind("mouseup keyup",l);f.find("a").click(function(){$('.jqte_Content').focus();i(a(this).attr("toggletag"));m();a(this).hasClass(h)||a(this).is("[toggletag="+d.remove.command+"]")||a(this).is("[toggletag="+d.hr.command+"]")?a(this).removeClass(h):a(this).addClass(h)});a.ctrl=function(a,b,c){var d=false;g.keydown(function(e){if(!c)c=[];if(e.ctrlKey)d=true;if(e.keyCode==a.charCodeAt(0)&&d){b.apply(this,c);return false}}).keyup(function(a){if(a.ctrlKey)d=false})};a.ctrl(d.b.key,function(){i(d.b.command)});a.ctrl(d.i.key,function(){i(d.i.command)});a.ctrl(d.u.key,function(){i(d.u.command)});a.ctrl(d.ol.key,function(){i(d.ol.command)});a.ctrl(d.ul.key,function(){i(d.ul.command)});a.ctrl(d.sub.key,function(){i(d.sub.command)});a.ctrl(d.sup.key,function(){i(d.sup.command)});a.ctrl(d.strike.key,function(){i(d.strike.command)});a.ctrl(d.remove.key,function(){i(d.remove.command)});a.ctrl(d.hr.key,function(){i(d.hr.command)});g.bind("keypress keyup keydown drop",function(){setTimeout(m,0)})})}})(jQuery) \ No newline at end of file
diff --git a/apps/atnotes/js/jquery.qtip.min.js b/apps/atnotes/js/jquery.qtip.min.js
new file mode 100644
index 00000000000..5be4613f94b
--- /dev/null
+++ b/apps/atnotes/js/jquery.qtip.min.js
@@ -0,0 +1,13 @@
+/*
+* qTip2 - Pretty powerful tooltips
+* http://craigsworks.com/projects/qtip2/
+*
+* Version: nightly
+* Copyright 2009-2010 Craig Michael Thompson - http://craigsworks.com
+*
+* Dual licensed under MIT or GPLv2 licenses
+* http://en.wikipedia.org/wiki/MIT_License
+* http://en.wikipedia.org/wiki/GNU_General_Public_License
+*
+* Date: Sun May 13 12:07:36.0000000000 2012
+*//*jslint browser: true, onevar: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: true *//*global window: false, jQuery: false, console: false, define: false */(function(a){typeof define==="function"&&define.amd?define(["jquery"],a):a(jQuery)})(function(a){function y(e,h){var i,j,k,l,m,n=a(this),o=a(document.body),p=this===document?o:n,q=n.metadata?n.metadata(h.metadata):d,r=h.metadata.type==="html5"&&q?q[h.metadata.name]:d,s=n.data(h.metadata.name||"qtipopts");try{s=typeof s==="string"?(new Function("return "+s))():s}catch(u){v("Unable to parse HTML5 attribute data: "+s)}l=a.extend(b,{},f.defaults,h,typeof s==="object"?w(s):d,w(r||q)),j=l.position,l.id=e;if("boolean"===typeof l.content.text){k=n.attr(l.content.attr);if(l.content.attr!==c&&k)l.content.text=k;else{v("Unable to locate content for tooltip! Aborting render of tooltip on element: ",n);return c}}j.container.length||(j.container=o),j.target===c&&(j.target=p),l.show.target===c&&(l.show.target=p),l.show.solo===b&&(l.show.solo=j.container.closest("body")),l.hide.target===c&&(l.hide.target=p),l.position.viewport===b&&(l.position.viewport=j.container),j.container=j.container.eq(0),j.at=new g.Corner(j.at),j.my=new g.Corner(j.my);if(a.data(this,"qtip"))if(l.overwrite)n.qtip("destroy");else if(l.overwrite===c)return c;l.suppress&&(m=a.attr(this,"title"))&&a(this).removeAttr("title").attr(t,m),i=new x(n,l,e,!!k),a.data(this,"qtip",i),n.bind("remove.qtip-"+e+" removeqtip.qtip-"+e,function(){i.destroy()});return i}function x(r,s,v,x){function Q(){var b=[s.show.target[0],s.hide.target[0],y.rendered&&F.tooltip[0],s.position.container[0],s.position.viewport[0],window,document];y.rendered?a([]).pushStack(a.grep(b,function(a){return typeof a==="object"})).unbind(E):s.show.target.unbind(E+"-create")}function P(){function o(a){y.rendered&&D[0].offsetWidth>0&&y.reposition(a)}function n(a){if(D.hasClass(l))return c;clearTimeout(y.timers.inactive),y.timers.inactive=setTimeout(function(){y.hide(a)},s.hide.inactive)}function k(b){if(D.hasClass(l)||B||C)return c;var f=a(b.relatedTarget||b.target),g=f.closest(m)[0]===D[0],h=f[0]===e.show[0];clearTimeout(y.timers.show),clearTimeout(y.timers.hide);if(d.target==="mouse"&&g||s.hide.fixed&&(/mouse(out|leave|move)/.test(b.type)&&(g||h)))try{b.preventDefault(),b.stopImmediatePropagation()}catch(i){}else s.hide.delay>0?y.timers.hide=setTimeout(function(){y.hide(b)},s.hide.delay):y.hide(b)}function j(a){if(D.hasClass(l))return c;clearTimeout(y.timers.show),clearTimeout(y.timers.hide);var d=function(){y.toggle(b,a)};s.show.delay>0?y.timers.show=setTimeout(d,s.show.delay):d()}var d=s.position,e={show:s.show.target,hide:s.hide.target,viewport:a(d.viewport),document:a(document),body:a(document.body),window:a(window)},g={show:a.trim(""+s.show.event).split(" "),hide:a.trim(""+s.hide.event).split(" ")},i=a.browser.msie&&parseInt(a.browser.version,10)===6;D.bind("mouseenter"+E+" mouseleave"+E,function(a){var b=a.type==="mouseenter";b&&y.focus(a),D.toggleClass(p,b)}),s.hide.fixed&&(e.hide=e.hide.add(D),D.bind("mouseover"+E,function(){D.hasClass(l)||clearTimeout(y.timers.hide)})),/mouse(out|leave)/i.test(s.hide.event)?s.hide.leave==="window"&&e.window.bind("mouseout"+E+" blur"+E,function(a){/select|option/.test(a.target)&&!a.relatedTarget&&y.hide(a)}):/mouse(over|enter)/i.test(s.show.event)&&e.hide.bind("mouseleave"+E,function(a){clearTimeout(y.timers.show)}),(""+s.hide.event).indexOf("unfocus")>-1&&d.container.closest("html").bind("mousedown"+E,function(b){var c=a(b.target),d=y.rendered&&!D.hasClass(l)&&D[0].offsetWidth>0,e=c.parents(m).filter(D[0]).length>0;c[0]!==r[0]&&c[0]!==D[0]&&!e&&!r.has(c[0]).length&&!c.attr("disabled")&&y.hide(b)}),"number"===typeof s.hide.inactive&&(e.show.bind("qtip-"+v+"-inactive",n),a.each(f.inactiveEvents,function(a,b){e.hide.add(F.tooltip).bind(b+E+"-inactive",n)})),a.each(g.hide,function(b,c){var d=a.inArray(c,g.show),f=a(e.hide);d>-1&&f.add(e.show).length===f.length||c==="unfocus"?(e.show.bind(c+E,function(a){D[0].offsetWidth>0?k(a):j(a)}),delete g.show[d]):e.hide.bind(c+E,k)}),a.each(g.show,function(a,b){e.show.bind(b+E,j)}),"number"===typeof s.hide.distance&&e.show.add(D).bind("mousemove"+E,function(a){var b=G.origin||{},c=s.hide.distance,d=Math.abs;(d(a.pageX-b.pageX)>=c||d(a.pageY-b.pageY)>=c)&&y.hide(a)}),d.target==="mouse"&&(e.show.bind("mousemove"+E,function(a){h={pageX:a.pageX,pageY:a.pageY,type:"mousemove"}}),d.adjust.mouse&&(s.hide.event&&(D.bind("mouseleave"+E,function(a){(a.relatedTarget||a.target)!==e.show[0]&&y.hide(a)}),F.target.bind("mouseenter"+E+" mouseleave"+E,function(a){G.onTarget=a.type==="mouseenter"})),e.document.bind("mousemove"+E,function(a){y.rendered&&G.onTarget&&!D.hasClass(l)&&D[0].offsetWidth>0&&y.reposition(a||h)}))),(d.adjust.resize||e.viewport.length)&&(a.event.special.resize?e.viewport:e.window).bind("resize"+E,o),(e.viewport.length||i&&D.css("position")==="fixed")&&e.viewport.bind("scroll"+E,o)}function O(b,d){function g(b){function i(e){e&&(delete h[e.src],clearTimeout(y.timers.img[e.src]),a(e).unbind(E)),a.isEmptyObject(h)&&(y.redraw(),d!==c&&y.reposition(G.event),b())}var g,h={};if((g=f.find("img[src]:not([height]):not([width])")).length===0)return i();g.each(function(b,c){if(h[c.src]===e){var d=0,f=3;(function g(){if(c.height||c.width||d>f)return i(c);d+=1,y.timers.img[c.src]=setTimeout(g,700)})(),a(c).bind("error"+E+" load"+E,function(){i(this)}),h[c.src]=c}})}var f=F.content;if(!y.rendered||!b)return c;a.isFunction(b)&&(b=b.call(r,G.event,y)||""),b.jquery&&b.length>0?f.empty().append(b.css({display:"block"})):f.html(b),y.rendered<0?D.queue("fx",g):(C=0,g(a.noop));return y}function N(b,d){var e=F.title;if(!y.rendered||!b)return c;a.isFunction(b)&&(b=b.call(r,G.event,y));if(b===c||!b&&b!=="")return J(c);b.jquery&&b.length>0?e.empty().append(b.css({display:"block"})):e.html(b),y.redraw(),d!==c&&y.rendered&&D[0].offsetWidth>0&&y.reposition(G.event)}function M(a){var b=F.button,d=F.title;if(!y.rendered)return c;a?(d||L(),K()):b.remove()}function L(){var c=A+"-title";F.titlebar&&J(),F.titlebar=a("<div />",{"class":j+"-titlebar "+(s.style.widget?"ui-widget-header":"")}).append(F.title=a("<div />",{id:c,"class":j+"-title","aria-atomic":b})).insertBefore(F.content).delegate(".ui-tooltip-close","mousedown keydown mouseup keyup mouseout",function(b){a(this).toggleClass("ui-state-active ui-state-focus",b.type.substr(-4)==="down")}).delegate(".ui-tooltip-close","mouseover mouseout",function(b){a(this).toggleClass("ui-state-hover",b.type==="mouseover")}),s.content.title.button?K():y.rendered&&y.redraw()}function K(){var b=s.content.title.button,d=typeof b==="string",e=d?b:"Close tooltip";F.button&&F.button.remove(),b.jquery?F.button=b:F.button=a("<a />",{"class":"ui-state-default ui-tooltip-close "+(s.style.widget?"":j+"-icon"),title:e,"aria-label":e}).prepend(a("<span />",{"class":"ui-icon ui-icon-close",html:"&times;"})),F.button.appendTo(F.titlebar).attr("role","button").click(function(a){D.hasClass(l)||y.hide(a);return c}),y.redraw()}function J(a){F.title&&(F.titlebar.remove(),F.titlebar=F.title=F.button=d,a!==c&&y.reposition())}function I(){var a=s.style.widget;D.toggleClass(k,a).toggleClass(n,s.style.def&&!a),F.content.toggleClass(k+"-content",a),F.titlebar&&F.titlebar.toggleClass(k+"-header",a),F.button&&F.button.toggleClass(j+"-icon",!a)}function H(a){var b=0,c,d=s,e=a.split(".");while(d=d[e[b++]])b<e.length&&(c=d);return[c||s,e.pop()]}var y=this,z=document.body,A=j+"-"+v,B=0,C=0,D=a(),E=".qtip-"+v,F,G;y.id=v,y.destroyed=y.rendered=c,y.elements=F={target:r},y.timers={img:{}},y.options=s,y.checks={},y.plugins={},y.cache=G={event:{},target:a(),disabled:c,attr:x,onTarget:c},y.checks.builtin={"^id$":function(d,e,g){var h=g===b?f.nextid:g,i=j+"-"+h;h!==c&&h.length>0&&!a("#"+i).length&&(D[0].id=i,F.content[0].id=i+"-content",F.title[0].id=i+"-title")},"^content.text$":function(a,b,c){O(c)},"^content.title.text$":function(a,b,c){if(!c)return J();!F.title&&c&&L(),N(c)},"^content.title.button$":function(a,b,c){M(c)},"^position.(my|at)$":function(a,b,c){"string"===typeof c&&(a[b]=new g.Corner(c))},"^position.container$":function(a,b,c){y.rendered&&D.appendTo(c)},"^show.ready$":function(){y.rendered?y.toggle(b):y.render(1)},"^style.classes$":function(a,b,c){D.attr("class",j+" qtip ui-helper-reset "+c)},"^style.widget|content.title":I,"^events.(render|show|move|hide|focus|blur)$":function(b,c,d){D[(a.isFunction(d)?"":"un")+"bind"]("tooltip"+c,d)},"^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)":function(){var a=s.position;D.attr("tracking",a.target==="mouse"&&a.adjust.mouse),Q(),P()}},a.extend(y,{render:function(d){if(y.rendered)return y;var e=s.content.text,f=s.content.title.text,h=s.position,i=a.Event("tooltiprender");a.attr(r[0],"aria-describedby",A),D=F.tooltip=a("<div/>",{id:A,"class":j+" qtip ui-helper-reset "+n+" "+s.style.classes+" "+j+"-pos-"+s.position.my.abbrev(),width:s.style.width||"",height:s.style.height||"",tracking:h.target==="mouse"&&h.adjust.mouse,role:"alert","aria-live":"polite","aria-atomic":c,"aria-describedby":A+"-content","aria-hidden":b}).toggleClass(l,G.disabled).data("qtip",y).appendTo(s.position.container).append(F.content=a("<div />",{"class":j+"-content",id:A+"-content","aria-atomic":b})),y.rendered=-1,B=C=1,f&&(L(),a.isFunction(f)||N(f,c)),a.isFunction(e)||O(e,c),y.rendered=b,I(),a.each(s.events,function(b,c){a.isFunction(c)&&D.bind(b==="toggle"?"tooltipshow tooltiphide":"tooltip"+b,c)}),a.each(g,function(){this.initialize==="render"&&this(y)}),P(),D.queue("fx",function(a){i.originalEvent=G.event,D.trigger(i,[y]),B=C=0,y.redraw(),(s.show.ready||d)&&y.toggle(b,G.event,c),a()});return y},get:function(a){var b,c;switch(a.toLowerCase()){case"dimensions":b={height:D.outerHeight(),width:D.outerWidth()};break;case"offset":b=g.offset(D,s.position.container);break;default:c=H(a.toLowerCase()),b=c[0][c[1]],b=b.precedance?b.string():b}return b},set:function(e,f){function m(a,b){var c,d,e;for(c in k)for(d in k[c])if(e=(new RegExp(d,"i")).exec(a))b.push(e),k[c][d].apply(y,b)}var g=/^position\.(my|at|adjust|target|container)|style|content|show\.ready/i,h=/^content\.(title|attr)|style/i,i=c,j=c,k=y.checks,l;"string"===typeof e?(l=e,e={},e[l]=f):e=a.extend(b,{},e),a.each(e,function(b,c){var d=H(b.toLowerCase()),f;f=d[0][d[1]],d[0][d[1]]="object"===typeof c&&c.nodeType?a(c):c,e[b]=[d[0],d[1],c,f],i=g.test(b)||i,j=h.test(b)||j}),w(s),B=C=1,a.each(e,m),B=C=0,y.rendered&&D[0].offsetWidth>0&&(i&&y.reposition(s.position.target==="mouse"?d:G.event),j&&y.redraw());return y},toggle:function(e,f){function t(){e?(a.browser.msie&&D[0].style.removeAttribute("filter"),D.css("overflow",""),"string"===typeof i.autofocus&&a(i.autofocus,D).focus(),i.target.trigger("qtip-"+v+"-inactive")):D.css({display:"",visibility:"",opacity:"",left:"",top:""}),r=a.Event("tooltip"+(e?"visible":"hidden")),r.originalEvent=f?G.event:d,D.trigger(r,[y])}if(!y.rendered)return e?y.render(1):y;var g=e?"show":"hide",i=s[g],j=s[e?"hide":"show"],k=s.position,l=s.content,n=D[0].offsetWidth>0,o=e||i.target.length===1,p=!f||i.target.length<2||G.target[0]===f.target,q,r;(typeof e).search("boolean|number")&&(e=!n);if(!D.is(":animated")&&n===e&&p)return y;if(f){if(/over|enter/.test(f.type)&&/out|leave/.test(G.event.type)&&s.show.target.add(f.target).length===s.show.target.length&&D.has(f.relatedTarget).length)return y;G.event=a.extend({},f)}r=a.Event("tooltip"+g),r.originalEvent=f?G.event:d,D.trigger(r,[y,90]);if(r.isDefaultPrevented())return y;a.attr(D[0],"aria-hidden",!e),e?(G.origin=a.extend({},h),y.focus(f),a.isFunction(l.text)&&O(l.text,c),a.isFunction(l.title.text)&&N(l.title.text,c),!u&&k.target==="mouse"&&k.adjust.mouse&&(a(document).bind("mousemove.qtip",function(a){h={pageX:a.pageX,pageY:a.pageY,type:"mousemove"}}),u=b),y.reposition(f,arguments[2]),(r.solo=!!i.solo)&&a(m,i.solo).not(D).qtip("hide",r)):(clearTimeout(y.timers.show),delete G.origin,u&&!a(m+'[tracking="true"]:visible',i.solo).not(D).length&&(a(document).unbind("mousemove.qtip"),u=c),y.blur(f)),i.effect===c||o===c?(D[g](),t.call(D)):a.isFunction(i.effect)?(D.stop(1,1),i.effect.call(D,y),D.queue("fx",function(a){t(),a()})):D.fadeTo(90,e?1:0,t),e&&i.target.trigger("qtip-"+v+"-inactive");return y},show:function(a){return y.toggle(b,a)},hide:function(a){return y.toggle(c,a)},focus:function(b){if(!y.rendered)return y;var c=a(m),d=parseInt(D[0].style.zIndex,10),e=f.zindex+c.length,g=a.extend({},b),h,i;D.hasClass(o)||(i=a.Event("tooltipfocus"),i.originalEvent=g,D.trigger(i,[y,e]),i.isDefaultPrevented()||(d!==e&&(c.each(function(){this.style.zIndex>d&&(this.style.zIndex=this.style.zIndex-1)}),c.filter("."+o).qtip("blur",g)),D.addClass(o)[0].style.zIndex=e));return y},blur:function(b){var c=a.extend({},b),d;D.removeClass(o),d=a.Event("tooltipblur"),d.originalEvent=c,D.trigger(d,[y]);return y},reposition:function(b,d){if(!y.rendered||B)return y;B=1;var e=s.position.target,f=s.position,i=f.my,k=f.at,l=f.adjust,m=l.method.split(" "),n=D.outerWidth(),o=D.outerHeight(),p=0,q=0,r=a.Event("tooltipmove"),t=D.css("position")==="fixed",u=f.viewport,v={left:0,top:0},w=f.container,x=c,A=y.plugins.tip,C=D[0].offsetWidth>0,E={horizontal:m[0],vertical:m[1]=m[1]||m[0],enabled:u.jquery&&e[0]!==window&&e[0]!==z&&l.method!=="none",left:function(a){var b=E.horizontal==="shift",c=l.x*(E.horizontal.substr(-6)==="invert"?2:0),d=-w.offset.left+u.offset.left+u.scrollLeft,e=i.x==="left"?n:i.x==="right"?-n:-n/2,f=k.x==="left"?p:k.x==="right"?-p:-p/2,g=A&&A.size?A.size.width||0:0,h=A&&A.corner&&A.corner.precedance==="x"&&!b?g:0,j=d-a+h,m=a+n-u.width-d+h,o=e-(i.precedance==="x"||i.x===i.y?f:0)-(k.x==="center"?p/2:0),q=i.x==="center";b?(h=A&&A.corner&&A.corner.precedance==="y"?g:0,o=(i.x==="left"?1:-1)*e-h,v.left+=j>0?j:m>0?-m:0,v.left=Math.max(-w.offset.left+u.offset.left+(h&&A.corner.x==="center"?A.offset:0),a-o,Math.min(Math.max(-w.offset.left+u.offset.left+u.width,a+o),v.left))):(j>0&&(i.x!=="left"||m>0)?v.left-=o+c:m>0&&(i.x!=="right"||j>0)&&(v.left-=(q?-o:o)+c),v.left<d&&-v.left>m&&(v.left=a));return v.left-a},top:function(a){var b=E.vertical==="shift",c=l.y*(E.vertical.substr(-6)==="invert"?2:0),d=-w.offset.top+u.offset.top+u.scrollTop,e=i.y==="top"?o:i.y==="bottom"?-o:-o/2,f=k.y==="top"?q:k.y==="bottom"?-q:-q/2,g=A&&A.size?A.size.height||0:0,h=A&&A.corner&&A.corner.precedance==="y"&&!b?g:0,j=d-a+h,m=a+o-u.height-d+h,n=e-(i.precedance==="y"||i.x===i.y?f:0)-(k.y==="center"?q/2:0),p=i.y==="center";b?(h=A&&A.corner&&A.corner.precedance==="x"?g:0,n=(i.y==="top"?1:-1)*e-h,v.top+=j>0?j:m>0?-m:0,v.top=Math.max(-w.offset.top+u.offset.top+(h&&A.corner.x==="center"?A.offset:0),a-n,Math.min(Math.max(-w.offset.top+u.offset.top+u.height,a+n),v.top))):(j>0&&(i.y!=="top"||m>0)?v.top-=n+c:m>0&&(i.y!=="bottom"||j>0)&&(v.top-=(p?-n:n)+c),v.top<0&&-v.top>m&&(v.top=a));return v.top-a}},H;if(a.isArray(e)&&e.length===2)k={x:"left",y:"top"},v={left:e[0],top:e[1]};else if(e==="mouse"&&(b&&b.pageX||G.event.pageX))k={x:"left",y:"top"},b=(b&&(b.type==="resize"||b.type==="scroll")?G.event:b&&b.pageX&&b.type==="mousemove"?b:h&&h.pageX&&(l.mouse||!b||!b.pageX)?{pageX:h.pageX,pageY:h.pageY}:!l.mouse&&G.origin&&G.origin.pageX&&s.show.distance?G.origin:b)||b||G.event||h||{},v={top:b.pageY,left:b.pageX};else{e==="event"?b&&b.target&&b.type!=="scroll"&&b.type!=="resize"?e=G.target=a(b.target):e=G.target:e=G.target=a(e.jquery?e:F.target),e=a(e).eq(0);if(e.length===0)return y;e[0]===document||e[0]===window?(p=g.iOS?window.innerWidth:e.width(),q=g.iOS?window.innerHeight:e.height(),e[0]===window&&(v={top:(u||e).scrollTop(),left:(u||e).scrollLeft()})):e.is("area")&&g.imagemap?v=g.imagemap(e,k,E.enabled?m:c):e[0].namespaceURI==="http://www.w3.org/2000/svg"&&g.svg?v=g.svg(e,k):(p=e.outerWidth(),q=e.outerHeight(),v=g.offset(e,w)),v.offset&&(p=v.width,q=v.height,x=v.flipoffset,v=v.offset);if(g.iOS<4.1&&g.iOS>3.1||g.iOS==4.3||!g.iOS&&t)H=a(window),v.left-=H.scrollLeft(),v.top-=H.scrollTop();v.left+=k.x==="right"?p:k.x==="center"?p/2:0,v.top+=k.y==="bottom"?q:k.y==="center"?q/2:0}v.left+=l.x+(i.x==="right"?-n:i.x==="center"?-n/2:0),v.top+=l.y+(i.y==="bottom"?-o:i.y==="center"?-o/2:0),E.enabled?(u={elem:u,height:u[(u[0]===window?"h":"outerH")+"eight"](),width:u[(u[0]===window?"w":"outerW")+"idth"](),scrollLeft:t?0:u.scrollLeft(),scrollTop:t?0:u.scrollTop(),offset:u.offset()||{left:0,top:0}},w={elem:w,scrollLeft:w.scrollLeft(),scrollTop:w.scrollTop(),offset:w.offset()||{left:0,top:0}},v.adjusted={left:E.horizontal!=="none"?E.left(v.left):0,top:E.vertical!=="none"?E.top(v.top):0},v.adjusted.left+v.adjusted.top&&D.attr("class",D[0].className.replace(/ui-tooltip-pos-\w+/i,j+"-pos-"+i.abbrev())),x&&v.adjusted.left&&(v.left+=x.left),x&&v.adjusted.top&&(v.top+=x.top)):v.adjusted={left:0,top:0},r.originalEvent=a.extend({},b),D.trigger(r,[y,v,u.elem||u]);if(r.isDefaultPrevented())return y;delete v.adjusted,d===c||!C||isNaN(v.left)||isNaN(v.top)||e==="mouse"||!a.isFunction(f.effect)?D.css(v):a.isFunction(f.effect)&&(f.effect.call(D,y,a.extend({},v)),D.queue(function(b){a(this).css({opacity:"",height:""}),a.browser.msie&&this.style.removeAttribute("filter"),b()})),B=0;return y},redraw:function(){if(y.rendered<1||C)return y;var a=s.position.container,b,c,d,e;C=1,s.style.height&&D.css("height",s.style.height),s.style.width?D.css("width",s.style.width):(D.css("width","").addClass(q),c=D.width()+1,d=D.css("max-width")||"",e=D.css("min-width")||"",b=(d+e).indexOf("%")>-1?a.width()/100:0,d=(d.indexOf("%")>-1?b:1)*parseInt(d,10)||c,e=(e.indexOf("%")>-1?b:1)*parseInt(e,10)||0,c=d+e?Math.min(Math.max(c,e),d):c,D.css("width",Math.round(c)).removeClass(q)),C=0;return y},disable:function(b){"boolean"!==typeof b&&(b=!D.hasClass(l)&&!G.disabled),y.rendered?(D.toggleClass(l,b),a.attr(D[0],"aria-disabled",b)):G.disabled=!!b;return y},enable:function(){return y.disable(c)},destroy:function(){var c=r[0],d=a.attr(c,t),e=r.data("qtip");y.destroyed=b,y.rendered&&(D.stop(1,0).remove(),a.each(y.plugins,function(){this.destroy&&this.destroy()})),clearTimeout(y.timers.show),clearTimeout(y.timers.hide),Q();if(!e||y===e)a.removeData(c,"qtip"),s.suppress&&d&&(a.attr(c,"title",d),r.removeAttr(t)),r.removeAttr("aria-describedby");r.unbind(".qtip-"+v),delete i[y.id];return r}})}function w(b){var e;if(!b||"object"!==typeof b)return c;if(b.metadata===d||"object"!==typeof b.metadata)b.metadata={type:b.metadata};if("content"in b){if(b.content===d||"object"!==typeof b.content||b.content.jquery)b.content={text:b.content};e=b.content.text||c,!a.isFunction(e)&&(!e&&!e.attr||e.length<1||"object"===typeof e&&!e.jquery)&&(b.content.text=c);if("title"in b.content){if(b.content.title===d||"object"!==typeof b.content.title)b.content.title={text:b.content.title};e=b.content.title.text||c,!a.isFunction(e)&&(!e&&!e.attr||e.length<1||"object"===typeof e&&!e.jquery)&&(b.content.title.text=c)}}if("position"in b)if(b.position===d||"object"!==typeof b.position)b.position={my:b.position,at:b.position};if("show"in b)if(b.show===d||"object"!==typeof b.show)b.show.jquery?b.show={target:b.show}:b.show={event:b.show};if("hide"in b)if(b.hide===d||"object"!==typeof b.hide)b.hide.jquery?b.hide={target:b.hide}:b.hide={event:b.hide};if("style"in b)if(b.style===d||"object"!==typeof b.style)b.style={classes:b.style};a.each(g,function(){this.sanitize&&this.sanitize(b)});return b}function v(){v.history=v.history||[],v.history.push(arguments);if("object"===typeof console){var a=console[console.warn?"warn":"log"],b=Array.prototype.slice.call(arguments),c;typeof arguments[0]==="string"&&(b[0]="qTip2: "+b[0]),c=a.apply?a.apply(console,b):a(b)}}"use strict";var b=!0,c=!1,d=null,e,f,g,h,i={},j="ui-tooltip",k="ui-widget",l="ui-state-disabled",m="div.qtip."+j,n=j+"-default",o=j+"-focus",p=j+"-hover",q=j+"-fluid",r="-31000px",s="_replacedByqTip",t="oldtitle",u;f=a.fn.qtip=function(g,h,i){var j=(""+g).toLowerCase(),k=d,l=a.makeArray(arguments).slice(1),m=l[l.length-1],n=this[0]?a.data(this[0],"qtip"):d;if(!arguments.length&&n||j==="api")return n;if("string"===typeof g){this.each(function(){var d=a.data(this,"qtip");if(!d)return b;m&&m.timeStamp&&(d.cache.event=m);if(j!=="option"&&j!=="options"||!h)d[j]&&d[j].apply(d[j],l);else if(a.isPlainObject(h)||i!==e)d.set(h,i);else{k=d.get(h);return c}});return k!==d?k:this}if("object"===typeof g||!arguments.length){n=w(a.extend(b,{},g));return f.bind.call(this,n,m)}},f.bind=function(d,j){return this.each(function(k){function r(b){function d(){p.render(typeof b==="object"||l.show.ready),m.show.add(m.hide).unbind(o)}if(p.cache.disabled)return c;p.cache.event=a.extend({},b),p.cache.target=b?a(b.target):[e],l.show.delay>0?(clearTimeout(p.timers.show),p.timers.show=setTimeout(d,l.show.delay),n.show!==n.hide&&m.hide.bind(n.hide,function(){clearTimeout(p.timers.show)})):d()}var l,m,n,o,p,q;q=a.isArray(d.id)?d.id[k]:d.id,q=!q||q===c||q.length<1||i[q]?f.nextid++:i[q]=q,o=".qtip-"+q+"-create",p=y.call(this,q,d);if(p===c)return b;l=p.options,a.each(g,function(){this.initialize==="initialize"&&this(p)}),m={show:l.show.target,hide:l.hide.target},n={show:a.trim(""+l.show.event).replace(/ /g,o+" ")+o,hide:a.trim(""+l.hide.event).replace(/ /g,o+" ")+o},/mouse(over|enter)/i.test(n.show)&&!/mouse(out|leave)/i.test(n.hide)&&(n.hide+=" mouseleave"+o),m.show.bind("mousemove"+o,function(a){h={pageX:a.pageX,pageY:a.pageY,type:"mousemove"},p.cache.onTarget=b}),m.show.bind(n.show,r),(l.show.ready||l.prerender)&&r(j)})},g=f.plugins={Corner:function(a){a=(""+a).replace(/([A-Z])/," $1").replace(/middle/gi,"center").toLowerCase(),this.x=(a.match(/left|right/i)||a.match(/center/)||["inherit"])[0].toLowerCase(),this.y=(a.match(/top|bottom|center/i)||["inherit"])[0].toLowerCase();var b=a.charAt(0);this.precedance=b==="t"||b==="b"?"y":"x",this.string=function(){return this.precedance==="y"?this.y+this.x:this.x+this.y},this.abbrev=function(){var a=this.x.substr(0,1),b=this.y.substr(0,1);return a===b?a:a==="c"||a!=="c"&&b!=="c"?b+a:a+b},this.clone=function(){return{x:this.x,y:this.y,precedance:this.precedance,string:this.string,abbrev:this.abbrev,clone:this.clone}}},offset:function(b,c){function j(a,b){d.left+=b*a.scrollLeft(),d.top+=b*a.scrollTop()}var d=b.offset(),e=b.closest("body")[0],f=c,g,h,i;if(f){do f.css("position")!=="static"&&(h=f.position(),d.left-=h.left+(parseInt(f.css("borderLeftWidth"),10)||0)+(parseInt(f.css("marginLeft"),10)||0),d.top-=h.top+(parseInt(f.css("borderTopWidth"),10)||0)+(parseInt(f.css("marginTop"),10)||0),!g&&(i=f.css("overflow"))!=="hidden"&&i!=="visible"&&(g=f));while((f=a(f[0].offsetParent)).length);g&&g[0]!==e&&j(g,1)}return d},iOS:parseFloat((""+(/CPU.*OS ([0-9_]{1,3})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent)||[0,""])[1]).replace("undefined","3_2").replace("_","."))||c,fn:{attr:function(b,c){if(this.length){var d=this[0],e="title",f=a.data(d,"qtip");if(b===e&&f&&"object"===typeof f&&f.options.suppress){if(arguments.length<2)return a.attr(d,t);f&&f.options.content.attr===e&&f.cache.attr&&f.set("content.text",c);return this.attr(t,c)}}return a.fn["attr"+s].apply(this,arguments)},clone:function(b){var c=a([]),d="title",e=a.fn["clone"+s].apply(this,arguments);b||e.filter("["+t+"]").attr("title",function(){return a.attr(this,t)}).removeAttr(t);return e}}},a.each(g.fn,function(c,d){if(!d||a.fn[c+s])return b;var e=a.fn[c+s]=a.fn[c];a.fn[c]=function(){return d.apply(this,arguments)||e.apply(this,arguments)}}),a.ui||(a["cleanData"+s]=a.cleanData,a.cleanData=function(b){for(var c=0,d;(d=b[c])!==e;c++)try{a(d).triggerHandler("removeqtip")}catch(f){}a["cleanData"+s](b)}),f.version="nightly",f.nextid=0,f.inactiveEvents="click dblclick mousedown mouseup mousemove mouseleave mouseenter".split(" "),f.zindex=15e3,f.defaults={prerender:c,id:c,overwrite:b,suppress:b,content:{text:b,attr:"title",title:{text:c,button:c}},position:{my:"top left",at:"bottom right",target:c,container:c,viewport:c,adjust:{x:0,y:0,mouse:b,resize:b,method:"flip flip"},effect:function(b,d,e){a(this).animate(d,{duration:200,queue:c})}},show:{target:c,event:"mouseenter",effect:b,delay:90,solo:c,ready:c,autofocus:c},hide:{target:c,event:"mouseleave",effect:b,delay:0,fixed:c,inactive:c,leave:"window",distance:c},style:{classes:"",widget:c,width:c,height:c,def:b},events:{render:d,move:d,show:d,hide:d,toggle:d,visible:d,hidden:d,focus:d,blur:d}}}) \ No newline at end of file
diff --git a/apps/atnotes/lib/atnotes.class.php b/apps/atnotes/lib/atnotes.class.php
new file mode 100644
index 00000000000..42100004eda
--- /dev/null
+++ b/apps/atnotes/lib/atnotes.class.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+* ownCloud - ATNotes plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+/**
+ * This class manages atnote db.
+ */
+class OC_ATNotes {
+
+ /**
+ * Save note
+ * @param $title Title of the note
+ * @param $content Content of the note
+ * @return $id Id of the saved note
+ */
+ public static function saveNote($id,$title,$content){
+ if($id == 0){
+ $query = OCP\DB::prepare('INSERT INTO *PREFIX*atnotes (oc_uid,create_ts,update_ts,note_title,note_content) VALUES (?,?,?,?,?)');
+ $query->execute(Array(OCP\User::getUser(),time(),time(),$title,$content));
+ $id = OCP\DB::insertid('*PREFIX*atnotes');
+ }else{
+ $query = OCP\DB::prepare('UPDATE *PREFIX*atnotes SET update_ts=?,note_title=?,note_content=? WHERE note_id=?');
+ $query->execute(Array(time(),$title,$content,$id));
+ }
+ return $id;
+ }
+
+ /**
+ * Get list
+ * @return Array()
+ */
+ public static function getNotesList(){
+ $query = OCP\DB::prepare('SELECT * FROM *PREFIX*atnotes WHERE oc_uid=? AND is_deleted=? ORDER BY update_ts DESC');
+ $results = $query->execute(Array(OCP\User::getUser(),0))->fetchAll();
+ return $results;
+ }
+
+ /**
+ * Get note
+ * @param $id The note db id
+ * @return Array if note found or Boolean FALSE
+ */
+ public static function getNote($id){
+ $query = OCP\DB::prepare('SELECT * FROM *PREFIX*atnotes WHERE oc_uid=? AND is_deleted=? AND note_id=?');
+ $result = $query->execute(Array(OCP\User::getUser(),0,$id))->fetchRow();
+ if($result){
+ return $result;
+ }else{
+ return FALSE;
+ }
+ }
+
+ /**
+ * Delete note
+ * @param $id The note db id
+ * @return Boolean
+ */
+ public static function deleteNote($id){
+ if(self::getNote($id)){
+ $query = OCP\DB::prepare('DELETE FROM *PREFIX*atnotes WHERE oc_uid=? AND note_id=?');
+ $query->execute(Array(OCP\User::getUser(),$id));
+ if(!self::getNote($id)){
+ return 0;
+ }
+ }
+ return 1;
+ }
+
+}
diff --git a/apps/atnotes/templates/main.tpl.php b/apps/atnotes/templates/main.tpl.php
new file mode 100644
index 00000000000..907785c4afe
--- /dev/null
+++ b/apps/atnotes/templates/main.tpl.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+* ownCloud - ATNotes plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+OCP\Util::addStyle('atnotes','jquery-te-1.0.3.min');
+OCP\Util::addScript('atnotes','jquery-te-1.0.3.min');
+OCP\Util::addStyle('atnotes','jquery.qtip.min');
+OCP\Util::addScript('atnotes','jquery.qtip.min');
+OCP\Util::addStyle('atnotes','atnotes.min');
+OCP\Util::addScript('atnotes','atnotes.min');
+
+?>
+
+<div id="atnotes_container">
+ <div id="save_dialog">
+ <div class="atnotes-explorer"><table border="0" cellpadding="0" cellspacing="0"></table></div>
+ </div>
+ <div id="controls">
+ <div class="title"><?php print('Always Take Notes !'); ?></div>
+ </div>
+ <ul class="atnotes-noteslist">
+ <?php foreach($_['notes_list'] as $note){ ?>
+ <li class="atnotes-elt" rel="<?php print($note['note_id']); ?>">
+ <div class="atnotes-elt-title"><?php print($note['note_title']); ?></div>
+ <div class="atnotes-elt-state"></div>
+ <div class="atnotes-elt-date"><?php print(date('m/d/Y', $note['update_ts'])); ?></div>
+ <div class="atnotes-elt-prerender"><?php print($note['note_content']); ?></div>
+ </li>
+ <?php } ?>
+ </ul>
+ <div class="atnotes-notesedit">
+ <div class="anotes-notesedit-title">
+ <input type="text" id="note_title" rel="0" maxlength="255" />
+ <input type="hidden" id="note_path" maxlength="255" />
+ <div class="atnotes-actions-btns">
+ <img class="atnotes-actions-list" src="<?php print(OCP\Util::imagePath('atnotes','action.png')); ?>" />
+ <div class="atnotes-actions-ddmenu">
+ <ul>
+ <li class="atnotes-new">New note</li>
+ <li class="atnotes-delete">Delete note</li>
+ </ul>
+ </div>
+ <img class="atnotes-save" src="<?php print(OCP\Util::imagePath('atnotes','save.png')); ?>" />
+ </div>
+ </div>
+ <div class="anotes-notesedit-states">
+ <div class="atnotes-created"></div>
+ <div class="atnotes-saved"></div>
+ </div>
+ </div>
+ <textarea class="atnotes-editor"></textarea>
+</div>
diff --git a/apps/calendar/ajax/calendar/activation.php b/apps/calendar/ajax/calendar/activation.php
index e31908beb14..f4aadc5b017 100644
--- a/apps/calendar/ajax/calendar/activation.php
+++ b/apps/calendar/ajax/calendar/activation.php
@@ -9,6 +9,8 @@
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('calendar');
+OCP\JSON::callCheck();
+
$calendarid = $_POST['calendarid'];
$calendar = OC_Calendar_App::getCalendar($calendarid, true);
if(!$calendar){
diff --git a/apps/calendar/ajax/calendar/delete.php b/apps/calendar/ajax/calendar/delete.php
index 4d6706f6002..089255cae39 100644
--- a/apps/calendar/ajax/calendar/delete.php
+++ b/apps/calendar/ajax/calendar/delete.php
@@ -9,6 +9,7 @@
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('calendar');
+OCP\JSON::callCheck();
$cal = $_POST["calendarid"];
$calendar = OC_Calendar_App::getCalendar($cal, true);
diff --git a/apps/calendar/ajax/calendar/new.php b/apps/calendar/ajax/calendar/new.php
index e77d4ebff03..67d12822378 100644
--- a/apps/calendar/ajax/calendar/new.php
+++ b/apps/calendar/ajax/calendar/new.php
@@ -6,11 +6,10 @@
* See the COPYING-README file.
*/
-
-
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('calendar');
+OCP\JSON::callCheck();
if(trim($_POST['name']) == ''){
OCP\JSON::error(array('message'=>'empty'));
diff --git a/apps/calendar/ajax/calendar/update.php b/apps/calendar/ajax/calendar/update.php
index a2c898c8075..c09b1008c9c 100644
--- a/apps/calendar/ajax/calendar/update.php
+++ b/apps/calendar/ajax/calendar/update.php
@@ -11,6 +11,7 @@
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('calendar');
+OCP\JSON::callCheck();
if(trim($_POST['name']) == ''){
OCP\JSON::error(array('message'=>'empty'));
diff --git a/apps/calendar/ajax/categories/rescan.php b/apps/calendar/ajax/categories/rescan.php
index f0060cb23b2..08c32865b6f 100644
--- a/apps/calendar/ajax/categories/rescan.php
+++ b/apps/calendar/ajax/categories/rescan.php
@@ -9,6 +9,7 @@
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('calendar');
+OCP\JSON::callCheck();
foreach ($_POST as $key=>$element) {
debug('_POST: '.$key.'=>'.print_r($element, true));
diff --git a/apps/calendar/ajax/event/delete.php b/apps/calendar/ajax/event/delete.php
index f183d431afa..17e45c001e8 100644
--- a/apps/calendar/ajax/event/delete.php
+++ b/apps/calendar/ajax/event/delete.php
@@ -9,6 +9,7 @@
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('calendar');
+OCP\JSON::callCheck();
$id = $_POST['id'];
$access = OC_Calendar_App::getaccess($id, OC_Calendar_App::EVENT);
diff --git a/apps/calendar/ajax/event/edit.php b/apps/calendar/ajax/event/edit.php
index 1c3babc3d90..db78bf6e5e0 100644
--- a/apps/calendar/ajax/event/edit.php
+++ b/apps/calendar/ajax/event/edit.php
@@ -9,6 +9,7 @@
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('calendar');
+OCP\JSON::callCheck();
$id = $_POST['id'];
diff --git a/apps/calendar/ajax/event/move.php b/apps/calendar/ajax/event/move.php
index 04cf2fb0513..f4e2b36376d 100644
--- a/apps/calendar/ajax/event/move.php
+++ b/apps/calendar/ajax/event/move.php
@@ -7,6 +7,7 @@
*/
OCP\JSON::checkLoggedIn();
+OCP\JSON::callCheck();
$id = $_POST['id'];
$access = OC_Calendar_App::getaccess($id, OC_Calendar_App::EVENT);
diff --git a/apps/calendar/ajax/event/new.form.php b/apps/calendar/ajax/event/new.form.php
index 0b19e7e92f9..db04cdf2d49 100644
--- a/apps/calendar/ajax/event/new.form.php
+++ b/apps/calendar/ajax/event/new.form.php
@@ -27,7 +27,7 @@ if (!$end){
}
$start = new DateTime('@'.$start);
$end = new DateTime('@'.$end);
-$timezone = OCP\Config::getUserValue(OCP\USER::getUser(), 'calendar', 'timezone', date_default_timezone_get());
+$timezone = OC_Calendar_App::getTimezone();
$start->setTimezone(new DateTimeZone($timezone));
$end->setTimezone(new DateTimeZone($timezone));
diff --git a/apps/calendar/ajax/event/new.php b/apps/calendar/ajax/event/new.php
index 30e2b0cae36..bc0439cc315 100644
--- a/apps/calendar/ajax/event/new.php
+++ b/apps/calendar/ajax/event/new.php
@@ -10,6 +10,7 @@
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('calendar');
+OCP\JSON::callCheck();
$errarr = OC_Calendar_Object::validateRequest($_POST);
if($errarr){
diff --git a/apps/calendar/ajax/event/resize.php b/apps/calendar/ajax/event/resize.php
index 56b83205e85..15b687b55da 100644
--- a/apps/calendar/ajax/event/resize.php
+++ b/apps/calendar/ajax/event/resize.php
@@ -7,6 +7,7 @@
*/
OCP\JSON::checkLoggedIn();
+OCP\JSON::callCheck();
$id = $_POST['id'];
diff --git a/apps/calendar/ajax/import/calendarcheck.php b/apps/calendar/ajax/import/calendarcheck.php
new file mode 100644
index 00000000000..a91bab70573
--- /dev/null
+++ b/apps/calendar/ajax/import/calendarcheck.php
@@ -0,0 +1,18 @@
+<?php
+/**
+ * Copyright (c) 2012 Georg Ehrke <ownclouddev at georgswebsite dot de>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+OCP\JSON::checkLoggedIn();
+OCP\App::checkAppEnabled('calendar');
+$calname = strip_tags($_POST['calname']);
+$calendars = OC_Calendar_Calendar::allCalendars(OCP\User::getUser());
+foreach($calendars as $calendar){
+ if($calendar['displayname'] == $calname){
+ OCP\JSON::success(array('message'=>'exists'));
+ exit;
+ }
+}
+OCP\JSON::error(); \ No newline at end of file
diff --git a/apps/calendar/ajax/import/dialog.php b/apps/calendar/ajax/import/dialog.php
index b99c32278c4..18fe226172c 100644
--- a/apps/calendar/ajax/import/dialog.php
+++ b/apps/calendar/ajax/import/dialog.php
@@ -5,8 +5,6 @@
* later.
* See the COPYING-README file.
*/
-
-
OCP\JSON::checkLoggedIn();
OCP\App::checkAppEnabled('calendar');
$tmpl = new OCP\Template('calendar', 'part.import');
diff --git a/apps/calendar/ajax/import/dropimport.php b/apps/calendar/ajax/import/dropimport.php
index 87667d4de68..f46e7314098 100644
--- a/apps/calendar/ajax/import/dropimport.php
+++ b/apps/calendar/ajax/import/dropimport.php
@@ -1,73 +1,32 @@
<?php
+/**
+ * Copyright (c) 2012 Georg Ehrke <ownclouddev@georgswebsite.de>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
$data = $_POST['data'];
$data = explode(',', $data);
$data = end($data);
$data = base64_decode($data);
OCP\JSON::checkLoggedIn();
OCP\App::checkAppEnabled('calendar');
-$nl="\r\n";
-$comps = array('VEVENT'=>true, 'VTODO'=>true, 'VJOURNAL'=>true);
-$data = str_replace(array("\r","\n\n"), array("\n","\n"), $data);
-$lines = explode("\n", $data);
-unset($data);
-$comp=$uid=$cal=false;
-$cals=$uids=array();
-$i = 0;
-foreach($lines as $line) {
- if(strpos($line, ':')!==false) {
- list($attr, $val) = explode(':', strtoupper($line));
- if ($attr == 'BEGIN' && $val == 'VCALENDAR') {
- $cal = $i;
- $cals[$cal] = array('first'=>$i,'last'=>$i,'end'=>$i);
- } elseif ($attr =='BEGIN' && $cal!==false && isset($comps[$val])) {
- $comp = $val;
- $beginNo = $i;
- } elseif ($attr == 'END' && $cal!==false && $val == 'VCALENDAR') {
- if($comp!==false) {
- unset($cals[$cal]); // corrupt calendar, unset it
- } else {
- $cals[$cal]['end'] = $i;
- }
- $comp=$uid=$cal=false; // reset calendar
- } elseif ($attr == 'END' && $comp!==false && $val == $comp) {
- if(! $uid) {
- $uid = OC_Calendar_Object::createUID();
- }
- $uids[$uid][$beginNo] = array('end'=>$i, 'cal'=>$cal);
- if ($cals[$cal]['first'] == $cal) {
- $cals[$cal]['first'] = $beginNo;
- }
- $cals[$cal]['last'] = $i;
- $comp=$uid=false; // reset component
- } elseif ($attr =="UID" && $comp!==false) {
- list($attr, $uid) = explode(':', $line);
- }
- }
- $i++;
+$import = new OC_Calendar_Import($data);
+$import->setUserID(OCP\User::getUser());
+$import->setTimeZone(OC_Calendar_App::$tz);
+$import->disableProgressCache();
+if(!$import->isValid()){
+ OCP\JSON::error();
+ exit;
}
-$calendars = OC_Calendar_Calendar::allCalendars(OCP\USER::getUser(), true);
-$id = $calendars[0]['id'];
-foreach($uids as $uid) {
- $prefix=$suffix=$content=array();
- foreach($uid as $begin=>$details) {
- $cal = $details['cal'];
- if(!isset($cals[$cal])) {
- continue; // from corrupt/incomplete calendar
- }
- $cdata = $cals[$cal];
- // if we have multiple components from different calendar objects,
- // we should really merge their elements (enhancement?) -- 1st one wins for now.
- if(! count($prefix)) {
- $prefix = array_slice($lines, $cal, $cdata['first'] - $cal);
- }
- if(! count($suffix)) {
- $suffix = array_slice($lines, $cdata['last']+1, $cdata['end'] - $cdata['last']);
- }
- $content = array_merge($content, array_slice($lines, $begin, $details['end'] - $begin + 1));
- }
- if(count($content)) {
- $import = join($nl, array_merge($prefix, $content, $suffix)) . $nl;
- OC_Calendar_Object::add($id, $import);
- }
-}
-OCP\JSON::success(); \ No newline at end of file
+$newcalendarname = strip_tags($import->createCalendarName());
+$newid = OC_Calendar_Calendar::addCalendar(OCP\User::getUser(),$newcalendarname,'VEVENT,VTODO,VJOURNAL',null,0,$import->createCalendarColor());
+$import->setCalendarID($newid);
+$import->import();
+$count = $import->getCount();
+if($count == 0){
+ OC_Calendar_Calendar::deleteCalendar($newid);
+ OCP\JSON::error(array('message'=>OC_Calendar_App::$l10n->t('The file contained either no events or all events are already saved in your calendar.')));
+}else{
+ OCP\JSON::success(array('message'=>$count . ' ' . OC_Calendar_App::$l10n->t('events has been saved in the new calendar') . ' ' . $newcalendarname, 'eventSource'=>OC_Calendar_Calendar::getEventSourceInfo(OC_Calendar_Calendar::find($newid))));
+} \ No newline at end of file
diff --git a/apps/calendar/ajax/import/import.php b/apps/calendar/ajax/import/import.php
index 1facedfe0da..b1dfc464d00 100644
--- a/apps/calendar/ajax/import/import.php
+++ b/apps/calendar/ajax/import/import.php
@@ -5,41 +5,71 @@
* later.
* See the COPYING-README file.
*/
-//check for calendar rights or create new one
-ob_start();
-
OCP\JSON::checkLoggedIn();
OCP\App::checkAppEnabled('calendar');
+OCP\JSON::callCheck();
session_write_close();
-
-$nl="\r\n";
-$comps = array('VEVENT'=>true, 'VTODO'=>true, 'VJOURNAL'=>true);
-
-global $progresskey;
-$progresskey = 'calendar.import-' . $_POST['progresskey'];
-
-if (isset($_POST['progress']) && $_POST['progress']) {
- echo OC_Cache::get($progresskey);
- die;
+if (isset($_POST['progresskey']) && isset($_POST['getprogress'])) {
+ echo OCP\JSON::success(array('percent'=>OC_Cache::get($_POST['progresskey'])));
+ exit;
}
-
-function writeProgress($pct) {
- global $progresskey;
- OC_Cache::set($progresskey, $pct, 300);
-}
-writeProgress('10');
$file = OC_Filesystem::file_get_contents($_POST['path'] . '/' . $_POST['file']);
+if(!$file){
+ OCP\JSON::error(array('error'=>'404'));
+}
+$import = new OC_Calendar_Import($file);
+$import->setUserID(OCP\User::getUser());
+$import->setTimeZone(OC_Calendar_App::$tz);
+$import->enableProgressCache();
+$import->setProgresskey($_POST['progresskey']);
+if(!$import->isValid()){
+ OCP\JSON::error(array('error'=>'notvalid'));
+ exit;
+}
+$newcal = false;
if($_POST['method'] == 'new'){
- $id = OC_Calendar_Calendar::addCalendar(OCP\USER::getUser(), $_POST['calname']);
- OC_Calendar_Calendar::setCalendarActive($id, 1);
+ $calendars = OC_Calendar_Calendar::allCalendars(OCP\User::getUser());
+ foreach($calendars as $calendar){
+ if($calendar['displayname'] == $_POST['calname']){
+ $id = $calendar['id'];
+ $newcal = false;
+ break;
+ }
+ $newcal = true;
+ }
+ if($newcal){
+ $id = OC_Calendar_Calendar::addCalendar(OCP\USER::getUser(), strip_tags($_POST['calname']),'VEVENT,VTODO,VJOURNAL',null,0,strip_tags($_POST['calcolor']));
+ OC_Calendar_Calendar::setCalendarActive($id, 1);
+ }
}else{
$calendar = OC_Calendar_App::getCalendar($_POST['id']);
if($calendar['userid'] != OCP\USER::getUser()){
- OCP\JSON::error();
+ OCP\JSON::error(array('error'=>'missingcalendarrights'));
exit();
}
$id = $_POST['id'];
}
+$import->setCalendarID($id);
+try{
+ $import->import();
+}catch (Exception $e) {
+ OCP\JSON::error(array('message'=>OC_Calendar_App::$l10n->t('Import failed'), 'debug'=>$e->getMessage()));
+ //write some log
+}
+$count = $import->getCount();
+if($count == 0){
+ if($newcal){
+ OC_Calendar_Calendar::deleteCalendar($id);
+ }
+ OCP\JSON::error(array('message'=>OC_Calendar_App::$l10n->t('The file contained either no events or all events are already saved in your calendar.')));
+}else{
+ if($newcal){
+ OCP\JSON::success(array('message'=>$count . ' ' . OC_Calendar_App::$l10n->t('events has been saved in the new calendar') . ' ' . strip_tags($_POST['calname'])));
+ }else{
+ OCP\JSON::success(array('message'=>$count . ' ' . OC_Calendar_App::$l10n->t('events has been saved in your calendar')));
+ }
+}
+/* //////////////////////////// Attention: following code is quite painfull !!! ///////////////////////
writeProgress('20');
// normalize the newlines
$file = str_replace(array("\r","\n\n"), array("\n","\n"), $file);
@@ -91,7 +121,6 @@ foreach($lines as $line) {
// import the calendar
writeProgress('60');
foreach($uids as $uid) {
-
$prefix=$suffix=$content=array();
foreach($uid as $begin=>$details) {
@@ -119,4 +148,4 @@ foreach($uids as $uid) {
writeProgress('100');
sleep(3);
OC_Cache::remove($progresskey);
-OCP\JSON::success(); \ No newline at end of file
+OCP\JSON::success();*/
diff --git a/apps/calendar/ajax/settings/setfirstday.php b/apps/calendar/ajax/settings/setfirstday.php
index 056a6037524..73cf0c19b78 100644
--- a/apps/calendar/ajax/settings/setfirstday.php
+++ b/apps/calendar/ajax/settings/setfirstday.php
@@ -7,6 +7,7 @@
*/
OCP\JSON::checkLoggedIn();
+OCP\JSON::callCheck();
if(isset($_POST["firstday"])){
OCP\Config::setUserValue(OCP\USER::getUser(), 'calendar', 'firstday', $_POST["firstday"]);
OCP\JSON::success();
diff --git a/apps/calendar/ajax/settings/settimeformat.php b/apps/calendar/ajax/settings/settimeformat.php
index 8e95f6f3bf5..6136857e2fe 100644
--- a/apps/calendar/ajax/settings/settimeformat.php
+++ b/apps/calendar/ajax/settings/settimeformat.php
@@ -7,6 +7,7 @@
*/
OCP\JSON::checkLoggedIn();
+OCP\JSON::callCheck();
if(isset($_POST["timeformat"])){
OCP\Config::setUserValue(OCP\USER::getUser(), 'calendar', 'timeformat', $_POST["timeformat"]);
OCP\JSON::success();
diff --git a/apps/calendar/ajax/settings/settimezone.php b/apps/calendar/ajax/settings/settimezone.php
index 6d029a6643a..06db66d578e 100644
--- a/apps/calendar/ajax/settings/settimezone.php
+++ b/apps/calendar/ajax/settings/settimezone.php
@@ -14,6 +14,7 @@ $l=OC_L10N::get('calendar');
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('calendar');
+OCP\JSON::callCheck();
// Get data
if( isset( $_POST['timezone'] ) ){
diff --git a/apps/calendar/ajax/share/changepermission.php b/apps/calendar/ajax/share/changepermission.php
index e807c164a23..5aff7666f79 100644
--- a/apps/calendar/ajax/share/changepermission.php
+++ b/apps/calendar/ajax/share/changepermission.php
@@ -5,7 +5,9 @@
* later.
* See the COPYING-README file.
*/
-
+
+OCP\JSON::callCheck();
+
$id = strip_tags($_POST['id']);
$idtype = strip_tags($_POST['idtype']);
$permission = (int) strip_tags($_POST['permission']);
diff --git a/apps/calendar/ajax/share/share.php b/apps/calendar/ajax/share/share.php
index 838db619f62..77e1ab9d657 100644
--- a/apps/calendar/ajax/share/share.php
+++ b/apps/calendar/ajax/share/share.php
@@ -6,6 +6,8 @@
* See the COPYING-README file.
*/
+OCP\JSON::callCheck();
+
$id = strip_tags($_POST['id']);
$idtype = strip_tags($_POST['idtype']);
switch($idtype){
diff --git a/apps/calendar/ajax/share/unshare.php b/apps/calendar/ajax/share/unshare.php
index 1ce04677fb1..c7c06113189 100644
--- a/apps/calendar/ajax/share/unshare.php
+++ b/apps/calendar/ajax/share/unshare.php
@@ -5,7 +5,9 @@
* later.
* See the COPYING-README file.
*/
-
+
+OCP\JSON::callCheck();
+
$id = strip_tags($_POST['id']);
$idtype = strip_tags($_POST['idtype']);
switch($idtype){
diff --git a/apps/calendar/appinfo/app.php b/apps/calendar/appinfo/app.php
index 3c8cc76133e..4fdba291262 100644
--- a/apps/calendar/appinfo/app.php
+++ b/apps/calendar/appinfo/app.php
@@ -9,7 +9,9 @@ OC::$CLASSPATH['OC_Calendar_Repeat'] = 'apps/calendar/lib/repeat.php';
OC::$CLASSPATH['OC_Calendar_Share'] = 'apps/calendar/lib/share.php';
OC::$CLASSPATH['OC_Search_Provider_Calendar'] = 'apps/calendar/lib/search.php';
OC::$CLASSPATH['OC_Calendar_Export'] = 'apps/calendar/lib/export.php';
+OC::$CLASSPATH['OC_Calendar_Import'] = 'apps/calendar/lib/import.php';
//General Hooks
+OCP\Util::connectHook('OC_User', 'post_createUser', 'OC_Calendar_Hooks', 'createUser');
OCP\Util::connectHook('OC_User', 'post_deleteUser', 'OC_Calendar_Hooks', 'deleteUser');
//Repeating Events Hooks
OCP\Util::connectHook('OC_Calendar', 'addEvent', 'OC_Calendar_Repeat', 'generate');
@@ -23,6 +25,8 @@ OCP\Util::connectHook('OC_Calendar', 'deleteCalendar', 'OC_Calendar_Share', 'pos
OCP\Util::addscript('calendar','loader');
OCP\Util::addscript("3rdparty", "chosen/chosen.jquery.min");
OCP\Util::addStyle("3rdparty", "chosen/chosen");
+OCP\Util::addStyle('3rdparty/miniColors', 'jquery.miniColors');
+OCP\Util::addscript('3rdparty/miniColors', 'jquery.miniColors.min');
OCP\App::addNavigationEntry( array(
'id' => 'calendar_index',
'order' => 10,
diff --git a/apps/calendar/appinfo/version b/apps/calendar/appinfo/version
index 2eb3c4fe4ee..cb0c939a936 100644
--- a/apps/calendar/appinfo/version
+++ b/apps/calendar/appinfo/version
@@ -1 +1 @@
-0.5
+0.5.2
diff --git a/apps/calendar/css/import.css b/apps/calendar/css/import.css
new file mode 100644
index 00000000000..8abdc3aecd1
--- /dev/null
+++ b/apps/calendar/css/import.css
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) 2012 Georg Ehrke <ownclouddev at georgswebsite dot de>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+#calendar_import_newcalform, #calendar_import_mergewarning, #calendar_import_process, #calendar_import_done{display:none;}
+#calendar_import_process_message, #calendar_import_status, #calendar_import_form_message, #calendar_import_mergewarning{text-align:center;}
+#calendar_import_form_message{font-weight: bold;}
+#calendar_import_newcalendar{width:415px;float:right;}
+#calendar_import_mergewarning{clear: both;}
+#calendar_import_defaultcolors{clear:both;margin: 0 auto;text-align: center;}
+.calendar_import_warning{border-color: #fc3333;}
+.calendar-colorpicker-color{display:inline-block;width:20px;height:5px;margin: 0 auto;cursor:pointer;border:2px solid transparent;} \ No newline at end of file
diff --git a/apps/calendar/js/calendar.js b/apps/calendar/js/calendar.js
index 004b2386fb1..25311fa0df4 100644
--- a/apps/calendar/js/calendar.js
+++ b/apps/calendar/js/calendar.js
@@ -622,18 +622,11 @@ Calendar={
drop:function(e){
var files = e.dataTransfer.files;
for(var i = 0;i < files.length;i++){
- var file = files[i]
+ var file = files[i];
reader = new FileReader();
reader.onload = function(event){
- if(file.type != 'text/calendar'){
- $('#notification').html('At least one file don\'t seems to be a calendar file. File skipped.');
- $('#notification').slideDown();
- window.setTimeout(function(){$('#notification').slideUp();}, 5000);
- return false;
- }else{
- Calendar.UI.Drop.import(event.target.result);
- $('#calendar_holder').fullCalendar('refetchEvents');
- }
+ Calendar.UI.Drop.import(event.target.result);
+ $('#calendar_holder').fullCalendar('refetchEvents');
}
reader.readAsDataURL(file);
}
@@ -641,9 +634,13 @@ Calendar={
import:function(data){
$.post(OC.filePath('calendar', 'ajax/import', 'dropimport.php'), {'data':data},function(result) {
if(result.status == 'success'){
+ $('#calendar_holder').fullCalendar('addEventSource', result.eventSource);
+ $('#notification').html(result.message);
+ $('#notification').slideDown();
+ window.setTimeout(function(){$('#notification').slideUp();}, 5000);
return true;
}else{
- $('#notification').html('ownCloud wasn\'t able to import at least one file. File skipped.');
+ $('#notification').html(result.message);
$('#notification').slideDown();
window.setTimeout(function(){$('#notification').slideUp();}, 5000);
}
diff --git a/apps/calendar/js/loader.js b/apps/calendar/js/loader.js
index 57cf5adff0e..253abafc427 100644
--- a/apps/calendar/js/loader.js
+++ b/apps/calendar/js/loader.js
@@ -5,77 +5,175 @@
* See the COPYING-README file.
*/
Calendar_Import={
- importdialog: function(filename){
- var path = $('#dir').val();
- $('body').append('<div id="calendar_import"></div>');
- $('#calendar_import').load(OC.filePath('calendar', 'ajax/import', 'dialog.php'), {filename:filename, path:path}, function(){Calendar_Import.initdialog(filename);});
+ Store:{
+ file: '',
+ path: '',
+ id: 0,
+ method: '',
+ calname: '',
+ calcolor: '',
+ progresskey: '',
+ percentage: 0
},
- initdialog: function(filename){
- $('#calendar_import_dialog').dialog({
- width : 500,
- close : function() {
- $(this).dialog('destroy').remove();
- $('#calendar_import').remove();
+ Dialog:{
+ open: function(filename){
+ OC.addStyle('calendar', 'import');
+ Calendar_Import.Store.file = filename;
+ Calendar_Import.Store.path = $('#dir').val();
+ $('body').append('<div id="calendar_import"></div>');
+ $('#calendar_import').load(OC.filePath('calendar', 'ajax/import', 'dialog.php'), {filename:Calendar_Import.Store.file, path:Calendar_Import.Store.path},function(){
+ Calendar_Import.Dialog.init();
+ });
+ },
+ close: function(){
+ Calendar_Import.reset();
+ $(this).dialog('destroy').remove();
+ $('#calendar_import_dialog').remove();
+ },
+ init: function(){
+ //init dialog
+ $('#calendar_import_dialog').dialog({
+ width : 500,
+ resizable: false,
+ close : function() {
+ Calendar_Import.Dialog.close();
+ }
+ });
+ //init buttons
+ $('#calendar_import_done').click(function(){
+ Calendar_Import.Dialog.close();
+ });
+ $('#calendar_import_submit').click(function(){
+ Calendar_Import.Core.process();
+ });
+ $('#calendar_import_mergewarning').click(function(){
+ $('#calendar_import_newcalendar').attr('value', $('#calendar_import_availablename').val());
+ Calendar_Import.Dialog.mergewarning($('#calendar_import_newcalendar').val());
+ });
+ $('#calendar_import_calendar').change(function(){
+ if($('#calendar_import_calendar option:selected').val() == 'newcal'){
+ $('#calendar_import_newcalform').slideDown('slow');
+ Calendar_Import.Dialog.mergewarning($('#calendar_import_newcalendar').val());
+ }else{
+ $('#calendar_import_newcalform').slideUp('slow');
+ $('#calendar_import_mergewarning').slideUp('slow');
+ }
+ });
+ $('#calendar_import_newcalendar').keyup(function(){
+ Calendar_Import.Dialog.mergewarning($.trim($('#calendar_import_newcalendar').val()));
+ });
+ $('#calendar_import_newcalendar_color').miniColors({
+ letterCase: 'uppercase'
+ });
+ $('.calendar-colorpicker-color').click(function(){
+ var str = $(this).attr('rel');
+ str = str.substr(1);
+ $('#calendar_import_newcalendar_color').attr('value', str);
+ $(".color-picker").miniColors('value', '#' + str);
+ });
+ //init progressbar
+ $('#calendar_import_progressbar').progressbar({value: Calendar_Import.Store.percentage});
+ Calendar_Import.Store.progresskey = $('#calendar_import_progresskey').val();
+ },
+ mergewarning: function(newcalname){
+ $.post(OC.filePath('calendar', 'ajax/import', 'calendarcheck.php'), {calname: newcalname}, function(data){
+ if(data.message == 'exists'){
+ $('#calendar_import_mergewarning').slideDown('slow');
+ }else{
+ $('#calendar_import_mergewarning').slideUp('slow');
+ }
+ });
+ },
+ update: function(){
+ if(Calendar_Import.Store.percentage == 100){
+ return false;
}
- });
- $('#import_done_button').click(function(){
- $('#calendar_import_dialog').dialog('destroy').remove();
- $('#calendar_import').remove();
- });
- $('#progressbar').progressbar({value: 0});
- $('#startimport').click(function(){
- var filename = $('#filename').val();
- var path = $('#path').val();
- var calid = $('#calendar option:selected').val();
- if($('#calendar option:selected').val() == 'newcal'){
- var method = 'new';
- var calname = $('#newcalendar').val();
- var calname = $.trim(calname);
- if(calname == ''){
- $('#newcalendar').css('background-color', '#FF2626');
- $('#newcalendar').focus(function(){
- $('#newcalendar').css('background-color', '#F8F8F8');
- });
- return false;
+ $.post(OC.filePath('calendar', 'ajax/import', 'import.php'), {progresskey: Calendar_Import.Store.progresskey, getprogress: true}, function(data){
+ if(data.status == 'success'){
+ if(data.percent == null){
+ return false;
+ }
+ Calendar_Import.Store.percentage = parseInt(data.percent);
+ $('#calendar_import_progressbar').progressbar('option', 'value', parseInt(data.percent));
+ if(data.percent < 100 ){
+ window.setTimeout('Calendar_Import.Dialog.update()', 250);
+ }else{
+ $('#calendar_import_done').css('display', 'block');
+ }
+ }else{
+ $('#calendar_import_progressbar').progressbar('option', 'value', 100);
+ $('#calendar_import_progressbar > div').css('background-color', '#FF2626');
+ $('#calendar_import_status').html(data.message);
}
- }else{
- var method = 'old';
+ });
+ return 0;
+ },
+ warning: function(selector){
+ $(selector).addClass('calendar_import_warning');
+ $(selector).focus(function(){
+ $(selector).removeClass('calendar_import_warning');
+ });
+ }
+ },
+ Core:{
+ process: function(){
+ var validation = Calendar_Import.Core.prepare();
+ if(validation){
+ $('#calendar_import_form').css('display', 'none');
+ $('#calendar_import_process').css('display', 'block');
+ $('#calendar_import_newcalendar').attr('readonly', 'readonly');
+ $('#calendar_import_calendar').attr('disabled', 'disabled');
+ Calendar_Import.Core.send();
+ window.setTimeout('Calendar_Import.Dialog.update()', 250);
}
- $('#newcalendar').attr('readonly', 'readonly');
- $('#calendar').attr('disabled', 'disabled');
- var progresskey = $('#progresskey').val();
- $.post(OC.filePath('calendar', 'ajax/import', 'import.php'), {progresskey: progresskey, method: String (method), calname: String (calname), path: String (path), file: String (filename), id: String (calid)}, function(data){
+ },
+ send: function(){
+ $.post(OC.filePath('calendar', 'ajax/import', 'import.php'),
+ {progresskey: Calendar_Import.Store.progresskey, method: String (Calendar_Import.Store.method), calname: String (Calendar_Import.Store.calname), path: String (Calendar_Import.Store.path), file: String (Calendar_Import.Store.file), id: String (Calendar_Import.Store.id), calcolor: String (Calendar_Import.Store.calcolor)}, function(data){
if(data.status == 'success'){
- $('#progressbar').progressbar('option', 'value', 100);
- $('#import_done').css('display', 'block');
+ $('#calendar_import_progressbar').progressbar('option', 'value', 100);
+ Calendar_Import.Store.percentage = 100;
+ $('#calendar_import_done').css('display', 'block');
+ $('#calendar_import_status').html(data.message);
+ }else{
+ $('#calendar_import_progressbar').progressbar('option', 'value', 100);
+ $('#calendar_import_progressbar > div').css('background-color', '#FF2626');
+ $('#calendar_import_status').html(data.message);
}
});
- $('#form_container').css('display', 'none');
- $('#progressbar_container').css('display', 'block');
- window.setTimeout('Calendar_Import.getimportstatus(\'' + progresskey + '\')', 500);
- });
- $('#calendar').change(function(){
- if($('#calendar option:selected').val() == 'newcal'){
- $('#newcalform').slideDown('slow');
+ },
+ prepare: function(){
+ Calendar_Import.Store.id = $('#calendar_import_calendar option:selected').val();
+ if($('#calendar_import_calendar option:selected').val() == 'newcal'){
+ Calendar_Import.Store.method = 'new';
+ Calendar_Import.Store.calname = $.trim($('#calendar_import_newcalendar').val());
+ if(Calendar_Import.Store.calname == ''){
+ Calendar_Import.Dialog.warning('#calendar_import_newcalendar');
+ return false;
+ }
+ Calendar_Import.Store.calcolor = $.trim($('#calendar_import_newcalendar_color').val());
+ if(Calendar_Import.Store.calcolor == ''){
+ Calendar_Import.Store.calcolor = $('.calendar-colorpicker-color:first').attr('rel');
+ }
}else{
- $('#newcalform').slideUp('slow');
+ Calendar_Import.Store.method = 'old';
}
- });
+ return true;
+ }
},
- getimportstatus: function(progresskey){
- $.post(OC.filePath('calendar', 'ajax/import', 'import.php'), {progress:1,progresskey: progresskey}, function(percent){
- $('#progressbar').progressbar('option', 'value', parseInt(percent));
- if(percent < 100){
- window.setTimeout('Calendar_Import.getimportstatus(\'' + progresskey + '\')', 500);
- }else{
- $('#import_done').css('display', 'block');
- }
- });
+ reset: function(){
+ Calendar_Import.Store.file = '';
+ Calendar_Import.Store.path = '';
+ Calendar_Import.Store.id = 0;
+ Calendar_Import.Store.method = '';
+ Calendar_Import.Store.calname = '';
+ Calendar_Import.Store.progresskey = '';
+ Calendar_Import.Store.percentage = 0;
}
}
$(document).ready(function(){
if(typeof FileActions !== 'undefined'){
- FileActions.register('text/calendar','importcal', FileActions.PERMISSION_READ, '', Calendar_Import.importdialog);
- FileActions.setDefault('text/calendar','importcal');
+ FileActions.register('text/calendar','importCalendar', FileActions.PERMISSION_READ, '', Calendar_Import.Dialog.open);
+ FileActions.setDefault('text/calendar','importCalendar');
};
});
diff --git a/apps/calendar/js/settings.js b/apps/calendar/js/settings.js
index 03e4217573d..60741f2b6fc 100644
--- a/apps/calendar/js/settings.js
+++ b/apps/calendar/js/settings.js
@@ -34,6 +34,7 @@ $(document).ready(function(){
$.getJSON(OC.filePath('calendar', 'ajax/settings', 'timeformat.php'), function(jsondata, status) {
$('#' + jsondata.timeformat).attr('selected',true);
$('#timeformat').chosen();
+ $('#timeformat_chzn').css('width', '100px');
});
$.getJSON(OC.filePath('calendar', 'ajax/settings', 'gettimezonedetection.php'), function(jsondata, status){
if(jsondata.detection == 'true'){
@@ -43,6 +44,7 @@ $(document).ready(function(){
$.getJSON(OC.filePath('calendar', 'ajax/settings', 'getfirstday.php'), function(jsondata, status) {
$('#' + jsondata.firstday).attr('selected',true);
$('#firstday').chosen();
+ $('#firstday_chzn').css('width', '100px');
});
$('#cleancalendarcache').click(function(){
$.getJSON(OC.filePath('calendar', 'ajax/cache', 'rescan.php'), function(){
@@ -55,7 +57,7 @@ function calendarcachecheck(){
$.getJSON(OC.filePath('calendar', 'ajax/cache', 'status.php'), function(jsondata, status) {
$('#cleancalendarcache').attr('title', jsondata.l10n.text);
if(jsondata.status == 'success'){
- $('#cleancalendarcache').css('background', '#90EE90');
+ $('#cleancalendarcache').css('background', '#F8F8F8');
$('#cleancalendarcache').css('color', '#333');
$('#cleancalendarcache').css('text-shadow', '#fff 0 1px 0');
}else{
diff --git a/apps/calendar/l10n/de.php b/apps/calendar/l10n/de.php
index f12a18baad0..33c924ac2c3 100644
--- a/apps/calendar/l10n/de.php
+++ b/apps/calendar/l10n/de.php
@@ -2,11 +2,17 @@
"No calendars found." => "Keine Kalender gefunden",
"No events found." => "Keine Termine gefunden",
"Wrong calendar" => "Falscher Kalender",
+"Import failed" => "Import fehlgeschlagen",
"New Timezone:" => "Neue Zeitzone:",
"Timezone changed" => "Zeitzone geändert",
"Invalid request" => "Fehlerhafte Anfrage",
"Calendar" => "Kalender",
-"MMM d[ yyyy]{ '&#8212;'[ MMM] d yyyy}" => "ddd d MMMM[ yyyy]{ -[ddd d] MMMM yyyy}",
+"ddd" => "ddd",
+"ddd M/d" => "ddd d.M",
+"dddd M/d" => "dddd d.M",
+"MMMM yyyy" => "MMMM yyyy",
+"MMM d[ yyyy]{ '&#8212;'[ MMM] d yyyy}" => "MMM d[ yyyy]{ '&#8212;'[ MMM] d yyyy}",
+"dddd, MMM d, yyyy" => "dddd, d. MMM yyyy",
"Birthday" => "Geburtstag",
"Business" => "Geschäftlich",
"Call" => "Anruf",
@@ -22,7 +28,9 @@
"Projects" => "Projekte",
"Questions" => "Fragen",
"Work" => "Arbeit",
+"by" => "von",
"unnamed" => "unbenannt",
+"New Calendar" => "Neuer Kalender",
"Does not repeat" => "einmalig",
"Daily" => "täglich",
"Weekly" => "wöchentlich",
@@ -67,8 +75,26 @@
"by day and month" => "nach Tag und Monat",
"Date" => "Datum",
"Cal." => "Kal.",
+"Sun." => "So",
+"Mon." => "Mo",
+"Tue." => "Di",
+"Wed." => "Mi",
+"Thu." => "Do",
+"Fri." => "Fr",
+"Sat." => "Sa",
+"Jan." => "Jan.",
+"Feb." => "Feb.",
+"Mar." => "Mär.",
+"Apr." => "Apr.",
+"May." => "Mai",
+"Jun." => "Jun.",
+"Jul." => "Jul.",
+"Aug." => "Aug.",
+"Sep." => "Sep.",
+"Oct." => "Okt.",
+"Nov." => "Nov.",
+"Dec." => "Dez.",
"All day" => "Ganztags",
-"New Calendar" => "Neuer Kalender",
"Missing fields" => "fehlende Felder",
"Title" => "Titel",
"From Date" => "Startdatum",
@@ -132,18 +158,14 @@
"Interval" => "Intervall",
"End" => "Ende",
"occurrences" => "Termine",
-"Import a calendar file" => "Kalenderdatei Importieren",
-"Please choose the calendar" => "Bitte wählen Sie den Kalender.",
"create a new calendar" => "Neuen Kalender anlegen",
+"Import a calendar file" => "Kalenderdatei Importieren",
"Name of new calendar" => "Kalendername",
"Import" => "Importieren",
-"Importing calendar" => "Kalender wird importiert.",
-"Calendar imported successfully" => "Kalender erfolgreich importiert",
"Close Dialog" => "Dialog schließen",
"Create a new event" => "Neues Ereignis",
"View an event" => "Termin öffnen",
"No categories selected" => "Keine Kategorie ausgewählt",
-"Select category" => "Kategorie auswählen",
"of" => "von",
"at" => "um",
"Timezone" => "Zeitzone",
@@ -152,9 +174,8 @@
"24h" => "24h",
"12h" => "12h",
"First day of the week" => "erster Wochentag",
-"Calendar CalDAV syncing address:" => "Kalender CalDAV Synchronisationsadresse:",
-"Users" => "Nutzer",
-"select users" => "Nutzer auswählen",
+"Users" => "Benutzer",
+"select users" => "Benutzer auswählen",
"Editable" => "editierbar",
"Groups" => "Gruppen",
"select groups" => "Gruppen auswählen",
diff --git a/apps/calendar/lib/app.php b/apps/calendar/lib/app.php
index 1a13f2958c0..29e5ab5b0c8 100644
--- a/apps/calendar/lib/app.php
+++ b/apps/calendar/lib/app.php
@@ -9,7 +9,7 @@
* This class manages our app actions
*/
OC_Calendar_App::$l10n = new OC_L10N('calendar');
-OC_Calendar_App::$tz = OCP\Config::getUserValue(OCP\USER::getUser(), 'calendar', 'timezone', date_default_timezone_get());
+OC_Calendar_App::$tz = OC_Calendar_App::getTimezone();
class OC_Calendar_App{
const CALENDAR = 'calendar';
const EVENT = 'event';
@@ -282,7 +282,17 @@ class OC_Calendar_App{
public static function getWeekofMonth(){
return OC_Calendar_Object::getWeekofMonth(self::$l10n);
}
-
+
+ /**
+ * @return (string) $timezone as set by user or the default timezone
+ */
+ public static function getTimezone() {
+ return OCP\Config::getUserValue(OCP\User::getUser(),
+ 'calendar',
+ 'timezone',
+ date_default_timezone_get());
+ }
+
/**
* @brief checks the access for a calendar / an event
* @param (int) $id - id of the calendar / event
diff --git a/apps/calendar/lib/calendar.php b/apps/calendar/lib/calendar.php
index 128b55c48e9..7778242464c 100644
--- a/apps/calendar/lib/calendar.php
+++ b/apps/calendar/lib/calendar.php
@@ -267,8 +267,42 @@ class OC_Calendar_Calendar{
'url' => OCP\Util::linkTo('calendar', 'ajax/events.php').'?calendar_id='.$calendar['id'],
'backgroundColor' => $calendar['calendarcolor'],
'borderColor' => '#888',
- 'textColor' => 'black',
+ 'textColor' => self::generateTextColor($calendar['calendarcolor']),
'cache' => true,
);
}
+
+ /*
+ * @brief checks if a calendar name is available for a user
+ * @param string $calendarname
+ * @param string $userid
+ * @return boolean
+ */
+ public static function isCalendarNameavailable($calendarname, $userid){
+ $calendars = self::allCalendars($userid);
+ foreach($calendars as $calendar){
+ if($calendar['displayname'] == $calendarname){
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /*
+ * @brief generates the text color for the calendar
+ * @param string $calendarcolor rgb calendar color code in hex format (with or without the leading #)
+ * (this function doesn't pay attention on the alpha value of rgba color codes)
+ * @return boolean
+ */
+ public static function generateTextColor($calendarcolor){
+ if(substr_count($calendarcolor, '#') == 1){
+ $calendarcolor = substr($calendarcolor,1);
+ }
+ $red = hexdec(substr($calendarcolor,0,2));
+ $green = hexdec(substr($calendarcolor,2,2));
+ $blue = hexdec(substr($calendarcolor,2,2));
+ //recommendation by W3C
+ $computation = ((($red * 299) + ($green * 587) + ($blue * 114)) / 1000);
+ return ($computation > 130)?'#000000':'#FAFAFA';
+ }
}
diff --git a/apps/calendar/lib/hooks.php b/apps/calendar/lib/hooks.php
index 328d2951d23..bc0b02c62b8 100644
--- a/apps/calendar/lib/hooks.php
+++ b/apps/calendar/lib/hooks.php
@@ -11,7 +11,18 @@
*/
class OC_Calendar_Hooks{
/**
- * @brief Deletes all Addressbooks of a certain user
+ * @brief Creates default calendar for a user
+ * @param paramters parameters from postCreateUser-Hook
+ * @return array
+ */
+ public static function createUser($parameters) {
+ OC_Calendar_Calendar::addCalendar($parameters['uid'],'Default calendar');
+
+ return true;
+ }
+
+ /**
+ * @brief Deletes all calendars of a certain user
* @param paramters parameters from postDeleteUser-Hook
* @return array
*/
diff --git a/apps/calendar/lib/import.php b/apps/calendar/lib/import.php
new file mode 100644
index 00000000000..d36891cb2b9
--- /dev/null
+++ b/apps/calendar/lib/import.php
@@ -0,0 +1,334 @@
+<?php
+/**
+ * Copyright (c) 2012 Georg Ehrke <ownclouddev@georgswebsite.de>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+/*
+ * This class does import and converts all times to the users current timezone
+ */
+class OC_Calendar_Import{
+ /*
+ * @brief counts the absolute number of parsed elements
+ */
+ private $abscount;
+
+ /*
+ * @brief var saves if the percentage should be saved with OC_Cache
+ */
+ private $cacheprogress;
+
+ /*
+ * @brief Sabre_VObject_Component_VCalendar object - for documentation see http://code.google.com/p/sabredav/wiki/Sabre_VObject_Component_VCalendar
+ */
+ private $calobject;
+
+ /*
+ * @brief var counts the number of imported elements
+ */
+ private $count;
+
+ /*
+ * @brief var to check if errors happend while initialization
+ */
+ private $error;
+
+ /*
+ * @brief var saves the ical string that was submitted with the __construct function
+ */
+ private $ical;
+
+ /*
+ * @brief calendar id for import
+ */
+ private $id;
+
+ /*
+ * @brief var saves the percentage of the import's progress
+ */
+ private $progress;
+
+ /*
+ * @brief var saves the key for the percentage of the import's progress
+ */
+ private $progresskey;
+
+ /*
+ * @brief var saves the timezone the events shell converted to
+ */
+ private $tz;
+
+ /*
+ * @brief var saves the userid
+ */
+ private $userid;
+
+ /*
+ * public methods
+ */
+
+ /*
+ * @brief does general initialization for import object
+ * @param string $calendar content of ical file
+ * @param string $tz timezone of the user
+ * @return boolean
+ */
+ public function __construct($ical){
+ $this->error = null;
+ $this->ical = $ical;
+ $this->abscount = 0;
+ $this->count = 0;
+ try{
+ $this->calobject = OC_VObject::parse($this->ical);
+ }catch(Exception $e){
+ //MISSING: write some log
+ $this->error = true;
+ return false;
+ }
+ return true;
+ }
+
+ /*
+ * @brief imports a calendar
+ * @return boolean
+ */
+ public function import(){
+ if(!$this->isValid()){
+ return false;
+ }
+ $numofcomponents = count($this->calobject->getComponents());
+ foreach($this->calobject->getComponents() as $object){
+ if(!($object instanceof Sabre_VObject_Component_VEvent) && !($object instanceof Sabre_VObject_Component_VJournal) && !($object instanceof Sabre_VObject_Component_VTodo)){
+ continue;
+ }
+ $dtend = OC_Calendar_Object::getDTEndFromVEvent($object);
+ $object->DTSTART->getDateTime()->setTimezone(new DateTimeZone($this->tz));
+ $object->DTEND->setDateTime($dtend->getDateTime(), $object->DTSTART->getDateType());
+ $object->DTEND->getDateTime()->setTimezone(new DateTimeZone($this->tz));
+ $vcalendar = $this->createVCalendar($object->serialize());
+ $insertid = OC_Calendar_Object::add($this->id, $vcalendar);
+ $this->abscount++;
+ if($this->isDuplicate($insertid)){
+ OC_Calendar_Object::delete($insertid);
+ }else{
+ $this->count++;
+ }
+ $this->updateProgress(intval(($this->abscount / $numofcomponents)*100));
+ }
+ OC_Cache::remove($this->progresskey);
+ return true;
+ }
+
+ /*
+ * @brief sets the timezone
+ * @return boolean
+ */
+ public function setTimeZone($tz){
+ $this->tz = $tz;
+ return true;
+ }
+
+ /*
+ * @brief sets the progresskey
+ * @return boolean
+ */
+ public function setProgresskey($progresskey){
+ $this->progresskey = $progresskey;
+ return true;
+ }
+
+ /*
+ * @brief checks if something went wrong while initialization
+ * @return boolean
+ */
+ public function isValid(){
+ if(is_null($this->error)){
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * @brief returns the percentage of progress
+ * @return integer
+ */
+ public function getProgress(){
+ return $this->progress;
+ }
+
+ /*
+ * @brief enables the cache for the percentage of progress
+ * @return boolean
+ */
+ public function enableProgressCache(){
+ $this->cacheprogress = true;
+ return true;
+ }
+
+ /*
+ * @brief disables the cache for the percentage of progress
+ * @return boolean
+ */
+ public function disableProgressCache(){
+ $this->cacheprogress = false;
+ return false;
+ }
+
+ /*
+ * @brief generates a new calendar name
+ * @return string
+ */
+ public function createCalendarName(){
+ $calendars = OC_Calendar_Calendar::allCalendars($this->userid);
+ $calendarname = $guessedcalendarname = !is_null($this->guessCalendarName())?($this->guessCalendarName()):(OC_Calendar_App::$l10n->t('New Calendar'));
+ $i = 1;
+ while(!OC_Calendar_Calendar::isCalendarNameavailable($calendarname, $this->userid)){
+ $calendarname = $guessedcalendarname . ' (' . $i . ')';
+ $i++;
+ }
+ return $calendarname;
+ }
+
+ /*
+ * @brief generates a new calendar color
+ * @return string
+ */
+ public function createCalendarColor(){
+ if(is_null($this->guessCalendarColor())){
+ return '#9fc6e7';
+ }
+ return $this->guessCalendarColor();
+ }
+
+ /*
+ * @brief sets the id for the calendar
+ * @param integer $id of the calendar
+ * @return boolean
+ */
+ public function setCalendarID($id){
+ $this->id = $id;
+ return true;
+ }
+
+ /*
+ * @brief sets the userid to import the calendar
+ * @param string $id of the user
+ * @return boolean
+ */
+ public function setUserID($userid){
+ $this->userid = $userid;
+ return true;
+ }
+
+ /*
+ * @brief returns the private
+ * @param string $id of the user
+ * @return boolean
+ */
+ public function getCount(){
+ return $this->count;
+ }
+
+ /*
+ * private methods
+ */
+
+ /*
+ * @brief generates an unique ID
+ * @return string
+ */
+ //private function createUID(){
+ // return substr(md5(rand().time()),0,10);
+ //}
+
+ /*
+ * @brief checks is the UID is already in use for another event
+ * @param string $uid uid to check
+ * @return boolean
+ */
+ //private function isUIDAvailable($uid){
+ //
+ //}
+
+ /*
+ * @brief generates a proper VCalendar string
+ * @param string $vobject
+ * @return string
+ */
+ private function createVCalendar($vobject){
+ if(is_object($vobject)){
+ $vobject = @$vobject->serialize();
+ }
+ $vcalendar = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:ownCloud Calendar " . OCP\App::getAppVersion('calendar') . "\n";
+ $vcalendar .= $vobject;
+ $vcalendar .= "END:VCALENDAR";
+ return $vcalendar;
+ }
+
+ /*
+ * @brief checks if an event already exists in the user's calendars
+ * @param integer $insertid id of the new object
+ * @return boolean
+ */
+ private function isDuplicate($insertid){
+ $newobject = OC_Calendar_Object::find($insertid);
+ $stmt = OCP\DB::prepare('SELECT COUNT(*) as count FROM *PREFIX*calendar_objects WHERE objecttype=? AND startdate=? AND enddate=? AND repeating=? AND summary=? AND calendardata=?');
+ $result = $stmt->execute(array($newobject['objecttype'],$newobject['startdate'],$newobject['enddate'],$newobject['repeating'],$newobject['summary'],$newobject['calendardata']));
+ $result = $result->fetchRow();
+ if($result['count'] >= 2){
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * @brief updates the progress var
+ * @param integer $percentage
+ * @return boolean
+ */
+ private function updateProgress($percentage){
+ $this->progress = $percentage;
+ if($this->cacheprogress){
+ OC_Cache::set($this->progresskey, $this->progress, 300);
+ }
+ return true;
+ }
+
+ /*
+ * public methods for (pre)rendering of X-... Attributes
+ */
+
+ /*
+ * @brief guesses the calendar color
+ * @return mixed - string or boolean
+ */
+ public function guessCalendarColor(){
+ if(!is_null($this->calobject->__get('X-APPLE-CALENDAR-COLOR'))){
+ return $this->calobject->__get('X-APPLE-CALENDAR-COLOR');
+ }
+ return null;
+ }
+
+ /*
+ * @brief guesses the calendar description
+ * @return mixed - string or boolean
+ */
+ public function guessCalendarDescription(){
+ if(!is_null($this->calobject->__get('X-WR-CALDESC'))){
+ return $this->calobject->__get('X-WR-CALDESC');
+ }
+ return null;
+ }
+
+ /*
+ * @brief guesses the calendar name
+ * @return mixed - string or boolean
+ */
+ public function guessCalendarName(){
+ if(!is_null($this->calobject->__get('X-WR-CALNAME'))){
+ return $this->calobject->__get('X-WR-CALNAME');
+ }
+ return null;
+ }
+}
diff --git a/apps/calendar/lib/object.php b/apps/calendar/lib/object.php
index 140542bf4fb..34676830205 100644
--- a/apps/calendar/lib/object.php
+++ b/apps/calendar/lib/object.php
@@ -856,7 +856,7 @@ class OC_Calendar_Object{
$vevent->setDateTime('DTSTART', $start, Sabre_VObject_Property_DateTime::DATE);
$vevent->setDateTime('DTEND', $end, Sabre_VObject_Property_DateTime::DATE);
}else{
- $timezone = OCP\Config::getUserValue(OCP\USER::getUser(), 'calendar', 'timezone', date_default_timezone_get());
+ $timezone = OC_Calendar_App::getTimezone();
$timezone = new DateTimeZone($timezone);
$start = new DateTime($from.' '.$fromtime, $timezone);
$end = new DateTime($to.' '.$totime, $timezone);
diff --git a/apps/calendar/lib/search.php b/apps/calendar/lib/search.php
index 560330f65e9..551489672b9 100644
--- a/apps/calendar/lib/search.php
+++ b/apps/calendar/lib/search.php
@@ -12,7 +12,7 @@ class OC_Search_Provider_Calendar extends OC_Search_Provider{
}else{
$searchquery[] = $query;
}
- $user_timezone = OCP\Config::getUserValue(OCP\USER::getUser(), 'calendar', 'timezone', date_default_timezone_get());
+ $user_timezone = OC_Calendar_App::getTimezone();
$l = new OC_l10n('calendar');
foreach($calendars as $calendar){
$objects = OC_Calendar_Object::all($calendar['id']);
diff --git a/apps/calendar/lib/share_backend.php b/apps/calendar/lib/share_backend.php
new file mode 100644
index 00000000000..f937c0d1c34
--- /dev/null
+++ b/apps/calendar/lib/share_backend.php
@@ -0,0 +1,44 @@
+<?php
+/**
+* ownCloud
+*
+* @author Michael Gapczynski
+* @copyright 2012 Michael Gapczynski mtgap@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/>.
+*/
+
+class OC_Share_Backend_Calendar extends OCP\Share_Backend {
+
+ public function getSource($item, $uid) {
+ $query = OCP\DB::prepare('SELECT id FROM *PREFIX*calendar_calendars WHERE userid = ? AND displayname = ? LIMIT 1');
+ return $query->execute(array($uid, $item))->fetchAll();
+ }
+
+ public function generateTarget($item, $uid) {
+
+ }
+
+ public function getItems($sources) {
+
+ }
+
+}
+
+class OC_Share_Backend_Event extends OCP\Share_Backend {
+
+}
+
+
+?> \ No newline at end of file
diff --git a/apps/calendar/templates/calendar.php b/apps/calendar/templates/calendar.php
index 2246a0178e3..29b9bf6bc54 100644
--- a/apps/calendar/templates/calendar.php
+++ b/apps/calendar/templates/calendar.php
@@ -2,10 +2,10 @@
var defaultView = '<?php echo OCP\Config::getUserValue(OCP\USER::getUser(), 'calendar', 'currentview', 'month') ?>';
var eventSources = <?php echo json_encode($_['eventSources']) ?>;
var categories = <?php echo json_encode($_['categories']); ?>;
- var dayNames = <?php echo json_encode($l->tA(array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'))) ?>;
- var dayNamesShort = <?php echo json_encode($l->tA(array('Sun.', 'Mon.', 'Tue.', 'Wed.', 'Thu.', 'Fri.', 'Sat.'))) ?>;
- var monthNames = <?php echo json_encode($l->tA(array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'))) ?>;
- var monthNamesShort = <?php echo json_encode($l->tA(array('Jan.', 'Feb.', 'Mar.', 'Apr.', 'May.', 'Jun.', 'Jul.', 'Aug.', 'Sep.', 'Oct.', 'Nov.', 'Dec.'))) ?>;
+ var dayNames = new Array("<?php echo $l -> t("Sunday");?>", "<?php echo $l -> t("Monday");?>", "<?php echo $l -> t("Tuesday");?>", "<?php echo $l -> t("Wednesday");?>", "<?php echo $l -> t("Thursday");?>", "<?php echo $l -> t("Friday");?>", "<?php echo $l -> t("Saturday");?>");
+ var dayNamesShort = new Array("<?php echo $l -> t("Sun.");?>", "<?php echo $l -> t("Mon.");?>", "<?php echo $l -> t("Tue.");?>", "<?php echo $l -> t("Wed.");?>", "<?php echo $l -> t("Thu.");?>", "<?php echo $l -> t("Fri.");?>", "<?php echo $l -> t("Sat.");?>");
+ var monthNames = new Array("<?php echo $l -> t("January");?>", "<?php echo $l -> t("February");?>", "<?php echo $l -> t("March");?>", "<?php echo $l -> t("April");?>", "<?php echo $l -> t("May");?>", "<?php echo $l -> t("June");?>", "<?php echo $l -> t("July");?>", "<?php echo $l -> t("August");?>", "<?php echo $l -> t("September");?>", "<?php echo $l -> t("October");?>", "<?php echo $l -> t("November");?>", "<?php echo $l -> t("December");?>");
+ var monthNamesShort = new Array("<?php echo $l -> t("Jan.");?>", "<?php echo $l -> t("Feb.");?>", "<?php echo $l -> t("Mar.");?>", "<?php echo $l -> t("Apr.");?>", "<?php echo $l -> t("May.");?>", "<?php echo $l -> t("Jun.");?>", "<?php echo $l -> t("Jul.");?>", "<?php echo $l -> t("Aug.");?>", "<?php echo $l -> t("Sep.");?>", "<?php echo $l -> t("Oct.");?>", "<?php echo $l -> t("Nov.");?>", "<?php echo $l -> t("Dec.");?>");
var agendatime = '<?php echo ((int) OCP\Config::getUserValue(OCP\USER::getUser(), 'calendar', 'timeformat', '24') == 24 ? 'HH:mm' : 'hh:mm tt'); ?>{ - <?php echo ((int) OCP\Config::getUserValue(OCP\USER::getUser(), 'calendar', 'timeformat', '24') == 24 ? 'HH:mm' : 'hh:mm tt'); ?>}';
var defaulttime = '<?php echo ((int) OCP\Config::getUserValue(OCP\USER::getUser(), 'calendar', 'timeformat', '24') == 24 ? 'HH:mm' : 'hh:mm tt'); ?>';
var allDayText = '<?php echo addslashes($l->t('All day')) ?>';
diff --git a/apps/calendar/templates/part.import.php b/apps/calendar/templates/part.import.php
index 70ff9612157..2ce3cc34239 100644
--- a/apps/calendar/templates/part.import.php
+++ b/apps/calendar/templates/part.import.php
@@ -1,30 +1,58 @@
-<div id="calendar_import_dialog" title="<?php echo $l->t("Import a calendar file"); ?>">
-<div id="form_container">
-<input type="hidden" id="filename" value="<?php echo $_['filename'];?>">
-<input type="hidden" id="path" value="<?php echo $_['path'];?>">
-<input type="hidden" id="progresskey" value="<?php echo rand() ?>">
-<p style="text-align:center;"><b><?php echo $l->t('Please choose the calendar'); ?></b></p>
-<select style="width:100%;" id="calendar" name="calendar">
<?php
+//Prerendering for iCalendar file
+$file = OC_Filesystem::file_get_contents($_['path'] . '/' . $_['filename']);
+if(!$file){
+ OCP\JSON::error(array('error'=>'404'));
+}
+$import = new OC_Calendar_Import($file);
+$import->setUserID(OCP\User::getUser());
+$newcalendarname = strip_tags($import->createCalendarName());
+$guessedcalendarname = strip_tags($import->guessCalendarName());
+$calendarcolor = strip_tags($import->createCalendarColor());
+//loading calendars for select box
$calendar_options = OC_Calendar_Calendar::allCalendars(OCP\USER::getUser());
$calendar_options[] = array('id'=>'newcal', 'displayname'=>$l->t('create a new calendar'));
-for($i = 0;$i<count($calendar_options);$i++){
- $calendar_options[$i]['displayname'] = $calendar_options[$i]['displayname'];
-}
-echo OCP\html_select_options($calendar_options, $calendar_options[0]['id'], array('value'=>'id', 'label'=>'displayname'));
+$defaultcolors = OC_Calendar_Calendar::getCalendarColorOptions();
?>
-</select>
-<div id="newcalform" style="display: none;">
- <input type="text" style="width: 97%;" placeholder="<?php echo $l->t('Name of new calendar'); ?>" id="newcalendar" name="newcalendar">
-</div>
-<input type="button" value="<?php echo $l->t("Import");?>!" id="startimport">
-</div>
-<div id="progressbar_container" style="display: none">
-<p style="text-align:center;"><b><?php echo $l->t('Importing calendar'); ?></b></p>
-<div id="progressbar"></div>
-<div id="import_done" style="display: none;">
-<p style="text-align:center;"><b><?php echo $l->t('Calendar imported successfully'); ?></b></p>
-<input type="button" value="<?php echo $l->t('Close Dialog'); ?>" id="import_done_button">
+<div id="calendar_import_dialog" title="<?php echo $l->t("Import a calendar file");?>">
+<div id="calendar_import_form">
+ <form>
+ <input type="hidden" id="calendar_import_filename" value="<?php echo $_['filename'];?>">
+ <input type="hidden" id="calendar_import_path" value="<?php echo $_['path'];?>">
+ <input type="hidden" id="calendar_import_progresskey" value="<?php echo rand() ?>">
+ <input type="hidden" id="calendar_import_availablename" value="<?php echo $newcalendarname ?>">
+ <div id="calendar_import_form_message"><?php echo $l->t('Please choose a calendar'); ?></div>
+ <select style="width:100%;" id="calendar_import_calendar" name="calendar_import_calendar">
+ <?php
+ for($i = 0;$i<count($calendar_options);$i++){
+ $calendar_options[$i]['displayname'] = $calendar_options[$i]['displayname'];
+ }
+ echo OCP\html_select_options($calendar_options, $calendar_options[0]['id'], array('value'=>'id', 'label'=>'displayname'));
+ ?>
+ </select>
+ <br><br>
+ <div id="calendar_import_newcalform">
+ <input id="calendar_import_newcalendar_color" class="color-picker" type="hidden" size="6" value="<?php echo substr($calendarcolor,1); ?>">
+ <input id="calendar_import_newcalendar" class="" type="text" placeholder="<?php echo $l->t('Name of new calendar'); ?>" value="<?php echo $guessedcalendarname ?>"><br>
+ <div id="calendar_import_defaultcolors">
+ <?php
+ foreach($defaultcolors as $color){
+ echo '<span class="calendar-colorpicker-color" rel="' . $color . '" style="background-color: ' . $color . ';"></span>';
+ }
+ ?>
+ </div>
+ <!--<input id="calendar_import_generatename" type="button" class="button" value="<?php echo $l->t('Take an available name!'); ?>"><br>-->
+ <div id="calendar_import_mergewarning" class="hint"><?php echo $l->t('A Calendar with this name already exists. If you continue anyhow, these calendars will be merged.'); ?></div>
+ </div>
+ <input id="calendar_import_submit" type="button" class="button" value="&raquo; <?php echo $l->t('Import'); ?> &raquo;" id="startimport">
+ <form>
</div>
+<div id="calendar_import_process">
+ <div id="calendar_import_process_message"></div>
+ <div id="calendar_import_progressbar"></div>
+ <br>
+ <div id="calendar_import_status" class="hint"></div>
+ <br>
+ <input id="calendar_import_done" type="button" value="<?php echo $l->t('Close Dialog'); ?>">
</div>
</div> \ No newline at end of file
diff --git a/apps/contacts/ajax/activation.php b/apps/contacts/ajax/activation.php
index 74cb738ab8f..69173c54c44 100644
--- a/apps/contacts/ajax/activation.php
+++ b/apps/contacts/ajax/activation.php
@@ -16,8 +16,12 @@ $bookid = $_POST['bookid'];
$book = OC_Contacts_App::getAddressbook($bookid);// is owner access check
if(!OC_Contacts_Addressbook::setActive($bookid, $_POST['active'])) {
- OCP\Util::writeLog('contacts','ajax/activation.php: Error activating addressbook: '.$bookid, OCP\Util::ERROR);
- OCP\JSON::error(array('data' => array('message' => OC_Contacts_App::$l10n->t('Error (de)activating addressbook.'))));
+ OCP\Util::writeLog('contacts',
+ 'ajax/activation.php: Error activating addressbook: '. $bookid,
+ OCP\Util::ERROR);
+ OCP\JSON::error(array(
+ 'data' => array(
+ 'message' => OC_Contacts_App::$l10n->t('Error (de)activating addressbook.'))));
exit();
}
diff --git a/apps/contacts/ajax/addaddressbook.php b/apps/contacts/ajax/addaddressbook.php
new file mode 100644
index 00000000000..40773704bb4
--- /dev/null
+++ b/apps/contacts/ajax/addaddressbook.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Copyright (c) 2011-2012 Thomas Tanghus <thomas@tanghus.net>
+ * Copyright (c) 2011 Bart Visscher <bartv@thisnet.nl>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('contacts');
+OCP\JSON::callCheck();
+require_once 'loghandler.php';
+
+debug('name: '.$_POST['name']);
+
+$userid = OCP\USER::getUser();
+$name = isset($_POST['name'])?trim(strip_tags($_POST['name'])):null;
+$description = isset($_POST['description'])
+ ? trim(strip_tags($_POST['description']))
+ : null;
+
+if(is_null($name)) {
+ bailOut('Cannot add addressbook with an empty name.');
+}
+$bookid = OC_Contacts_Addressbook::add($userid, $name, $description);
+if(!$bookid) {
+ bailOut('Error adding addressbook: '.$name);
+}
+
+if(!OC_Contacts_Addressbook::setActive($bookid, 1)) {
+ bailOut('Error activating addressbook.');
+}
+$addressbook = OC_Contacts_App::getAddressbook($bookid);
+OCP\JSON::success(array('data' => $addressbook));
diff --git a/apps/contacts/ajax/addcontact.php b/apps/contacts/ajax/addcontact.php
index 12f7bb9db96..6aaf5a9df35 100644
--- a/apps/contacts/ajax/addcontact.php
+++ b/apps/contacts/ajax/addcontact.php
@@ -37,13 +37,15 @@ $n = trim($_POST['n']);
$vcard = new OC_VObject('VCARD');
$vcard->setUID();
-$vcard->setString('FN',$fn);
-$vcard->setString('N',$n);
+$vcard->setString('FN', $fn);
+$vcard->setString('N', $n);
$id = OC_Contacts_VCard::add($aid, $vcard, null, $isnew);
if(!$id) {
- OCP\JSON::error(array('data' => array('message' => OC_Contacts_App::$l10n->t('There was an error adding the contact.'))));
- OCP\Util::writeLog('contacts','ajax/addcontact.php: Recieved non-positive ID on adding card: '.$id, OCP\Util::ERROR);
+ OCP\JSON::error(array(
+ 'data' => array(
+ 'message' => OC_Contacts_App::$l10n->t('There was an error adding the contact.'))));
+ OCP\Util::writeLog('contacts', 'ajax/addcontact.php: Recieved non-positive ID on adding card: '.$id, OCP\Util::ERROR);
exit();
}
diff --git a/apps/contacts/ajax/addproperty.php b/apps/contacts/ajax/addproperty.php
index 1b6db0c8f81..58b857547fb 100644
--- a/apps/contacts/ajax/addproperty.php
+++ b/apps/contacts/ajax/addproperty.php
@@ -25,7 +25,7 @@ OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
-require_once('loghandler.php');
+require_once 'loghandler.php';
$id = isset($_POST['id'])?$_POST['id']:null;
$name = isset($_POST['name'])?$_POST['name']:null;
@@ -33,22 +33,27 @@ $value = isset($_POST['value'])?$_POST['value']:null;
$parameters = isset($_POST['parameters'])?$_POST['parameters']:array();
$vcard = OC_Contacts_App::getContactVCard($id);
+$l10n = OC_Contacts_App::$l10n;
if(!$name) {
- bailOut(OC_Contacts_App::$l10n->t('element name is not set.'));
+ bailOut($l10n->t('element name is not set.'));
}
if(!$id) {
- bailOut(OC_Contacts_App::$l10n->t('id is not set.'));
+ bailOut($l10n->t('id is not set.'));
}
if(!$vcard) {
- bailOut(OC_Contacts_App::$l10n->t('Could not parse contact: ').$id);
+ bailOut($l10n->t('Could not parse contact: ').$id);
}
-if(!is_array($value)){
+if(!is_array($value)) {
$value = trim($value);
- if(!$value && in_array($name, array('TEL', 'EMAIL', 'ORG', 'BDAY', 'URL', 'NICKNAME', 'NOTE'))) {
- bailOut(OC_Contacts_App::$l10n->t('Cannot add empty property.'));
+ if(!$value
+ && in_array(
+ $name,
+ array('TEL', 'EMAIL', 'ORG', 'BDAY', 'URL', 'NICKNAME', 'NOTE'))
+ ) {
+ bailOut($l10n->t('Cannot add empty property.'));
}
} elseif($name === 'ADR') { // only add if non-empty elements.
$empty = true;
@@ -59,7 +64,7 @@ if(!is_array($value)){
}
}
if($empty) {
- bailOut(OC_Contacts_App::$l10n->t('At least one of the address fields has to be filled out.'));
+ bailOut($l10n->t('At least one of the address fields has to be filled out.'));
}
}
@@ -68,12 +73,14 @@ $current = $vcard->select($name);
foreach($current as $item) {
$tmpvalue = (is_array($value)?implode(';', $value):$value);
if($tmpvalue == $item->value) {
- bailOut(OC_Contacts_App::$l10n->t('Trying to add duplicate property: '.$name.': '.$tmpvalue));
+ bailOut($l10n->t('Trying to add duplicate property: '.$name.': '.$tmpvalue));
}
}
if(is_array($value)) {
- ksort($value); // NOTE: Important, otherwise the compound value will be set in the order the fields appear in the form!
+ // NOTE: Important, otherwise the compound value will
+ // be set in the order the fields appear in the form!
+ ksort($value);
$value = array_map('strip_tags', $value);
} else {
$value = strip_tags($value);
@@ -116,24 +123,25 @@ switch($name) {
$line = count($vcard->children) - 1;
-// Apparently Sabre_VObject_Parameter doesn't do well with multiple values or I don't know how to do it. Tanghus.
+// Apparently Sabre_VObject_Parameter doesn't do well with
+// multiple values or I don't know how to do it. Tanghus.
foreach ($parameters as $key=>$element) {
if(is_array($element) && strtoupper($key) == 'TYPE') {
// NOTE: Maybe this doesn't only apply for TYPE?
// And it probably shouldn't be done here anyways :-/
- foreach($element as $e){
- if($e != '' && !is_null($e)){
- $vcard->children[$line]->parameters[] = new Sabre_VObject_Parameter($key,$e);
+ foreach($element as $e) {
+ if($e != '' && !is_null($e)) {
+ $vcard->children[$line]->parameters[] = new Sabre_VObject_Parameter($key, $e);
}
}
} else {
- $vcard->children[$line]->parameters[] = new Sabre_VObject_Parameter($key,$element);
+ $vcard->children[$line]->parameters[] = new Sabre_VObject_Parameter($key, $element);
}
}
$checksum = md5($vcard->children[$line]->serialize());
-if(!OC_Contacts_VCard::edit($id,$vcard)) {
- bailOut(OC_Contacts_App::$l10n->t('Error adding contact property: '.$name));
+if(!OC_Contacts_VCard::edit($id, $vcard)) {
+ bailOut($l10n->t('Error adding contact property: '.$name));
}
OCP\JSON::success(array('data' => array( 'checksum' => $checksum )));
diff --git a/apps/contacts/ajax/categories/categoriesfor.php b/apps/contacts/ajax/categories/categoriesfor.php
index 6b6fcad0ebb..8391b14b545 100644
--- a/apps/contacts/ajax/categories/categoriesfor.php
+++ b/apps/contacts/ajax/categories/categoriesfor.php
@@ -12,16 +12,23 @@ OCP\JSON::checkAppEnabled('contacts');
$id = isset($_GET['id'])?$_GET['id']:null;
if(is_null($id)) {
- OCP\JSON::error(array('data' => array('message' => OC_Contacts_App::$l10n->t('No ID provided'))));
+ OCP\JSON::error(array(
+ 'data' => array(
+ 'message' => OC_Contacts_App::$l10n->t('No ID provided'))));
exit();
}
$vcard = OC_Contacts_App::getContactVCard( $id );
foreach($vcard->children as $property){
- //OCP\Util::writeLog('contacts','ajax/categories/checksumfor.php: '.$property->name, OCP\Util::DEBUG);
if($property->name == 'CATEGORIES') {
$checksum = md5($property->serialize());
- OCP\JSON::success(array('data' => array('value'=>$property->value, 'checksum'=>$checksum)));
+ OCP\JSON::success(array(
+ 'data' => array(
+ 'value' => $property->value,
+ 'checksum' => $checksum,
+ )));
exit();
}
}
-OCP\JSON::error(array('data' => array('message' => OC_Contacts_App::$l10n->t('Error setting checksum.'))));
+OCP\JSON::error(array(
+ 'data' => array(
+ 'message' => OC_Contacts_App::$l10n->t('Error setting checksum.'))));
diff --git a/apps/contacts/ajax/categories/delete.php b/apps/contacts/ajax/categories/delete.php
index 7c3261446bb..bc9f3e14e87 100644
--- a/apps/contacts/ajax/categories/delete.php
+++ b/apps/contacts/ajax/categories/delete.php
@@ -9,8 +9,9 @@
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
+OCP\JSON::callCheck();
-require_once('../loghandler.php');
+require_once __DIR__.'/../loghandler.php';
$categories = isset($_POST['categories'])?$_POST['categories']:null;
diff --git a/apps/contacts/ajax/categories/rescan.php b/apps/contacts/ajax/categories/rescan.php
index fd875a965dc..a06e7803955 100644
--- a/apps/contacts/ajax/categories/rescan.php
+++ b/apps/contacts/ajax/categories/rescan.php
@@ -9,6 +9,7 @@
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
+OCP\JSON::callCheck();
OC_Contacts_App::scanCategories();
$categories = OC_Contacts_App::getCategories();
diff --git a/apps/contacts/ajax/contactdetails.php b/apps/contacts/ajax/contactdetails.php
index b697b1a8e5b..27d7611ade9 100644
--- a/apps/contacts/ajax/contactdetails.php
+++ b/apps/contacts/ajax/contactdetails.php
@@ -20,7 +20,7 @@
*
*/
-require_once('loghandler.php');
+require_once 'loghandler.php';
// Check if we are a user
OCP\JSON::checkLoggedIn();
@@ -30,6 +30,7 @@ $id = isset($_GET['id'])?$_GET['id']:null;
if(is_null($id)) {
bailOut(OC_Contacts_App::$l10n->t('Missing ID'));
}
+$card = OC_Contacts_VCard::find($id);
$vcard = OC_Contacts_App::getContactVCard( $id );
if(is_null($vcard)) {
bailOut(OC_Contacts_App::$l10n->t('Error parsing VCard for ID: "'.$id.'"'));
@@ -50,5 +51,7 @@ if(isset($details['PHOTO'])) {
$details['PHOTO'] = false;
}
$details['id'] = $id;
+$details['displayname'] = $card['fullname'];
+$details['addressbookid'] = $card['addressbookid'];
OC_Contacts_App::setLastModifiedHeader($vcard);
OCP\JSON::success(array('data' => $details));
diff --git a/apps/contacts/ajax/createaddressbook.php b/apps/contacts/ajax/createaddressbook.php
index 2ec5f542bb3..8dbd63f6425 100644
--- a/apps/contacts/ajax/createaddressbook.php
+++ b/apps/contacts/ajax/createaddressbook.php
@@ -12,7 +12,7 @@
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
-require_once('loghandler.php');
+require_once 'loghandler.php';
$userid = OCP\USER::getUser();
$name = trim(strip_tags($_POST['name']));
diff --git a/apps/contacts/ajax/currentphoto.php b/apps/contacts/ajax/currentphoto.php
index 8f60eca08ec..96080e661ef 100644
--- a/apps/contacts/ajax/currentphoto.php
+++ b/apps/contacts/ajax/currentphoto.php
@@ -24,7 +24,7 @@
OCP\JSON::setContentTypeHeader('text/plain');
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
-require_once('loghandler.php');
+require_once 'loghandler.php';
if (!isset($_GET['id'])) {
bailOut(OC_Contacts_App::$l10n->t('No contact ID was submitted.'));
diff --git a/apps/contacts/ajax/deletecard.php b/apps/contacts/ajax/deletecard.php
index 1161c18abda..9777046fc82 100644
--- a/apps/contacts/ajax/deletecard.php
+++ b/apps/contacts/ajax/deletecard.php
@@ -23,17 +23,7 @@
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
-require_once('loghandler.php');
-
-// foreach($_SERVER as $key=>$value) {
-// OCP\Util::writeLog('contacts','ajax/saveproperty.php: _SERVER: '.$key.'=>'.$value, OCP\Util::DEBUG);
-// }
-// foreach($_POST as $key=>$value) {
-// debug($key.'=>'.print_r($value, true));
-// }
-// foreach($_GET as $key=>$value) {
-// debug($key.'=>'.print_r($value, true));
-// }
+require_once 'loghandler.php';
$id = isset($_POST['id'])?$_POST['id']:null;
if(!$id) {
diff --git a/apps/contacts/ajax/deleteproperty.php b/apps/contacts/ajax/deleteproperty.php
index 90e5e64903e..205df8bc184 100644
--- a/apps/contacts/ajax/deleteproperty.php
+++ b/apps/contacts/ajax/deleteproperty.php
@@ -24,22 +24,23 @@
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
-require_once('loghandler.php');
+require_once 'loghandler.php';
$id = $_POST['id'];
$checksum = $_POST['checksum'];
+$l10n = OC_Contacts_App::$l10n;
$vcard = OC_Contacts_App::getContactVCard( $id );
$line = OC_Contacts_App::getPropertyLineByChecksum($vcard, $checksum);
-if(is_null($line)){
- bailOut(OC_Contacts_App::$l10n->t('Information about vCard is incorrect. Please reload the page.'));
+if(is_null($line)) {
+ bailOut($l10n->t('Information about vCard is incorrect. Please reload the page.'));
exit();
}
unset($vcard->children[$line]);
-if(!OC_Contacts_VCard::edit($id,$vcard)) {
- bailOut(OC_Contacts_App::$l10n->t('Error deleting contact property.'));
+if(!OC_Contacts_VCard::edit($id, $vcard)) {
+ bailOut($l10n->t('Error deleting contact property.'));
}
OCP\JSON::success(array('data' => array( 'id' => $id )));
diff --git a/apps/contacts/ajax/editaddress.php b/apps/contacts/ajax/editaddress.php
index 1eb9429d79c..b5e4b72ed57 100644
--- a/apps/contacts/ajax/editaddress.php
+++ b/apps/contacts/ajax/editaddress.php
@@ -34,8 +34,8 @@ if($checksum) {
$tmpl->assign('adr', $adr, false);
}
-$tmpl->assign('id',$id);
-$tmpl->assign('adr_types',$adr_types);
+$tmpl->assign('id', $id);
+$tmpl->assign('adr_types', $adr_types);
$page = $tmpl->fetchPage();
OCP\JSON::success(array('data' => array('page'=>$page, 'checksum'=>$checksum)));
diff --git a/apps/contacts/ajax/editname.php b/apps/contacts/ajax/editname.php
index 9e7c090eeed..eb55634011d 100644
--- a/apps/contacts/ajax/editname.php
+++ b/apps/contacts/ajax/editname.php
@@ -9,7 +9,7 @@
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
-require_once('loghandler.php');
+require_once 'loghandler.php';
$tmpl = new OCP\Template("contacts", "part.edit_name_dialog");
@@ -25,8 +25,8 @@ if($id) {
}
}
$name = array_map('htmlspecialchars', $name['value']);
- $tmpl->assign('name',$name, false);
- $tmpl->assign('id',$id, false);
+ $tmpl->assign('name', $name, false);
+ $tmpl->assign('id', $id, false);
} else {
bailOut(OC_Contacts_App::$l10n->t('Contact ID is missing.'));
}
diff --git a/apps/contacts/ajax/importaddressbook.php b/apps/contacts/ajax/importaddressbook.php
deleted file mode 100644
index 6b5b06681ce..00000000000
--- a/apps/contacts/ajax/importaddressbook.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-/**
- * Copyright (c) 2012 Georg Ehrke <ownclouddev at georgswebsite dot de>
- * This file is licensed under the Affero General Public License version 3 or
- * later.
- * See the COPYING-README file.
- */
-
-OCP\JSON::checkLoggedIn();
-OCP\App::checkAppEnabled('contacts');
-$upload_max_filesize = OCP\Util::computerFileSize(ini_get('upload_max_filesize'));
-$post_max_size = OCP\Util::computerFileSize(ini_get('post_max_size'));
-$maxUploadFilesize = min($upload_max_filesize, $post_max_size);
-
-$freeSpace=OC_Filesystem::free_space('/');
-$freeSpace=max($freeSpace,0);
-$maxUploadFilesize = min($maxUploadFilesize ,$freeSpace);
-
-$tmpl = new OCP\Template('contacts', 'part.importaddressbook');
-$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize);
-$tmpl->assign('requesttoken', $_SERVER['HTTP_REQUESTTOKEN']);
-$tmpl->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize));
-$tmpl->printpage();
diff --git a/apps/contacts/ajax/loadcard.php b/apps/contacts/ajax/loadcard.php
index 1309faaa7a2..75fe33ada6f 100644
--- a/apps/contacts/ajax/loadcard.php
+++ b/apps/contacts/ajax/loadcard.php
@@ -30,20 +30,20 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size);
$requesttoken = $_GET['requesttoken'];
$freeSpace=OC_Filesystem::free_space('/');
-$freeSpace=max($freeSpace,0);
-$maxUploadFilesize = min($maxUploadFilesize ,$freeSpace);
+$freeSpace=max($freeSpace, 0);
+$maxUploadFilesize = min($maxUploadFilesize, $freeSpace);
$adr_types = OC_Contacts_App::getTypesOfProperty('ADR');
$phone_types = OC_Contacts_App::getTypesOfProperty('TEL');
$email_types = OC_Contacts_App::getTypesOfProperty('EMAIL');
-$tmpl = new OCP\Template('contacts','part.contact');
+$tmpl = new OCP\Template('contacts', 'part.contact');
$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize);
$tmpl->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize));
-$tmpl->assign('adr_types',$adr_types);
-$tmpl->assign('phone_types',$phone_types);
-$tmpl->assign('email_types',$email_types);
+$tmpl->assign('adr_types', $adr_types);
+$tmpl->assign('phone_types', $phone_types);
+$tmpl->assign('email_types', $email_types);
$tmpl->assign('requesttoken', $requesttoken);
-$tmpl->assign('id','');
+$tmpl->assign('id', '');
$page = $tmpl->fetchPage();
OCP\JSON::success(array('data' => array( 'page' => $page )));
diff --git a/apps/contacts/ajax/loadintro.php b/apps/contacts/ajax/loadintro.php
index 6e8fcc4b049..1da08950ca0 100644
--- a/apps/contacts/ajax/loadintro.php
+++ b/apps/contacts/ajax/loadintro.php
@@ -25,7 +25,7 @@ OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
-$tmpl = new OCP\Template('contacts','part.no_contacts');
+$tmpl = new OCP\Template('contacts', 'part.no_contacts');
$page = $tmpl->fetchPage();
OCP\JSON::success(array('data' => array( 'page' => $page )));
diff --git a/apps/contacts/ajax/loghandler.php b/apps/contacts/ajax/loghandler.php
index 831b2e50c1e..be4b98c1112 100644
--- a/apps/contacts/ajax/loghandler.php
+++ b/apps/contacts/ajax/loghandler.php
@@ -20,13 +20,15 @@
*
*/
-function bailOut($msg, $tracelevel=1, $debuglevel=OCP\Util::ERROR) {
+function bailOut($msg, $tracelevel=1, $debuglevel=OCP\Util::ERROR)
+{
OCP\JSON::error(array('data' => array('message' => $msg)));
debug($msg, $tracelevel, $debuglevel);
exit();
}
-function debug($msg, $tracelevel=0, $debuglevel=OCP\Util::DEBUG) {
+function debug($msg, $tracelevel=0, $debuglevel=OCP\Util::DEBUG)
+{
if(PHP_VERSION >= "5.4") {
$call = debug_backtrace(false, $tracelevel+1);
} else {
@@ -35,6 +37,8 @@ function debug($msg, $tracelevel=0, $debuglevel=OCP\Util::DEBUG) {
error_log('trace: '.print_r($call, true));
$call = $call[$tracelevel];
if($debuglevel !== false) {
- OCP\Util::writeLog('contacts', $call['file'].'. Line: '.$call['line'].': '.$msg, $debuglevel);
+ OCP\Util::writeLog('contacts',
+ $call['file'].'. Line: '.$call['line'].': '.$msg,
+ $debuglevel);
}
}
diff --git a/apps/contacts/ajax/oc_photo.php b/apps/contacts/ajax/oc_photo.php
index 710179fffcc..fe37b530f82 100644
--- a/apps/contacts/ajax/oc_photo.php
+++ b/apps/contacts/ajax/oc_photo.php
@@ -22,7 +22,7 @@
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
-require_once('loghandler.php');
+require_once 'loghandler.php';
if(!isset($_GET['id'])) {
bailOut(OC_Contacts_App::$l10n->t('No contact ID was submitted.'));
@@ -50,7 +50,9 @@ if($image->width() > 400 || $image->height() > 400) {
$image->resize(400); // Prettier resizing than with browser and saves bandwidth.
}
if(!$image->fixOrientation()) { // No fatal error so we don't bail out.
- OCP\Util::writeLog('contacts','ajax/oc_photo.php: Couldn\'t save correct image orientation: '.$localpath, OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts',
+ 'ajax/oc_photo.php: Couldn\'t save correct image orientation: '.$localpath,
+ OCP\Util::DEBUG);
}
if(OC_Cache::set($tmpkey, $image->data(), 600)) {
OCP\JSON::success(array('data' => array('id'=>$_GET['id'], 'tmp'=>$tmpkey)));
diff --git a/apps/contacts/ajax/savecrop.php b/apps/contacts/ajax/savecrop.php
index adce6be3b39..8ee2e46bf0c 100644
--- a/apps/contacts/ajax/savecrop.php
+++ b/apps/contacts/ajax/savecrop.php
@@ -27,7 +27,7 @@ OCP\JSON::callCheck();
// Firefox and Konqueror tries to download application/json for me. --Arthur
OCP\JSON::setContentTypeHeader('text/plain');
-require_once('loghandler.php');
+require_once 'loghandler.php';
$image = null;
@@ -48,7 +48,7 @@ if($id == '') {
bailOut('Missing contact id.');
}
-OCP\Util::writeLog('contacts','savecrop.php: key: '.$tmpkey, OCP\Util::DEBUG);
+OCP\Util::writeLog('contacts', 'savecrop.php: key: '.$tmpkey, OCP\Util::DEBUG);
$data = OC_Cache::get($tmpkey);
if($data) {
@@ -56,7 +56,9 @@ if($data) {
if($image->loadFromdata($data)) {
$w = ($w != -1 ? $w : $image->width());
$h = ($h != -1 ? $h : $image->height());
- OCP\Util::writeLog('contacts','savecrop.php, x: '.$x1.' y: '.$y1.' w: '.$w.' h: '.$h, OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts',
+ 'savecrop.php, x: '.$x1.' y: '.$y1.' w: '.$w.' h: '.$h,
+ OCP\Util::DEBUG);
if($image->crop($x1, $y1, $w, $h)) {
if(($image->width() <= 200 && $image->height() <= 200) || $image->resize(200)) {
$card = OC_Contacts_App::getContactVCard($id);
@@ -65,7 +67,9 @@ if($data) {
bailOut(OC_Contacts_App::$l10n->t('Error getting contact object.'));
}
if($card->__isset('PHOTO')) {
- OCP\Util::writeLog('contacts','savecrop.php: PHOTO property exists.', OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts',
+ 'savecrop.php: PHOTO property exists.',
+ OCP\Util::DEBUG);
$property = $card->__get('PHOTO');
if(!$property) {
OC_Cache::remove($tmpkey);
@@ -76,12 +80,16 @@ if($data) {
$property->parameters[] = new Sabre_VObject_Parameter('TYPE', $image->mimeType());
$card->__set('PHOTO', $property);
} else {
- OCP\Util::writeLog('contacts','savecrop.php: files: Adding PHOTO property.', OCP\Util::DEBUG);
- $card->addProperty('PHOTO', $image->__toString(), array('ENCODING' => 'b', 'TYPE' => $image->mimeType()));
+ OCP\Util::writeLog('contacts',
+ 'savecrop.php: files: Adding PHOTO property.',
+ OCP\Util::DEBUG);
+ $card->addProperty('PHOTO',
+ $image->__toString(), array('ENCODING' => 'b',
+ 'TYPE' => $image->mimeType()));
}
$now = new DateTime;
$card->setString('REV', $now->format(DateTime::W3C));
- if(!OC_Contacts_VCard::edit($id,$card)) {
+ if(!OC_Contacts_VCard::edit($id, $card)) {
bailOut(OC_Contacts_App::$l10n->t('Error saving contact.'));
}
$tmpl = new OCP\Template("contacts", "part.contactphoto");
diff --git a/apps/contacts/ajax/saveproperty.php b/apps/contacts/ajax/saveproperty.php
index 34fc3cc5351..5d743c99df4 100644
--- a/apps/contacts/ajax/saveproperty.php
+++ b/apps/contacts/ajax/saveproperty.php
@@ -19,7 +19,7 @@
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
-require_once('loghandler.php');
+require_once 'loghandler.php';
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
@@ -39,9 +39,11 @@ if(!$id) {
if(!$checksum) {
bailOut(OC_Contacts_App::$l10n->t('checksum is not set.'));
}
-if(is_array($value)){
+if(is_array($value)) {
$value = array_map('strip_tags', $value);
- ksort($value); // NOTE: Important, otherwise the compound value will be set in the order the fields appear in the form!
+ // NOTE: Important, otherwise the compound value will be
+ // set in the order the fields appear in the form!
+ ksort($value);
//if($name == 'CATEGORIES') {
// $value = OC_Contacts_VCard::escapeDelimiters($value, ',');
//} else {
@@ -66,8 +68,7 @@ if($element != $name) {
switch($element) {
case 'BDAY':
$date = New DateTime($value);
- //$vcard->setDateTime('BDAY', $date, Sabre_VObject_Element_DateTime::DATE);
- $value = $date->format(DateTime::ATOM);
+ $value = $date->format('Y-m-d');
break;
case 'FN':
if(!$value) {
@@ -89,6 +90,14 @@ if(!$value) {
} else {
/* setting value */
switch($element) {
+ case 'BDAY':
+ // I don't use setDateTime() because that formats it as YYYYMMDD instead of YYYY-MM-DD
+ // which is what the RFC recommends.
+ $vcard->children[$line]->setValue($value);
+ $vcard->children[$line]->parameters = array();
+ $vcard->children[$line]->add(new Sabre_VObject_Parameter('VALUE', 'DATE'));
+ debug('Setting value:'.$name.' '.$vcard->children[$line]);
+ break;
case 'CATEGORIES':
debug('Setting string:'.$name.' '.$value);
$vcard->children[$line]->setValue($value);
@@ -120,7 +129,7 @@ if(!$value) {
}
//debug('New checksum: '.$checksum);
-if(!OC_Contacts_VCard::edit($id,$vcard)) {
+if(!OC_Contacts_VCard::edit($id, $vcard)) {
bailOut(OC_Contacts_App::$l10n->t('Error updating contact property.'));
exit();
}
diff --git a/apps/contacts/ajax/selectaddressbook.php b/apps/contacts/ajax/selectaddressbook.php
new file mode 100644
index 00000000000..6c35d08c829
--- /dev/null
+++ b/apps/contacts/ajax/selectaddressbook.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * Copyright (c) 2011 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('contacts');
+
+$addressbooks = OC_Contacts_Addressbook::all(OCP\USER::getUser());
+$tmpl = new OCP\Template("contacts", "part.selectaddressbook");
+$tmpl->assign('addressbooks', $addressbooks);
+$page = $tmpl->fetchPage();
+OCP\JSON::success(array('data' => array('page' => $page )));
diff --git a/apps/contacts/ajax/updateaddressbook.php b/apps/contacts/ajax/updateaddressbook.php
index d3a772c727f..a14b2158431 100644
--- a/apps/contacts/ajax/updateaddressbook.php
+++ b/apps/contacts/ajax/updateaddressbook.php
@@ -11,7 +11,7 @@
// Check if we are a user
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
-require_once('loghandler.php');
+require_once 'loghandler.php';
$bookid = $_POST['id'];
OC_Contacts_App::getAddressbook($bookid); // is owner access check
diff --git a/apps/contacts/ajax/uploadimport.php b/apps/contacts/ajax/uploadimport.php
index 80b282f38a3..87032b731a5 100644
--- a/apps/contacts/ajax/uploadimport.php
+++ b/apps/contacts/ajax/uploadimport.php
@@ -24,48 +24,56 @@
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('contacts');
OCP\JSON::callCheck();
-require_once('loghandler.php');
+require_once 'loghandler.php';
+
+$l10n = OC_Contacts_App::$l10n;
$view = OCP\Files::getStorage('contacts');
+if(!$view->file_exists('imports')) {
+ $view->mkdir('imports');
+}
$tmpfile = md5(rand());
// If it is a Drag'n'Drop transfer it's handled here.
$fn = (isset($_SERVER['HTTP_X_FILE_NAME']) ? $_SERVER['HTTP_X_FILE_NAME'] : false);
if($fn) {
- if($view->file_put_contents('/'.$tmpfile, file_get_contents('php://input'))) {
- OCP\JSON::success(array('data' => array('path'=>'', 'file'=>$tmpfile)));
+ if($view->file_put_contents('/imports/'.$fn, file_get_contents('php://input'))) {
+ OCP\JSON::success(array('data' => array('file'=>$tmpfile, 'name'=>$fn)));
exit();
} else {
- bailOut(OC_Contacts_App::$l10n->t('Error uploading contacts to storage.'));
+ bailOut($l10n->t('Error uploading contacts to storage.'));
}
}
// File input transfers are handled here
if (!isset($_FILES['importfile'])) {
- OCP\Util::writeLog('contacts','ajax/uploadphoto.php: No file was uploaded. Unknown error.', OCP\Util::DEBUG);
- OCP\JSON::error(array('data' => array( 'message' => 'No file was uploaded. Unknown error' )));
+ OCP\Util::writeLog('contacts',
+ 'ajax/uploadphoto.php: No file was uploaded. Unknown error.',
+ OCP\Util::DEBUG);
+ OCP\JSON::error(array('
+ data' => array(
+ 'message' => 'No file was uploaded. Unknown error' )));
exit();
}
$error = $_FILES['importfile']['error'];
if($error !== UPLOAD_ERR_OK) {
$errors = array(
- 0=>OC_Contacts_App::$l10n->t("There is no error, the file uploaded with success"),
- 1=>OC_Contacts_App::$l10n->t("The uploaded file exceeds the upload_max_filesize directive in php.ini").ini_get('upload_max_filesize'),
- 2=>OC_Contacts_App::$l10n->t("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"),
- 3=>OC_Contacts_App::$l10n->t("The uploaded file was only partially uploaded"),
- 4=>OC_Contacts_App::$l10n->t("No file was uploaded"),
- 6=>OC_Contacts_App::$l10n->t("Missing a temporary folder")
+ 0=>$l10n->t("There is no error, the file uploaded with success"),
+ 1=>$l10n->t("The uploaded file exceeds the upload_max_filesize directive in php.ini").ini_get('upload_max_filesize'),
+ 2=>$l10n->t("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"),
+ 3=>$l10n->t("The uploaded file was only partially uploaded"),
+ 4=>$l10n->t("No file was uploaded"),
+ 6=>$l10n->t("Missing a temporary folder")
);
bailOut($errors[$error]);
}
$file=$_FILES['importfile'];
-$tmpfname = tempnam(get_temp_dir(), "occOrig");
if(file_exists($file['tmp_name'])) {
- if($view->file_put_contents('/'.$tmpfile, file_get_contents($file['tmp_name']))) {
- OCP\JSON::success(array('data' => array('path'=>'', 'file'=>$tmpfile)));
+ if($view->file_put_contents('/imports/'.$file['name'], file_get_contents($file['tmp_name']))) {
+ OCP\JSON::success(array('data' => array('file'=>$file['name'], 'name'=>$file['name'])));
} else {
- bailOut(OC_Contacts_App::$l10n->t('Error uploading contacts to storage.'));
+ bailOut($l10n->t('Error uploading contacts to storage.'));
}
} else {
bailOut('Temporary file: \''.$file['tmp_name'].'\' has gone AWOL?');
diff --git a/apps/contacts/ajax/uploadphoto.php b/apps/contacts/ajax/uploadphoto.php
index 6bb3fe8a5e0..4cd38db8c72 100644
--- a/apps/contacts/ajax/uploadphoto.php
+++ b/apps/contacts/ajax/uploadphoto.php
@@ -27,13 +27,13 @@ OCP\JSON::callCheck();
// Firefox and Konqueror tries to download application/json for me. --Arthur
OCP\JSON::setContentTypeHeader('text/plain');
-require_once('loghandler.php');
-
+require_once 'loghandler.php';
+$l10n = OC_Contacts_App::$l10n;
// If it is a Drag'n'Drop transfer it's handled here.
$fn = (isset($_SERVER['HTTP_X_FILE_NAME']) ? $_SERVER['HTTP_X_FILE_NAME'] : false);
if ($fn) {
if (!isset($_GET['id'])) {
- bailOut(OC_Contacts_App::$l10n->t('No contact ID was submitted.'));
+ bailOut($l10n->t('No contact ID was submitted.'));
}
$id = $_GET['id'];
$tmpkey = 'contact-photo-'.md5($fn);
@@ -48,33 +48,38 @@ if ($fn) {
debug('Couldn\'t save correct image orientation: '.$tmpkey);
}
if(OC_Cache::set($tmpkey, $image->data(), 600)) {
- OCP\JSON::success(array('data' => array('mime'=>$_SERVER['CONTENT_TYPE'], 'name'=>$fn, 'id'=>$id, 'tmp'=>$tmpkey)));
+ OCP\JSON::success(array(
+ 'data' => array(
+ 'mime'=>$_SERVER['CONTENT_TYPE'],
+ 'name'=>$fn,
+ 'id'=>$id,
+ 'tmp'=>$tmpkey)));
exit();
} else {
- bailOut(OC_Contacts_App::$l10n->t('Couldn\'t save temporary image: ').$tmpkey);
+ bailOut($l10n->t('Couldn\'t save temporary image: ').$tmpkey);
}
} else {
- bailOut(OC_Contacts_App::$l10n->t('Couldn\'t load temporary image: ').$tmpkey);
+ bailOut($l10n->t('Couldn\'t load temporary image: ').$tmpkey);
}
}
// Uploads from file dialog are handled here.
if (!isset($_POST['id'])) {
- bailOut(OC_Contacts_App::$l10n->t('No contact ID was submitted.'));
+ bailOut($l10n->t('No contact ID was submitted.'));
}
if (!isset($_FILES['imagefile'])) {
- bailOut(OC_Contacts_App::$l10n->t('No file was uploaded. Unknown error'));
+ bailOut($l10n->t('No file was uploaded. Unknown error'));
}
$error = $_FILES['imagefile']['error'];
if($error !== UPLOAD_ERR_OK) {
$errors = array(
- 0=>OC_Contacts_App::$l10n->t("There is no error, the file uploaded with success"),
- 1=>OC_Contacts_App::$l10n->t("The uploaded file exceeds the upload_max_filesize directive in php.ini").ini_get('upload_max_filesize'),
- 2=>OC_Contacts_App::$l10n->t("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"),
- 3=>OC_Contacts_App::$l10n->t("The uploaded file was only partially uploaded"),
- 4=>OC_Contacts_App::$l10n->t("No file was uploaded"),
- 6=>OC_Contacts_App::$l10n->t("Missing a temporary folder")
+ 0=>$l10n->t("There is no error, the file uploaded with success"),
+ 1=>$l10n->t("The uploaded file exceeds the upload_max_filesize directive in php.ini").ini_get('upload_max_filesize'),
+ 2=>$l10n->t("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"),
+ 3=>$l10n->t("The uploaded file was only partially uploaded"),
+ 4=>$l10n->t("No file was uploaded"),
+ 6=>$l10n->t("Missing a temporary folder")
);
bailOut($errors[$error]);
}
@@ -91,13 +96,20 @@ if(file_exists($file['tmp_name'])) {
debug('Couldn\'t save correct image orientation: '.$tmpkey);
}
if(OC_Cache::set($tmpkey, $image->data(), 600)) {
- OCP\JSON::success(array('data' => array('mime'=>$file['type'],'size'=>$file['size'],'name'=>$file['name'], 'id'=>$_POST['id'], 'tmp'=>$tmpkey)));
+ OCP\JSON::success(array(
+ 'data' => array(
+ 'mime'=>$file['type'],
+ 'size'=>$file['size'],
+ 'name'=>$file['name'],
+ 'id'=>$_POST['id'],
+ 'tmp'=>$tmpkey,
+ )));
exit();
} else {
- bailOut(OC_Contacts_App::$l10n->t('Couldn\'t save temporary image: ').$tmpkey);
+ bailOut($l10n->t('Couldn\'t save temporary image: ').$tmpkey);
}
} else {
- bailOut(OC_Contacts_App::$l10n->t('Couldn\'t load temporary image: ').$file['tmp_name']);
+ bailOut($l10n->t('Couldn\'t load temporary image: ').$file['tmp_name']);
}
} else {
bailOut('Temporary file: \''.$file['tmp_name'].'\' has gone AWOL?');
diff --git a/apps/contacts/appinfo/app.php b/apps/contacts/appinfo/app.php
index daf7bee1c97..74660a2e2b8 100644
--- a/apps/contacts/appinfo/app.php
+++ b/apps/contacts/appinfo/app.php
@@ -20,7 +20,7 @@ OCP\App::addNavigationEntry( array(
'name' => OC_L10N::get('contacts')->t('Contacts') ));
-OCP\App::registerPersonal('contacts','settings');
+OCP\App::registerPersonal('contacts', 'settings');
OCP\Util::addscript('contacts', 'loader');
OC_Search::registerProvider('OC_Search_Provider_Contacts');
OCP\Share::registerBackend('addressbook', 'OC_Contacts_Share');
diff --git a/apps/contacts/appinfo/database.xml b/apps/contacts/appinfo/database.xml
index 9b269d765dc..b814b0f1516 100644
--- a/apps/contacts/appinfo/database.xml
+++ b/apps/contacts/appinfo/database.xml
@@ -44,7 +44,7 @@
<type>text</type>
<default></default>
<notnull>false</notnull>
- <length>100</length>
+ <length>200</length>
</field>
<field>
@@ -118,7 +118,7 @@
<type>text</type>
<default></default>
<notnull>false</notnull>
- <length>100</length>
+ <length>200</length>
</field>
<field>
diff --git a/apps/contacts/appinfo/migrate.php b/apps/contacts/appinfo/migrate.php
index 02026c5979c..2559b4ea456 100644
--- a/apps/contacts/appinfo/migrate.php
+++ b/apps/contacts/appinfo/migrate.php
@@ -2,7 +2,7 @@
class OC_Migration_Provider_Contacts extends OC_Migration_Provider{
// Create the xml for the user supplied
- function export( ){
+ function export( ) {
$options = array(
'table'=>'contacts_addressbooks',
'matchcol'=>'userid',
@@ -21,9 +21,8 @@ class OC_Migration_Provider_Contacts extends OC_Migration_Provider{
$ids2 = $this->content->copyRows( $options );
// If both returned some ids then they worked
- if( is_array( $ids ) && is_array( $ids2 ) )
- {
- return true;
+ if(is_array($ids) && is_array($ids2)) {
+ return true;
} else {
return false;
}
@@ -31,14 +30,14 @@ class OC_Migration_Provider_Contacts extends OC_Migration_Provider{
}
// Import function for contacts
- function import( ){
- switch( $this->appinfo->version ){
+ 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() ){
+ while( $row = $results->fetchRow() ) {
// Import each addressbook
$addressbookquery = OCP\DB::prepare( "INSERT INTO *PREFIX*contacts_addressbooks (`userid`, `displayname`, `uri`, `description`, `ctag`) VALUES (?, ?, ?, ?, ?)" );
$addressbookquery->execute( array( $this->uid, $row['displayname'], $row['uri'], $row['description'], $row['ctag'] ) );
@@ -48,7 +47,7 @@ class OC_Migration_Provider_Contacts extends OC_Migration_Provider{
OC_Contacts_Addressbook::setActive($idmap[$row['id']], true);
}
// Now tags
- foreach($idmap as $oldid => $newid){
+ foreach($idmap as $oldid => $newid) {
$query = $this->content->prepare( "SELECT * FROM contacts_cards WHERE addressbookid LIKE ?" );
$results = $query->execute( array( $oldid ) );
diff --git a/apps/contacts/appinfo/remote.php b/apps/contacts/appinfo/remote.php
index 09c2de17990..fd5604aec6f 100644
--- a/apps/contacts/appinfo/remote.php
+++ b/apps/contacts/appinfo/remote.php
@@ -22,7 +22,7 @@
OCP\App::checkAppEnabled('contacts');
-if(substr($_SERVER["REQUEST_URI"],0,strlen(OC_App::getAppWebPath('contacts').'/carddav.php')) == OC_App::getAppWebPath('contacts').'/carddav.php'){
+if(substr($_SERVER["REQUEST_URI"], 0, strlen(OC_App::getAppWebPath('contacts').'/carddav.php')) == OC_App::getAppWebPath('contacts').'/carddav.php') {
$baseuri = OC_App::getAppWebPath('contacts').'/carddav.php';
}
@@ -45,7 +45,7 @@ $nodes = array(
$server = new Sabre_DAV_Server($nodes);
$server->setBaseUri($baseuri);
// Add plugins
-$server->addPlugin(new Sabre_DAV_Auth_Plugin($authBackend,'ownCloud'));
+$server->addPlugin(new Sabre_DAV_Auth_Plugin($authBackend, 'ownCloud'));
$server->addPlugin(new Sabre_CardDAV_Plugin());
$server->addPlugin(new Sabre_DAVACL_Plugin());
$server->addPlugin(new Sabre_DAV_Browser_Plugin(false)); // Show something in the Browser, but no upload
diff --git a/apps/contacts/appinfo/update.php b/apps/contacts/appinfo/update.php
index 873899f578b..21e736bb446 100644
--- a/apps/contacts/appinfo/update.php
+++ b/apps/contacts/appinfo/update.php
@@ -1,7 +1,7 @@
<?php
$installedVersion=OCP\Config::getAppValue('contacts', 'installed_version');
-if (version_compare($installedVersion, '0.2.90', '<')) {
+if (version_compare($installedVersion, '0.2.3', '<')) {
// First set all address books in-active.
$stmt = OCP\DB::prepare( 'UPDATE *PREFIX*contacts_addressbooks SET active=0' );
$result = $stmt->execute(array());
diff --git a/apps/contacts/appinfo/version b/apps/contacts/appinfo/version
index 7dff5b89211..72f9fa82020 100644
--- a/apps/contacts/appinfo/version
+++ b/apps/contacts/appinfo/version
@@ -1 +1 @@
-0.2.1 \ No newline at end of file
+0.2.4 \ No newline at end of file
diff --git a/apps/contacts/carddav.php b/apps/contacts/carddav.php
index e0579e625d7..264eb30836b 100644
--- a/apps/contacts/carddav.php
+++ b/apps/contacts/carddav.php
@@ -1,6 +1,6 @@
<?php
-if(!file_exists('../../lib/base.php')){
+if(!file_exists('../../lib/base.php')) {
die('Please update the path to /lib/base.php in carddav.php or make use of /remote.php/carddav/');
}
-require_once('../../lib/base.php');
-require_once('appinfo/remote.php'); \ No newline at end of file
+require_once '../../lib/base.php';
+require_once 'appinfo/remote.php'; \ No newline at end of file
diff --git a/apps/contacts/css/contacts.css b/apps/contacts/css/contacts.css
index 439c611b1dc..5a9e9cb9fe3 100644
--- a/apps/contacts/css/contacts.css
+++ b/apps/contacts/css/contacts.css
@@ -5,17 +5,18 @@
#leftcontent a { padding: 0 0 0 25px; }
#rightcontent { top: 3.5em !important; padding-top: 5px; }
#leftcontent h3 { cursor: pointer; -moz-transition: background 300ms ease 0s; background: none no-repeat scroll 1em center #eee; border-bottom: 1px solid #ddd; border-top: 1px solid #fff; display: block; max-width: 100%; padding: 0.5em 0.8em; color: #666; text-shadow: 0 1px 0 #f8f8f8; font-size: 1.2em; }
-#leftcontent h3:hover,#leftcontent h3:active,#leftcontent h3.active { background-color: #DBDBDB; border-bottom: 1px solid #CCCCCC; border-top: 1px solid #D4D4D4; color: #333333; }
+#leftcontent h3:hover,#leftcontent h3:active,#leftcontent h3.active { background-color: #DBDBDB; border-bottom: 1px solid #CCCCCC; border-top: 1px solid #D4D4D4; color: #333333; font-weight: bold; }
#contacts { position: fixed; background: #fff; max-width: 100%; width: 20em; left: 12.5em; top: 3.7em; bottom: 3em; overflow: auto; padding: 0; margin: 0; }
.contacts a { height: 23px; display: block; left: 12.5em; margin: 0 0 0 0; padding: 0 0 0 25px; }
.contacts li.ui-draggable { height: 23px; }
-.ui-draggable-dragging { width: 16em; }
+.ui-draggable-dragging { width: 17em; cursor: move; }
.ui-state-hover { border: 1px solid dashed; }
#bottomcontrols { padding: 0; bottom:0px; height:2.8em; width: 20em; margin:0; background:#eee; border-top:1px solid #ccc; position:fixed; -moz-box-shadow: 0 -3px 3px -3px #000; -webkit-box-shadow: 0 -3px 3px -3px #000; box-shadow: 0 -3px 3px -3px #000;}
#bottomcontrols img { margin-top: 0.35em; }
-#contacts_newcontact { float: left; margin: 0.2em 0 0 1em; }
+#uploadprogressbar { display: none; padding: 0; bottom: 3em; height:2em; width: 20em; margin:0; background:#eee; border:1px solid #ccc; position:fixed; }
+#contacts_newcontact, #contacts_import, #chooseaddressbook { float: left; margin: 0.2em 0 0 1em; border: 0 none; border-radius: 0; -moz-box-shadow: none; box-shadow: none; outline: 0 none; }
#chooseaddressbook { float: right; margin: 0.2em 1em 0 0; }
-#actionbar { position: relative; clear: both; height: 30px;}
+#actionbar { clear: both; height: 30px;}
#contacts_deletecard {position:relative; float:left; background:url('%webroot%/core/img/actions/delete.svg') no-repeat center; }
#contacts_downloadcard {position:relative; float:left; background:url('%webroot%/core/img/actions/download.svg') no-repeat center; }
#contacts_propertymenu { clear: left; float:left; max-width: 15em; margin: 2em; }
@@ -72,7 +73,7 @@ label:hover, dt:hover { color: #333; }
#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;*/ }
+.contactsection { position: relative; float: left; padding: 0.5em; height: auto; }
#cropbox { margin: auto; }
#contacts_details_photo_wrapper { width: 200px; }
@@ -111,7 +112,7 @@ dl.addresscard .action { float: right; }
#file_upload_form { width: 0; height: 0; }
#file_upload_target, #import_upload_target, #crop_target { display:none; }
#file_upload_start, #import_upload_start { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0; z-index:1001; width:0; height:0;}
-#import_upload_start { width: 16px; height: 16px; margin: 0 0 0 0; }
+#import_upload_start { width: 20px; height: 20px; margin: 0 0 -24px 0; padding: 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; }
@@ -125,3 +126,12 @@ input[type="checkbox"] { width: 20px; height: 20px; vertical-align: bottom; }
.typelist[type="button"] { float: left; max-width: 10em; border: 0; background-color: #fff; color: #bbb} /* for multiselect */
.typelist[type="button"]:hover { color: #777; } /* for multiselect */
.addresslist { clear: both; font-weight: bold; }
+#ninjahelp { position: absolute; bottom: 0; left: 0; right: 0; padding: 1em; margin: 1em; border: thin solid #eee; border-radius: 5px; background-color: #DBDBDB; opacity: 0.9; }
+#ninjahelp .close { position: absolute; top: 5px; right: 5px; height: 20px; width: 20px; }
+#ninjahelp h2, .help-section h3 { width: 100%; font-weight: bold; text-align: center; }
+#ninjahelp h2 { font-size: 1.4em; }
+.help-section { width: 45%; min-width: 35em; float: left; }
+.help-section h3 { font-size: 1.2em; }
+.help-section dl { width: 100%; float: left; clear: right; margin: 0; padding: 0; cursor: normal; }
+.help-section dt { display: table-cell; clear: left; float: left; width: 35%; margin: 0; padding: 0.2em; text-align: right; text-overflow: ellipsis; vertical-align: text-bottom; font-weight: bold: }
+.help-section dd { display: table-cell; clear: right; float: left; margin: 0; padding: 0.2em; white-space: nowrap; vertical-align: text-bottom; }
diff --git a/apps/contacts/export.php b/apps/contacts/export.php
index eb506506c42..161ca8047ac 100644
--- a/apps/contacts/export.php
+++ b/apps/contacts/export.php
@@ -9,26 +9,30 @@
OCP\User::checkLoggedIn();
OCP\App::checkAppEnabled('contacts');
-$bookid = isset($_GET['bookid']) ? $_GET['bookid'] : NULL;
-$contactid = isset($_GET['contactid']) ? $_GET['contactid'] : NULL;
+$bookid = isset($_GET['bookid']) ? $_GET['bookid'] : null;
+$contactid = isset($_GET['contactid']) ? $_GET['contactid'] : null;
$nl = "\n";
-if(isset($bookid)){
+if(isset($bookid)) {
$addressbook = OC_Contacts_App::getAddressbook($bookid);
//$cardobjects = OC_Contacts_VCard::all($bookid);
header('Content-Type: text/directory');
- header('Content-Disposition: inline; filename=' . str_replace(' ', '_', $addressbook['displayname']) . '.vcf');
+ header('Content-Disposition: inline; filename='
+ . str_replace(' ', '_', $addressbook['displayname']) . '.vcf');
$start = 0;
- $batchsize = OCP\Config::getUserValue(OCP\User::getUser(), 'contacts', 'export_batch_size', 20);
+ $batchsize = OCP\Config::getUserValue(OCP\User::getUser(),
+ 'contacts',
+ 'export_batch_size', 20);
while($cardobjects = OC_Contacts_VCard::all($bookid, $start, $batchsize)){
foreach($cardobjects as $card) {
echo $card['carddata'] . $nl;
}
$start += $batchsize;
}
-}elseif(isset($contactid)){
+}elseif(isset($contactid)) {
$data = OC_Contacts_App::getContactObject($contactid);
header('Content-Type: text/vcard');
- header('Content-Disposition: inline; filename=' . str_replace(' ', '_', $data['fullname']) . '.vcf');
+ header('Content-Disposition: inline; filename='
+ . str_replace(' ', '_', $data['fullname']) . '.vcf');
echo $data['carddata'];
}
diff --git a/apps/contacts/import.php b/apps/contacts/import.php
index 93c47ef2667..1986d79f6d6 100644
--- a/apps/contacts/import.php
+++ b/apps/contacts/import.php
@@ -12,7 +12,6 @@ OCP\JSON::checkLoggedIn();
OCP\App::checkAppEnabled('contacts');
session_write_close();
-$cr = "\r";
$nl = "\n";
global $progresskey;
@@ -31,7 +30,7 @@ writeProgress('10');
$view = $file = null;
if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') {
$view = OCP\Files::getStorage('contacts');
- $file = $view->file_get_contents('/' . $_POST['file']);
+ $file = $view->file_get_contents('/imports/' . $_POST['file']);
} else {
$file = OC_Filesystem::file_get_contents($_POST['path'] . '/' . $_POST['file']);
}
@@ -39,33 +38,43 @@ if(!$file) {
OCP\JSON::error(array('data' => array('message' => 'Import file was empty.')));
exit();
}
-if(isset($_POST['method']) && $_POST['method'] == 'new'){
- $id = OC_Contacts_Addressbook::add(OCP\USER::getUser(), $_POST['addressbookname']);
+if(isset($_POST['method']) && $_POST['method'] == 'new') {
+ $id = OC_Contacts_Addressbook::add(OCP\USER::getUser(),
+ $_POST['addressbookname']);
if(!$id) {
- OCP\JSON::error(array('data' => array('message' => 'Error creating address book.')));
+ OCP\JSON::error(
+ array(
+ 'data' => array('message' => 'Error creating address book.')
+ )
+ );
exit();
}
OC_Contacts_Addressbook::setActive($id, 1);
}else{
$id = $_POST['id'];
if(!$id) {
- OCP\JSON::error(array('data' => array('message' => 'Error getting the ID of the address book.')));
+ OCP\JSON::error(
+ array(
+ 'data' => array(
+ 'message' => 'Error getting the ID of the address book.',
+ 'file'=>$_POST['file']
+ )
+ )
+ );
exit();
}
OC_Contacts_App::getAddressbook($id); // is owner access check
}
//analyse the contacts file
writeProgress('40');
+$file = str_replace(array("\r","\n\n"), array("\n","\n"), $file);
$lines = explode($nl, $file);
-if(count($lines) == 1) { // Mac eol
- $lines = explode($cr, $file);
-}
$inelement = false;
$parts = array();
$card = array();
foreach($lines as $line){
- if(strtoupper(trim($line)) == 'BEGIN:VCARD'){
+ if(strtoupper(trim($line)) == 'BEGIN:VCARD') {
$inelement = true;
} elseif (strtoupper(trim($line)) == 'END:VCARD') {
$card[] = $line;
@@ -82,21 +91,40 @@ writeProgress('70');
$imported = 0;
$failed = 0;
if(!count($parts) > 0) {
- OCP\JSON::error(array('data' => array('message' => 'No contacts to import in .'.$_POST['file'].' Please check if the file is corrupted.')));
+ OCP\JSON::error(
+ array(
+ 'data' => array(
+ 'message' => 'No contacts to import in '
+ . $_POST['file'].'. Please check if the file is corrupted.',
+ 'file'=>$_POST['file']
+ )
+ )
+ );
+ if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') {
+ if(!$view->unlink('/imports/' . $_POST['file'])) {
+ OCP\Util::writeLog('contacts',
+ 'Import: Error unlinking OC_FilesystemView ' . '/' . $_POST['file'],
+ OCP\Util::ERROR);
+ }
+ }
exit();
}
foreach($parts as $part){
$card = OC_VObject::parse($part);
if (!$card) {
$failed += 1;
- OCP\Util::writeLog('contacts','Import: skipping card. Error parsing VCard: '.$part, OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts',
+ 'Import: skipping card. Error parsing VCard: ' . $part,
+ OCP\Util::ERROR);
continue; // Ditch cards that can't be parsed by Sabre.
}
try {
OC_Contacts_VCard::add($id, $card);
$imported += 1;
} catch (Exception $e) {
- OCP\Util::writeLog('contacts', 'Error importing vcard: '.$e->getMessage().$nl.$card, OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts',
+ 'Error importing vcard: ' . $e->getMessage() . $nl . $card,
+ OCP\Util::ERROR);
$failed += 1;
}
}
@@ -105,8 +133,18 @@ writeProgress('100');
sleep(3);
OC_Cache::remove($progresskey);
if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') {
- if(!$view->unlink('/' . $_POST['file'])) {
- OCP\Util::writeLog('contacts','Import: Error unlinking OC_FilesystemView ' . '/' . $_POST['file'], OCP\Util::ERROR);
+ if(!$view->unlink('/imports/' . $_POST['file'])) {
+ OCP\Util::writeLog('contacts',
+ 'Import: Error unlinking OC_FilesystemView ' . '/' . $_POST['file'],
+ OCP\Util::ERROR);
}
}
-OCP\JSON::success(array('data' => array('imported'=>$imported, 'failed'=>$failed)));
+OCP\JSON::success(
+ array(
+ 'data' => array(
+ 'imported'=>$imported,
+ 'failed'=>$failed,
+ 'file'=>$_POST['file'],
+ )
+ )
+);
diff --git a/apps/contacts/index.php b/apps/contacts/index.php
index fbc3565c4da..c35e1b85d4e 100644
--- a/apps/contacts/index.php
+++ b/apps/contacts/index.php
@@ -14,13 +14,17 @@ OCP\App::checkAppEnabled('contacts');
// Get active address books. This creates a default one if none exists.
$ids = OC_Contacts_Addressbook::activeIds(OCP\USER::getUser());
-$has_contacts = (count(OC_Contacts_VCard::all($ids, 0, 1)) > 0 ? true : false); // just to check if there are any contacts.
+$has_contacts = (count(OC_Contacts_VCard::all($ids, 0, 1)) > 0
+ ? true
+ : false); // just to check if there are any contacts.
if($has_contacts === false) {
- OCP\Util::writeLog('contacts','index.html: No contacts found.',OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts',
+ 'index.html: No contacts found.',
+ OCP\Util::DEBUG);
}
// Load the files we need
-OCP\App::setActiveNavigationEntry( 'contacts_index' );
+OCP\App::setActiveNavigationEntry('contacts_index');
// Load a specific user?
$id = isset( $_GET['id'] ) ? $_GET['id'] : null;
@@ -34,29 +38,31 @@ $post_max_size = OCP\Util::computerFileSize(ini_get('post_max_size'));
$maxUploadFilesize = min($upload_max_filesize, $post_max_size);
$freeSpace=OC_Filesystem::free_space('/');
-$freeSpace=max($freeSpace,0);
-$maxUploadFilesize = min($maxUploadFilesize ,$freeSpace);
-
-OCP\Util::addscript('','jquery.multiselect');
-OCP\Util::addscript('','oc-vcategories');
-OCP\Util::addscript('contacts','contacts');
-OCP\Util::addscript('contacts','expanding');
-OCP\Util::addscript('contacts','jquery.combobox');
-OCP\Util::addscript('contacts','jquery.inview');
-OCP\Util::addscript('contacts','jquery.Jcrop');
-OCP\Util::addscript('contacts','jquery.multi-autocomplete');
-OCP\Util::addStyle('','jquery.multiselect');
-OCP\Util::addStyle('contacts','jquery.combobox');
-OCP\Util::addStyle('contacts','jquery.Jcrop');
-OCP\Util::addStyle('contacts','contacts');
+$freeSpace=max($freeSpace, 0);
+$maxUploadFilesize = min($maxUploadFilesize, $freeSpace);
+
+OCP\Util::addscript('', 'jquery.multiselect');
+OCP\Util::addscript('', 'oc-vcategories');
+OCP\Util::addscript('contacts', 'contacts');
+OCP\Util::addscript('contacts', 'expanding');
+OCP\Util::addscript('contacts', 'jquery.combobox');
+OCP\Util::addscript('files', 'jquery.fileupload');
+OCP\Util::addscript('contacts', 'jquery.inview');
+OCP\Util::addscript('contacts', 'jquery.Jcrop');
+OCP\Util::addscript('contacts', 'jquery.multi-autocomplete');
+OCP\Util::addStyle('', 'jquery.multiselect');
+OCP\Util::addStyle('contacts', 'jquery.combobox');
+OCP\Util::addStyle('contacts', 'jquery.Jcrop');
+OCP\Util::addStyle('contacts', 'contacts');
$tmpl = new OCP\Template( "contacts", "index", "user" );
$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize, false);
-$tmpl->assign('uploadMaxHumanFilesize', OCP\Util::humanFileSize($maxUploadFilesize), false);
+$tmpl->assign('uploadMaxHumanFilesize',
+ OCP\Util::humanFileSize($maxUploadFilesize), false);
$tmpl->assign('property_types', $property_types, false);
$tmpl->assign('phone_types', $phone_types, false);
$tmpl->assign('email_types', $email_types, false);
$tmpl->assign('categories', $categories, false);
$tmpl->assign('has_contacts', $has_contacts, false);
-$tmpl->assign('id',$id, false);
+$tmpl->assign('id', $id, false);
$tmpl->printPage();
diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js
index dd194db0161..4c6c8bf3d93 100644
--- a/apps/contacts/js/contacts.js
+++ b/apps/contacts/js/contacts.js
@@ -12,13 +12,42 @@ String.prototype.strip_tags = function(){
Contacts={
UI:{
- notification:function(msg, ndata) {
- $('#notification').text(msg);
- if(data) {
- $('#notification').data(ndata[0],ndata[1]);
+ /**
+ * Arguments:
+ * message: The text message to show. The only mandatory parameter.
+ * timeout: The timeout in seconds before the notification disappears. Default 10.
+ * timeouthandler: A function to run on timeout.
+ * clickhandler: A function to run on click. If a timeouthandler is given it will be cancelled.
+ * data: An object that will be passed as argument to the timeouthandler and clickhandler functions.
+ */
+ notify:function(params) {
+ self = this;
+ if(!self.notifier) {
+ self.notifier = $('#notification');
+ }
+ self.notifier.text(params.message);
+ self.notifier.fadeIn();
+ self.notifier.on('click', function() { $(this).fadeOut();});
+ var timer = setTimeout(function() {
+ self.notifier.fadeOut();
+ if(params.timeouthandler && $.isFunction(params.timeouthandler)) {
+ params.timeouthandler(self.notifier.data(dataid));
+ self.notifier.off('click');
+ self.notifier.removeData(dataid);
+ }
+ }, params.timeout && $.isNumeric(params.timeout) ? parseInt(params.timeout)*1000 : 10000);
+ var dataid = timer.toString();
+ if(params.data) {
+ self.notifier.data(dataid, params.data);
+ }
+ if(params.clickhandler && $.isFunction(params.clickhandler)) {
+ self.notifier.on('click', function() {
+ clearTimeout(timer);
+ self.notifier.off('click');
+ params.clickhandler(self.notifier.data(dataid));
+ self.notifier.removeData(dataid);
+ });
}
- $('#notification').fadeIn();
- setTimeout($('#notification').fadeOut(), 10000);
},
notImplemented:function() {
OC.dialogs.alert(t('contacts', 'Sorry, this functionality has not been implemented yet'), t('contacts', 'Not implemented'));
@@ -99,7 +128,7 @@ Contacts={
$('.addresscard,.propertylist li,.propertycontainer').hover(
function () {
$(this).find('.globe,.mail,.delete,.edit').animate({ opacity: 1.0 }, 200, function() {});
- },
+ },
function () {
$(this).find('.globe,.mail,.delete,.edit').animate({ opacity: 0.1 }, 200, function() {});
}
@@ -110,7 +139,7 @@ Contacts={
obj.tipsy('hide');
Contacts.UI.Card.deleteProperty(obj, 'single');
}
-
+
var goToUrl = function(obj) {
var url = Contacts.UI.propertyContainerFor(obj).find('#url').val();
if(url != '') {
@@ -118,7 +147,7 @@ Contacts={
newWindow.focus();
}
}
-
+
$('#identityprops a.delete').click( function() { deleteItem($(this)) });
$('#identityprops a.delete').keydown( function() { deleteItem($(this)) });
$('#categories_value a.edit').click( function() { $(this).tipsy('hide');OCCategories.edit(); } );
@@ -143,7 +172,7 @@ Contacts={
});
$('#edit_name').click(function(){Contacts.UI.Card.editName()});
$('#edit_name').keydown(function(){Contacts.UI.Card.editName()});
-
+
$('#phototools li a').click(function() {
$(this).tipsy('hide');
});
@@ -170,13 +199,13 @@ Contacts={
OC.dialogs.filepicker(t('contacts', 'Select photo'), Contacts.UI.Card.cloudPhotoSelected, false, 'image', true);
});
/* Initialize the photo edit dialog */
- $('#edit_photo_dialog').dialog({
+ $('#edit_photo_dialog').dialog({
autoOpen: false, modal: true, height: 'auto', width: 'auto'
});
$('#edit_photo_dialog' ).dialog( 'option', 'buttons', [
{
text: "Ok",
- click: function() {
+ click: function() {
Contacts.UI.Card.savePhoto(this);
$(this).dialog('close');
}
@@ -186,47 +215,28 @@ Contacts={
click: function() { $(this).dialog('close'); }
}
] );
-
- /*$('#fn').blur(function(){
- if($('#fn').val() == '') {
- OC.dialogs.alert(t('contacts','The name field cannot be empty. Please enter a name for this contact.'), t('contacts','Name is empty'), function() { $('#fn').focus(); });
- $('#fn').focus();
- return false;
- }
- });*/
-
+
// Name has changed. Update it and reorder.
- // TODO: Take addressbook into account
$('#fn').change(function(){
var name = $('#fn').val().strip_tags();
- var item = $('.contacts li[data-id="'+Contacts.UI.Card.id+'"]');
+ var item = $('.contacts li[data-id="'+Contacts.UI.Card.id+'"]').detach();
$(item).find('a').html(name);
Contacts.UI.Card.fn = name;
- var added = false;
- $('.contacts li[data-bookid="'+Contacts.UI.Card.bookid+'"]').each(function(){
- if ($(this).text().toLowerCase() > name.toLowerCase()) {
- $(this).before(item).fadeIn('fast');
- added = true;
- return false;
- }
- });
- if(!added) {
- $('#contacts ul[data-id="'+Contacts.UI.Card.bookid+'"]').append(item);
- }
+ Contacts.UI.Contacts.insertContact({contact:item});
Contacts.UI.Contacts.scrollTo(Contacts.UI.Card.id);
});
- $('#contacts_deletecard').click( function() { Contacts.UI.Card.doDelete();return false;} );
- $('#contacts_deletecard').keydown( function(event) {
- if(event.which == 13) {
- Contacts.UI.Card.doDelete();
+ $('#contacts_deletecard').click( function() { Contacts.UI.Card.delayedDelete();return false;} );
+ $('#contacts_deletecard').keydown( function(event) {
+ if(event.which == 13 || event.which == 32) {
+ Contacts.UI.Card.delayedDelete();
}
return false;
});
$('#contacts_downloadcard').click( function() { Contacts.UI.Card.doExport();return false;} );
- $('#contacts_downloadcard').keydown( function(event) {
- if(event.which == 13) {
+ $('#contacts_downloadcard').keydown( function(event) {
+ if(event.which == 13 || event.which == 32) {
Contacts.UI.Card.doExport();
}
return false;
@@ -240,12 +250,12 @@ Contacts={
$('#contacts_details_photo_wrapper').bind('dragover',function(event){
$(event.target).addClass('droppable');
event.stopPropagation();
- event.preventDefault();
+ event.preventDefault();
});
$('#contacts_details_photo_wrapper').bind('dragleave',function(event){
$(event.target).removeClass('droppable');
//event.stopPropagation();
- //event.preventDefault();
+ //event.preventDefault();
});
$('#contacts_details_photo_wrapper').bind('drop',function(event){
event.stopPropagation();
@@ -258,7 +268,7 @@ Contacts={
$('#contacts_deletecard').tipsy({gravity: 'ne'});
$('#contacts_downloadcard').tipsy({gravity: 'ne'});
$('#contacts_propertymenu_button').tipsy();
- $('#contacts_newcontact, #chooseaddressbook').tipsy({gravity: 'sw'});
+ $('#contacts_newcontact, #contacts_import, #chooseaddressbook').tipsy({gravity: 'sw'});
$('body').click(function(e){
if(!$(e.target).is('#contacts_propertymenu_button')) {
@@ -285,46 +295,61 @@ Contacts={
$('#contacts_propertymenu_dropdown a').keydown(propertyMenuItem);
},
Card:{
- id:'',
- fn:'',
- fullname:'',
- shortname:'',
- famname:'',
- givname:'',
- addname:'',
- honpre:'',
- honsuf:'',
- data:undefined,
- update:function(id, bookid) {
- var newid, firstitem;
- if(!id) {
+ update:function(params) { // params {cid:int, aid:int}
+ if(!params) { params = {}; }
+ $('#contacts li,#contacts h3').removeClass('active');
+ console.log('Card, cid: ' + params.cid + ' aid: ' + params.aid);
+ var newid, bookid, firstitem;
+ if(!parseInt(params.cid) && !parseInt(params.aid)) {
firstitem = $('#contacts ul').first().find('li:first-child');
if(firstitem.length > 0) {
- newid = firstitem.data('id');
- bookid = firstitem.data('bookid');
+ newid = parseInt(firstitem.data('id'));
+ bookid = parseInt(firstitem.data('bookid'));
+ }
+ } else if(!parseInt(params.cid) && parseInt(params.aid)) {
+ bookid = parseInt(params.aid);
+ newid = parseInt($('#contacts').find('li[data-bookid="'+bookid+'"]').first().data('id'));
+ } else if(parseInt(params.cid) && !parseInt(params.aid)) {
+ newid = parseInt(params.cid);
+ var listitem = Contacts.UI.Contacts.getContact(newid); //$('#contacts li[data-id="'+newid+'"]');
+ console.log('Is contact in list? ' + listitem.length);
+ if(listitem.length) {
+ //bookid = parseInt($('#contacts li[data-id="'+newid+'"]').data('bookid'));
+ bookid = parseInt(Contacts.UI.Contacts.getContact(newid).data('bookid'));
+ } else { // contact isn't in list yet.
+ bookid = 'unknown';
}
} else {
- newid = id;
- bookid = bookid?bookid:$('#contacts li[data-id="'+newid+'"]').data('bookid');
+ newid = parseInt(params.cid);
+ bookid = parseInt(params.aid);
}
- if(!bookid) {
- bookid = $('#contacts h3').first().data('id');
+ if(!bookid || !newid) {
+ bookid = parseInt($('#contacts h3').first().data('id'));
+ newid = parseInt($('#contacts').find('li[data-bookid="'+bookid+'"]').first().data('id'));
}
- console.log('bookid: ' +bookid);
+ console.log('newid: ' + newid + ' bookid: ' +bookid);
var localLoadContact = function(newid, bookid) {
if($('.contacts li').length > 0) {
- $('#contacts li[data-id="'+newid+'"]').addClass('active');
$.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':newid},function(jsondata){
if(jsondata.status == 'success'){
+ if(bookid == 'unknown') {
+ bookid = jsondata.data.addressbookid;
+ var contact = Contacts.UI.Contacts.insertContact({
+ contactlist:$('#contacts ul[data-id="'+bookid+'"]'),
+ data:jsondata.data
+ });
+ }
+ $('#contacts li[data-id="'+newid+'"],#contacts h3[data-id="'+bookid+'"]').addClass('active');
$('#contacts ul[data-id="'+bookid+'"]').slideDown(300);
Contacts.UI.Card.loadContact(jsondata.data, bookid);
+ Contacts.UI.Contacts.scrollTo(newid);
} else {
OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
}
});
}
}
-
+
// Make sure proper DOM is loaded.
if(!$('#card').length && newid) {
console.log('Loading card DOM');
@@ -359,18 +384,12 @@ Contacts={
doExport:function() {
document.location.href = OC.linkTo('contacts', 'export.php') + '?contactid=' + this.id;
},
- doImport:function(){
- Contacts.UI.notImplemented();
- },
editNew:function(){ // add a new contact
this.id = ''; this.fn = ''; this.fullname = ''; this.givname = ''; this.famname = ''; this.addname = ''; this.honpre = ''; this.honsuf = '';
//Contacts.UI.Card.add(t('contacts', 'Contact')+';'+t('contacts', 'New')+';;;', t('contacts', 'New Contact'), '', true);
Contacts.UI.Card.add(';;;;;', '', '', true);
return false;
},
- createEntry:function(data) {
- return $('<li data-id="'+data.id+'" data-bookid="'+data.addressbookid+'" role="button"><a href="'+OC.linkTo('contacts', 'index.php')+'&id='+data.id+'" style="background: url('+OC.filePath('contacts', '', 'thumbnail.php')+'?id='+data.id+') no-repeat scroll 0% 0% transparent;">'+data.displayname+'</a></li>');
- },
add:function(n, fn, aid, isnew){ // add a new contact
console.log('Adding ' + fn);
aid = aid?aid:$('#contacts h3.active').first().data('id');
@@ -384,19 +403,7 @@ Contacts={
$.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':id},function(jsondata){
if(jsondata.status == 'success'){
Contacts.UI.Card.loadContact(jsondata.data, aid);
- $('#contacts .active').removeClass('active');
- var item = $('<li data-id="'+jsondata.data.id+'" class="active"><a href="index.php?id='+jsondata.data.id+'" style="background: url('+OC.filePath('contacts', '', 'thumbnail.php')+'?id='+jsondata.data.id+') no-repeat scroll 0% 0% transparent;">'+Contacts.UI.Card.fn+'</a></li>');
- var added = false;
- $('#contacts ul[data-id="'+aid+'"] li').each(function(){
- if ($(this).text().toLowerCase() > Contacts.UI.Card.fn.toLowerCase()) {
- $(this).before(item).fadeIn('fast');
- added = true;
- return false;
- }
- });
- if(!added) {
- $('#contacts ul[data-id="'+aid+'"]').append(item);
- }
+ var item = Contacts.UI.Contacts.insertContact({data:jsondata.data});
if(isnew) { // add some default properties
Contacts.UI.Card.addProperty('EMAIL');
Contacts.UI.Card.addProperty('TEL');
@@ -415,7 +422,7 @@ Contacts={
}
});
}
-
+
if(!$('#card').length) {
console.log('Loading card DOM');
$.getJSON(OC.filePath('contacts', 'ajax', 'loadcard.php'),{'requesttoken': requesttoken},function(jsondata){
@@ -432,47 +439,63 @@ Contacts={
localAddcontact(n, fn, aid, isnew);
}
},
- doDelete:function() {
+ delayedDelete:function() {
+ /* TODO:
+ $(window).unload(function() {
+ deleteFilesInQueue();
+ });
+ */
$('#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) {
- $.post(OC.filePath('contacts', 'ajax', 'deletecard.php'),{'id':Contacts.UI.Card.id},function(jsondata){
- if(jsondata.status == 'success'){
- var newid = '', bookid;
- var curlistitem = $('#contacts li[data-id="'+jsondata.data.id+'"]');
- var newlistitem = curlistitem.prev('li');
- if(newlistitem == undefined) {
- newlistitem = curlistitem.next('li');
- }
- curlistitem.remove();
- if(!$(newlistitem).is('li')) {
- newid = newlistitem.data('id');
- bookid = newlistitem.data('id');
- }
- $('#rightcontent').data('id',newid);
- this.id = this.fn = this.fullname = this.shortname = this.famname = this.givname = this.addname = this.honpre = this.honsuf = '';
- this.data = undefined;
-
- if($('.contacts li').length > 0) { // Load first in list.
- Contacts.UI.Card.update(newid, bookid);
- } else {
- // load intro page
- $.getJSON(OC.filePath('contacts', 'ajax', 'loadintro.php'),{},function(jsondata){
- if(jsondata.status == 'success'){
- id = '';
- $('#rightcontent').data('id','');
- $('#rightcontent').html(jsondata.data.page);
- }
- else{
- OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
- }
- });
- }
- }
- else{
- OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
- }
- });
+ var newid = '', bookid;
+ var curlistitem = Contacts.UI.Contacts.getContact(this.id);
+ curlistitem.removeClass('active');
+ var newlistitem = curlistitem.prev('li');
+ if(!newlistitem) {
+ newlistitem = curlistitem.next('li');
+ }
+ curlistitem.detach();
+ if($(newlistitem).is('li')) {
+ newid = newlistitem.data('id');
+ bookid = newlistitem.data('bookid');
+ }
+ $('#rightcontent').data('id', newid);
+
+ with(this) {
+ delete id; delete fn; delete fullname; delete shortname; delete famname;
+ delete givname; delete addname; delete honpre; delete honsuf; delete data;
+ }
+
+ if($('.contacts li').length > 0) {
+ Contacts.UI.Card.update({cid:newid, aid:bookid});
+ } else {
+ // load intro page
+ $.getJSON(OC.filePath('contacts', 'ajax', 'loadintro.php'),{},function(jsondata){
+ if(jsondata.status == 'success'){
+ id = '';
+ $('#rightcontent').html(jsondata.data.page).removeData('id');
+ }
+ else{
+ OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
+ }
+ });
+ }
+ Contacts.UI.notify({
+ data:curlistitem,
+ message:t('contacts','Click to undo deletion of "') + curlistitem.find('a').text() + '"',
+ timeouthandler:function(contact) {
+ Contacts.UI.Card.doDelete(contact.data('id'));
+ delete contact;
+ },
+ clickhandler:function(contact) {
+ Contacts.UI.Contacts.insertContact({contact:contact});
+ Contacts.UI.notify({message:t('contacts', 'Cancelled deletion of: "') + curlistitem.find('a').text() + '"'});
+ }
+ });
+ },
+ doDelete:function(id) {
+ $.post(OC.filePath('contacts', 'ajax', 'deletecard.php'),{'id':id},function(jsondata) {
+ if(jsondata.status == 'error'){
+ OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
}
});
return false;
@@ -489,21 +512,22 @@ Contacts={
this.loadAddresses();
this.loadSingleProperties();
Contacts.UI.loadListHandlers();
+ var note = $('#note');
if(this.data.NOTE) {
- $('#note').data('checksum', this.data.NOTE[0]['checksum']);
- var note = $('#note').find('textarea');
+ note.data('checksum', this.data.NOTE[0]['checksum']);
+ var textarea = note.find('textarea');
var txt = this.data.NOTE[0]['value'];
var nheight = txt.split('\n').length > 4 ? txt.split('\n').length+2 : 5;
- note.css('min-height', nheight+'em');
- note.attr('rows', nheight);
- note.val(txt);
- $('#note').show();
- note.expandingTextarea();
+ textarea.css('min-height', nheight+'em');
+ textarea.attr('rows', nheight);
+ textarea.val(txt);
+ note.show();
+ textarea.expandingTextarea();
$('#contacts_propertymenu_dropdown a[data-type="NOTE"]').parent().hide();
} else {
- $('#note').data('checksum', '');
- $('#note').find('textarea').val('');
- $('#note').hide();
+ note.removeData('checksum');
+ note.find('textarea').val('');
+ note.hide();
$('#contacts_propertymenu_dropdown a[data-type="NOTE"]').parent().show();
}
},
@@ -524,15 +548,16 @@ Contacts={
$('#contacts_propertymenu_dropdown a[data-type="'+propname+'"]').parent().hide();
var property = this.data[propname][0];
var value = property['value'], checksum = property['checksum'];
-
+
if(propname == 'BDAY') {
var val = $.datepicker.parseDate('yy-mm-dd', value.substring(0, 10));
value = $.datepicker.formatDate('dd-mm-yy', val);
}
- $('#contact_identity').find('#'+propname.toLowerCase()).val(value);
- $('#contact_identity').find('#'+propname.toLowerCase()+'_value').data('checksum', checksum);
- $('#contact_identity').find('#'+propname.toLowerCase()+'_label').show();
- $('#contact_identity').find('#'+propname.toLowerCase()+'_value').show();
+ var identcontainer = $('#contact_identity');
+ identcontainer.find('#'+propname.toLowerCase()).val(value);
+ identcontainer.find('#'+propname.toLowerCase()+'_value').data('checksum', checksum);
+ identcontainer.find('#'+propname.toLowerCase()+'_label').show();
+ identcontainer.find('#'+propname.toLowerCase()+'_value').show();
} else {
$('#contacts_propertymenu_dropdown a[data-type="'+propname+'"]').parent().show();
}
@@ -547,8 +572,12 @@ Contacts={
$(this).find('input').val('');
}
});
- this.fn = ''; this.fullname = ''; this.givname = ''; this.famname = ''; this.addname = ''; this.honpre = ''; this.honsuf = '';
- var narray = undefined;
+
+ with(this) {
+ delete fn; delete fullname; delete givname; delete famname;
+ delete addname; delete honpre; delete honsuf;
+ }
+
if(this.data.FN) {
this.fn = this.data.FN[0]['value'];
}
@@ -589,7 +618,7 @@ Contacts={
$.each(names, function(key, value) {
$('#fn_select')
.append($('<option></option>')
- .text(value));
+ .text(value));
});
$('#fn_select').combobox('value', this.fn);
$('#contact_identity').find('*[data-element="N"]').data('checksum', this.data.N[0]['checksum']);
@@ -732,17 +761,9 @@ Contacts={
},
addProperty:function(type){
switch (type) {
- case 'PHOTO':
- this.loadPhoto(true);
- $('#file_upload_form').show();
- $('#contacts_propertymenu_dropdown a[data-type="'+type+'"]').parent().hide();
- $('#file_upload_start').trigger('click');
- break;
case 'NOTE':
- $('#note').show();
$('#contacts_propertymenu_dropdown a[data-type="'+type+'"]').parent().hide();
- $('#note').find('textarea').expandingTextarea();
- $('#note').find('textarea').focus();
+ $('#note').find('textarea').expandingTextarea().show().focus();
break;
case 'EMAIL':
if($('#emaillist>li').length == 1) {
@@ -774,6 +795,7 @@ Contacts={
}
},
deleteProperty:function(obj, type){
+ console.log('deleteProperty');
Contacts.UI.loading(obj, true);
var checksum = Contacts.UI.checksumFor(obj);
if(checksum) {
@@ -796,8 +818,7 @@ Contacts={
}
} else {
$('dl dt[data-element="'+proptype+'"],dd[data-element="'+proptype+'"]').hide();
- $('dl dd[data-element="'+proptype+'"]').data('checksum', '');
- $('dl dd[data-element="'+proptype+'"]').find('input').val('');
+ $('dl dd[data-element="'+proptype+'"]').data('checksum', '').find('input').val('');
}
$('#contacts_propertymenu_dropdown a[data-type="'+proptype+'"]').parent().show();
Contacts.UI.loading(obj, false);
@@ -824,14 +845,14 @@ Contacts={
}
}
},
- editName:function(){
+ editName:function() {
var params = {id: this.id};
/* Initialize the name edit dialog */
- if($('#edit_name_dialog').dialog('isOpen') == true){
+ if($('#edit_name_dialog').dialog('isOpen') == true) {
$('#edit_name_dialog').dialog('moveToTop');
- }else{
- $.getJSON(OC.filePath('contacts', 'ajax', 'editname.php'),{id: this.id},function(jsondata){
- if(jsondata.status == 'success'){
+ } else {
+ $.getJSON(OC.filePath('contacts', 'ajax', 'editname.php'),{id: this.id},function(jsondata) {
+ if(jsondata.status == 'success') {
$('body').append('<div id="name_dialog"></div>');
$('#name_dialog').html(jsondata.data.page).find('#edit_name_dialog' ).dialog({
modal: true,
@@ -839,7 +860,7 @@ Contacts={
title: t('contacts', 'Edit name'),
height: 'auto', width: 'auto',
buttons: {
- 'Ok':function() {
+ 'Ok':function() {
Contacts.UI.Card.saveName(this);
$(this).dialog('close');
},
@@ -890,9 +911,9 @@ Contacts={
$.each(names, function(key, value) {
$('#fn_select')
.append($('<option></option>')
- .text(value));
+ .text(value));
});
-
+
if(this.id == '') {
var aid = $(dlg).find('#aid').val();
Contacts.UI.Card.add(n.join(';'), $('#short').text(), aid);
@@ -903,10 +924,11 @@ Contacts={
loadAddresses:function(){
$('#addresses').hide();
$('#addressdisplay dl.propertycontainer').remove();
+ var addresscontainer = $('#addressdisplay');
for(var adr in this.data.ADR) {
- $('#addressdisplay dl').first().clone().insertAfter($('#addressdisplay dl').last()).show();
- $('#addressdisplay dl').last().removeClass('template').addClass('propertycontainer');
- $('#addressdisplay dl').last().data('checksum', this.data.ADR[adr]['checksum']);
+ addresscontainer.find('dl').first().clone().insertAfter($('#addressdisplay dl').last()).show();
+ addresscontainer.find('dl').last().removeClass('template').addClass('propertycontainer');
+ addresscontainer.find('dl').last().data('checksum', this.data.ADR[adr]['checksum']);
var adrarray = this.data.ADR[adr]['value'];
var adrtxt = '';
if(adrarray[0] && adrarray[0].length > 0) {
@@ -918,7 +940,7 @@ Contacts={
if(adrarray[2] && adrarray[2].length > 0) {
adrtxt = adrtxt + '<li>' + adrarray[2].strip_tags() + '</li>';
}
- if((adrarray[3] && adrarray[5]) && adrarray[3].length > 0 || adrarray[5].length > 0) {
+ if((3 in adrarray && 5 in adrarray) && adrarray[3].length > 0 || adrarray[5].length > 0) {
adrtxt = adrtxt + '<li>' + adrarray[5].strip_tags() + ' ' + adrarray[3].strip_tags() + '</li>';
}
if(adrarray[4] && adrarray[4].length > 0) {
@@ -927,7 +949,7 @@ Contacts={
if(adrarray[6] && adrarray[6].length > 0) {
adrtxt = adrtxt + '<li>' + adrarray[6].strip_tags() + '</li>';
}
- $('#addressdisplay dl').last().find('.addresslist').html(adrtxt);
+ addresscontainer.find('dl').last().find('.addresslist').html(adrtxt);
var types = new Array();
var ttypes = new Array();
for(var param in this.data.ADR[adr]['parameters']) {
@@ -936,12 +958,12 @@ Contacts={
ttypes.push(this.data.ADR[adr]['parameters'][param]);
}
}
- $('#addressdisplay dl').last().find('.adr_type_label').text(types.join('/'));
- $('#addressdisplay dl').last().find('.adr_type').val(ttypes.join(','));
- $('#addressdisplay dl').last().find('.adr').val(adrarray.join(';'));
- $('#addressdisplay dl').last().data('checksum', this.data.ADR[adr]['checksum']);
+ addresscontainer.find('dl').last().find('.adr_type_label').text(types.join('/'));
+ addresscontainer.find('dl').last().find('.adr_type').val(ttypes.join(','));
+ addresscontainer.find('dl').last().find('.adr').val(adrarray.join(';'));
+ addresscontainer.find('dl').last().data('checksum', this.data.ADR[adr]['checksum']);
}
- if($('#addressdisplay dl').length > 1) {
+ if(addresscontainer.find('dl').length > 1) {
$('#addresses').show();
$('#contact_communication').show();
}
@@ -956,7 +978,7 @@ Contacts={
container = $('#addressdisplay dl').last();
container.removeClass('template').addClass('propertycontainer');
} else {
- params['checksum'] = Contacts.UI.checksumFor(obj);
+ params['checksum'] = Contacts.UI.checksumFor(obj);
}
/* Initialize the address edit dialog */
if($('#edit_address_dialog').dialog('isOpen') == true){
@@ -986,9 +1008,6 @@ Contacts={
close : function(event, ui) {
$(this).dialog('destroy').remove();
$('#address_dialog').remove();
- if(isnew) {
- container.remove();
- }
},
open : function(event, ui) {
$( "#adr_city" ).autocomplete({
@@ -1027,7 +1046,7 @@ Contacts={
$( this ).removeClass( "ui-corner-top" ).addClass( "ui-corner-all" );
}
});
- $( "#adr_country" ).autocomplete({
+ $('#adr_country').autocomplete({
source: function( request, response ) {
$.ajax({
url: "http://ws.geonames.org/searchJSON",
@@ -1078,15 +1097,23 @@ Contacts={
saveAddress:function(dlg, obj, isnew){
if(isnew) {
container = $('#addressdisplay dl').last();
- obj = $('#addressdisplay dl:last-child').find('input').first();
+ obj = container.find('input').first();
} else {
checksum = Contacts.UI.checksumFor(obj);
container = Contacts.UI.propertyContainerFor(obj);
}
- var adr = new Array($(dlg).find('#adr_pobox').val().strip_tags(),$(dlg).find('#adr_extended').val().strip_tags(),$(dlg).find('#adr_street').val().strip_tags(),$(dlg).find('#adr_city').val().strip_tags(),$(dlg).find('#adr_region').val().strip_tags(),$(dlg).find('#adr_zipcode').val().strip_tags(),$(dlg).find('#adr_country').val().strip_tags());
- $(container).find('.adr').val(adr.join(';'));
- $(container).find('.adr_type').val($(dlg).find('#adr_type').val());
- $(container).find('.adr_type_label').html(t('contacts',ucwords($(dlg).find('#adr_type').val().toLowerCase())));
+ var adr = new Array(
+ $(dlg).find('#adr_pobox').val().strip_tags(),
+ $(dlg).find('#adr_extended').val().strip_tags(),
+ $(dlg).find('#adr_street').val().strip_tags(),
+ $(dlg).find('#adr_city').val().strip_tags(),
+ $(dlg).find('#adr_region').val().strip_tags(),
+ $(dlg).find('#adr_zipcode').val().strip_tags(),
+ $(dlg).find('#adr_country').val().strip_tags()
+ );
+ container.find('.adr').val(adr.join(';'));
+ container.find('.adr_type').val($(dlg).find('#adr_type').val());
+ container.find('.adr_type_label').html(t('contacts',ucwords($(dlg).find('#adr_type').val().toLowerCase())));
Contacts.UI.Card.saveProperty($(container).find('input').first());
var adrtxt = '';
if(adr[0].length > 0) {
@@ -1107,7 +1134,7 @@ Contacts={
if(adr[6].length > 0) {
adrtxt = adrtxt + '<li>' + adr[6] + '</li>';
}
- $(container).find('.addresslist').html(adrtxt);
+ container.find('.addresslist').html(adrtxt);
},
uploadPhoto:function(filelist) {
if(!filelist) {
@@ -1134,24 +1161,25 @@ Contacts={
form.submit();
}
},
- loadPhotoHandlers:function(){
- $('#phototools li a').tipsy('hide');
- $('#phototools li a').tipsy();
+ loadPhotoHandlers:function() {
+ var phototools = $('#phototools');
+ phototools.find('li a').tipsy('hide');
+ phototools.find('li a').tipsy();
if(this.data.PHOTO) {
- $('#phototools .delete').click(function() {
+ phototools.find('.delete').click(function() {
$(this).tipsy('hide');
Contacts.UI.Card.deleteProperty($('#contacts_details_photo'), 'single');
$(this).hide();
});
- $('#phototools .edit').click(function() {
+ phototools.find('.edit').click(function() {
$(this).tipsy('hide');
Contacts.UI.Card.editCurrentPhoto();
});
- $('#phototools .delete').show();
- $('#phototools .edit').show();
+ phototools.find('.delete').show();
+ phototools.find('.edit').show();
} else {
- $('#phototools .delete').hide();
- $('#phototools .edit').hide();
+ phototools.find('.delete').hide();
+ phototools.find('.edit').hide();
}
},
cloudPhotoSelected:function(path){
@@ -1172,28 +1200,18 @@ Contacts={
$('#phototools li a').tipsy('hide');
var wrapper = $('#contacts_details_photo_wrapper');
wrapper.addClass('loading').addClass('wait');
-
- var img = new Image();
- $(img).load(function () {
+ delete this.photo;
+ this.photo = new Image();
+ $(this.photo).load(function () {
$('img.contacts_details_photo').remove()
- $(this).addClass('contacts_details_photo').hide();
+ $(this).addClass('contacts_details_photo');
wrapper.removeClass('loading').removeClass('wait');
$(this).insertAfter($('#phototools')).fadeIn();
}).error(function () {
// notify the user that the image could not be loaded
- $(t('contacts','something went wrong.')).insertAfter($('#phototools'));
+ Contacts.UI.notify({message:t('contacts','Error loading profile picture.')});
}).attr('src', OC.linkTo('contacts', 'photo.php')+'?id='+self.id+refreshstr);
-
- $.getJSON(OC.filePath('contacts', 'ajax', 'loadphoto.php'),{'id':this.id, 'refresh': refresh},function(jsondata){
- if(jsondata.status == 'success'){
- $('#contacts_details_photo_wrapper').data('checksum', jsondata.data.checksum);
- Contacts.UI.Card.loadPhotoHandlers();
- }
- else{
- OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
- }
- });
- $('#file_upload_form').show();
+ this.loadPhotoHandlers()
},
editCurrentPhoto:function(){
$.getJSON(OC.filePath('contacts', 'ajax', 'currentphoto.php'),{'id':this.id},function(jsondata){
@@ -1247,10 +1265,11 @@ Contacts={
},
addMail:function() {
//alert('addMail');
- $('#emaillist li.template:first-child').clone(true).appendTo($('#emaillist')).show().find('a .tip').tipsy();
- $('#emaillist li.template:last-child').find('select').addClass('contacts_property');
- $('#emaillist li.template:last-child').removeClass('template').addClass('propertycontainer');
- $('#emaillist li:last-child').find('input[type="email"]').focus();
+ var emaillist = $('#emaillist');
+ emaillist.find('li.template:first-child').clone(true).appendTo(emaillist).show().find('a .tip').tipsy();
+ emaillist.find('li.template:last-child').find('select').addClass('contacts_property');
+ emaillist.find('li.template:last-child').removeClass('template').addClass('propertycontainer');
+ emaillist.find('li:last-child').find('input[type="email"]').focus();
return false;
},
loadMails:function() {
@@ -1286,35 +1305,37 @@ Contacts={
return false;
},
addPhone:function() {
- $('#phonelist li.template:first-child').clone(true).appendTo($('#phonelist')); //.show();
- $('#phonelist li.template:last-child').find('select').addClass('contacts_property');
- $('#phonelist li.template:last-child').removeClass('template').addClass('propertycontainer');
- $('#phonelist li:last-child').find('input[type="text"]').focus();
- $('#phonelist li:last-child').find('select').multiselect({
+ var phonelist = $('#phonelist');
+ phonelist.find('li.template:first-child').clone(true).appendTo(phonelist); //.show();
+ phonelist.find('li.template:last-child').find('select').addClass('contacts_property');
+ phonelist.find('li.template:last-child').removeClass('template').addClass('propertycontainer');
+ phonelist.find('li:last-child').find('input[type="text"]').focus();
+ phonelist.find('li:last-child').find('select').multiselect({
noneSelectedText: t('contacts', 'Select type'),
header: false,
selectedList: 4,
classes: 'typelist'
});
- $('#phonelist li:last-child').show();
+ phonelist.find('li:last-child').show();
return false;
},
loadPhones:function() {
$('#phones').hide();
$('#phonelist li.propertycontainer').remove();
+ var phonelist = $('#phonelist');
for(var phone in this.data.TEL) {
this.addPhone();
- $('#phonelist li:last-child').find('select').multiselect('destroy');
- $('#phonelist li:last-child').data('checksum', this.data.TEL[phone]['checksum'])
- $('#phonelist li:last-child').find('input[type="text"]').val(this.data.TEL[phone]['value']);
+ phonelist.find('li:last-child').find('select').multiselect('destroy');
+ phonelist.find('li:last-child').data('checksum', this.data.TEL[phone]['checksum'])
+ phonelist.find('li:last-child').find('input[type="text"]').val(this.data.TEL[phone]['value']);
for(var param in this.data.TEL[phone]['parameters']) {
if(param.toUpperCase() == 'PREF') {
- $('#phonelist li:last-child').find('input[type="checkbox"]').attr('checked', 'checked');
+ phonelist.find('li:last-child').find('input[type="checkbox"]').attr('checked', 'checked');
}
else if(param.toUpperCase() == 'TYPE') {
for(ptype in this.data.TEL[phone]['parameters'][param]) {
var pt = this.data.TEL[phone]['parameters'][param][ptype];
- $('#phonelist li:last-child').find('select option').each(function(){
+ phonelist.find('li:last-child').find('select option').each(function(){
//if ($(this).val().toUpperCase() == pt.toUpperCase()) {
if ($.inArray($(this).val().toUpperCase(), pt.toUpperCase().split(',')) > -1) {
$(this).attr('selected', 'selected');
@@ -1323,14 +1344,14 @@ Contacts={
}
}
}
- $('#phonelist li:last-child').find('select').multiselect({
- noneSelectedText: t('contacts', 'Select type'),
- header: false,
- selectedList: 4,
- classes: 'typelist'
- });
+ phonelist.find('li:last-child').find('select').multiselect({
+ noneSelectedText: t('contacts', 'Select type'),
+ header: false,
+ selectedList: 4,
+ classes: 'typelist'
+ });
}
- if($('#phonelist li').length > 1) {
+ if(phonelist.find('li').length > 1) {
$('#phones').show();
$('#contact_communication').show();
}
@@ -1338,8 +1359,6 @@ Contacts={
},
},
Addressbooks:{
- droptarget:undefined,
- droptext:t('contacts', 'Drop a VCF file<br />to import contacts.'),
overview:function(){
if($('#chooseaddressbook_dialog').dialog('isOpen') == true){
$('#chooseaddressbook_dialog').dialog('moveToTop');
@@ -1377,6 +1396,20 @@ Contacts={
}
});
},
+ addAddressbook:function(name, description, cb) {
+ $.post(OC.filePath('contacts', 'ajax', 'addaddressbook.php'), { name: name, description: description, active: true },
+ function(jsondata){
+ if(jsondata.status == 'success'){
+ if(cb) {
+ cb(jsondata.data);
+ }
+ } else {
+ OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
+ return false;
+ }
+ });
+
+ },
newAddressbook:function(object){
var tr = $(document.createElement('tr'))
.load(OC.filePath('contacts', 'ajax', 'addbook.php'));
@@ -1404,135 +1437,20 @@ Contacts={
});
}
},
- loadImportHandlers:function() {
- $('#import_upload_start').change(function(){
- Contacts.UI.Addressbooks.uploadImport(this.files);
- });
- $('#importaddressbook_dialog').find('.upload').click(function() {
- Contacts.UI.Addressbooks.droptarget.html(t('contacts', 'Uploading...'));
- Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, true);
- //$('#import_upload_start').trigger('click');
- //return false;
- });
- $('#importaddressbook_dialog').find('.upload').tipsy();
- this.droptarget = $('#import_drop_target');
- $(this.droptarget).bind('dragover',function(event){
- $(event.target).addClass('droppable');
- event.stopPropagation();
- event.preventDefault();
- });
- $(this.droptarget).bind('dragleave',function(event){
- $(event.target).removeClass('droppable');
- });
- $(this.droptarget).bind('drop',function(event){
- event.stopPropagation();
- event.preventDefault();
- $(event.target).removeClass('droppable');
- $(event.target).html(t('contacts', 'Uploading...'));
- Contacts.UI.loading(event.target, true);
- $.importUpload(event.originalEvent.dataTransfer.files);
- });
-
- $.importUpload = function(files){
- var file = files[0];
- if(file.size > $('#max_upload').val()){
- OC.dialogs.alert(t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'), t('contacts','Upload too large'));
- $(Contacts.UI.Addressbooks.droptarget).html(Contacts.UI.Addressbooks.droptext);
- Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false);
- return;
- }
- if(file.type.indexOf('text') != 0) {
- OC.dialogs.alert(t('contacts','You have dropped a file type that cannot be imported: ') + file.type, t('contacts','Wrong file type'));
- $(Contacts.UI.Addressbooks.droptarget).html(Contacts.UI.Addressbooks.droptext);
- Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false);
- return;
- }
- var xhr = new XMLHttpRequest();
-
- if (!xhr.upload) {
- OC.dialogs.alert(t('contacts', 'Your browser doesn\'t support AJAX upload. Please upload the contacts file to ownCloud and import that way.'), t('contacts', 'Error'))
- }
- importUpload = xhr.upload,
- xhr.onreadystatechange = function() {
- if (xhr.readyState == 4){
- response = $.parseJSON(xhr.responseText);
- if(response.status == 'success') {
- if(xhr.status == 200) {
- Contacts.UI.Addressbooks.doImport(response.data.path, response.data.file);
- } else {
- $(Contacts.UI.Addressbooks.droptarget).html(Contacts.UI.Addressbooks.droptext);
- Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false);
- OC.dialogs.alert(xhr.status + ': ' + xhr.responseText, t('contacts', 'Error'));
- }
- } else {
- OC.dialogs.alert(response.data.message, t('contacts', 'Error'));
- }
- }
- };
- xhr.open('POST', OC.filePath('contacts', 'ajax', 'uploadimport.php') + '?file='+encodeURIComponent(file.name)+'&requesttoken='+requesttoken, true);
- xhr.setRequestHeader('Cache-Control', 'no-cache');
- xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
- xhr.setRequestHeader('X_FILE_NAME', encodeURIComponent(file.name));
- xhr.setRequestHeader('X-File-Size', file.size);
- xhr.setRequestHeader('Content-Type', file.type);
- xhr.send(file);
- }
- },
- uploadImport:function(filelist) {
- if(!filelist) {
- OC.dialogs.alert(t('contacts','No files selected for upload.'), t('contacts', 'Error'));
- return;
- }
- //var file = filelist.item(0);
- var file = filelist[0];
- var target = $('#import_upload_target');
- var form = $('#import_upload_form');
- var totalSize=0;
- if(file.size > $('#max_upload').val()){
- OC.dialogs.alert(t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'), t('contacts', 'Error'));
- return;
- } else {
- target.load(function(){
- var response=jQuery.parseJSON(target.contents().text());
- if(response != undefined && response.status == 'success'){
- Contacts.UI.Addressbooks.doImport(response.data.path, response.data.file);
- }else{
- OC.dialogs.alert(response.data.message, t('contacts', 'Error'));
- }
- });
- form.submit();
- }
- },
- importAddressbook:function(object){
- var tr = $(document.createElement('tr'))
- .load(OC.filePath('contacts', 'ajax', 'importaddressbook.php'));
- $(object).closest('tr').after(tr).hide();
- },
- doImport:function(path, file){
- $(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Importing...'));
- Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, true);
- var id = $('#importaddressbook_dialog').find('#book').val();
- $.post(OC.filePath('contacts', '', 'import.php'), { id: id, path: path, file: file, fstype: 'OC_FilesystemView' },
+ doImport:function(file, aid){
+ $.post(OC.filePath('contacts', '', 'import.php'), { id: aid, file: file, fstype: 'OC_FilesystemView' },
function(jsondata){
- if(jsondata.status == 'success'){
- Contacts.UI.Addressbooks.droptarget.html(t('contacts', 'Import done. Success/Failure: ')+jsondata.data.imported+'/'+jsondata.data.failed);
- $('#chooseaddressbook_dialog').find('#close_button').val(t('contacts', 'OK'));
- Contacts.UI.Contacts.update();
- setTimeout(
- function() {
- $(Contacts.UI.Addressbooks.droptarget).html(Contacts.UI.Addressbooks.droptext);
- }, 5000);
- } else {
- OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
+ if(jsondata.status != 'success'){
+ Contacts.UI.notify({message:jsondata.data.message});
}
});
- Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false);
+ return false;
},
submit:function(button, bookid){
var displayname = $("#displayname_"+bookid).val().trim();
var active = $("#edit_active_"+bookid+":checked").length;
var description = $("#description_"+bookid).val();
-
+
if(displayname.length == 0) {
OC.dialogs.alert(t('contacts', 'Displayname cannot be empty.'), t('contacts', 'Error'));
return false;
@@ -1558,60 +1476,151 @@ Contacts={
}
},
Contacts:{
+ contacts:{},
batchnum:50,
+ getContact:function(id) {
+ if(!this.contacts[id]) {
+ this.contacts[id] = $('#contacts li[data-id="'+id+'"]');
+ if(!this.contacts[id]) {
+ self = this;
+ $.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':id},function(jsondata){
+ if(jsondata.status == 'success'){
+ self.contacts[id] = self.insertContact({data:jsondata.data});
+ }
+ else{
+ OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
+ }
+ });
+ }
+ }
+ return this.contacts[id];
+ },
drop:function(event, ui) {
var dragitem = ui.draggable, droptarget = $(this);
- //console.log('Drop ' + dragitem.data('id') +' on: ' + droptarget.data('id'));
+ if(dragitem.is('li')) {
+ Contacts.UI.Contacts.dropContact(event, dragitem, droptarget);
+ } else {
+ Contacts.UI.Contacts.dropAddressbook(event, dragitem, droptarget);
+ }
+ },
+ dropContact:function(event, dragitem, droptarget) {
if(dragitem.data('bookid') == droptarget.data('id')) {
return false;
}
var droplist = (droptarget.is('ul'))?droptarget:droptarget.next();
- $.post(OC.filePath('contacts', 'ajax', 'movetoaddressbook.php'), { ids: dragitem.data('id'), aid: $(this).data('id') },
+ $.post(OC.filePath('contacts', 'ajax', 'movetoaddressbook.php'), { ids: dragitem.data('id'), aid: droptarget.data('id') },
function(jsondata){
if(jsondata.status == 'success'){
- // Do some inserting/removing/sorting magic
- var name = $(dragitem).find('a').html();
- var added = false;
- $(droplist).children().each(function(){
- if ($(this).text().toLowerCase() > name.toLowerCase()) {
- $(this).before(dragitem.detach()); //.fadeIn('slow');
- added = true;
- return false;
- }
- });
- if(!added) {
- $(droplist).append(dragitem.detach());
- }
+ dragitem.attr('data-bookid', droptarget.data('id'))
dragitem.data('bookid', droptarget.data('id'));
+ Contacts.UI.Contacts.insertContact({
+ contactlist:droplist,
+ contact:dragitem.detach()
+ });
Contacts.UI.Contacts.scrollTo(dragitem.data('id'));
} else {
OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
}
});
},
+ dropAddressbook:function(event, dragitem, droptarget) {
+ alert('Dropping address books not implemented yet');
+ },
+ /**
+ * @params params An object with the properties 'contactlist':a jquery object of the ul to insert into,
+ * 'contacts':a jquery object of all items in the list and either 'data': an object with the properties
+ * id, addressbookid and displayname or 'contact': a listitem to be inserted directly.
+ * If 'contactlist' or 'contacts' aren't defined they will be search for based in the properties in 'data'.
+ */
+ insertContact:function(params) {
+ var id, bookid;
+ if(!params.contactlist) {
+ // FIXME: Check if contact really exists.
+ bookid = params.data ? params.data.addressbookid : params.contact.data('bookid');
+ id = params.data ? params.data.id : params.contact.data('id');
+ params.contactlist = $('#contacts ul[data-id="'+bookid+'"]');
+ }
+ if(!params.contacts) {
+ bookid = params.data ? params.data.addressbookid : params.contact.data('bookid');
+ id = params.data ? params.data.id : params.contact.data('id');
+ params.contacts = $('#contacts ul[data-id="'+bookid+'"] li');
+ }
+ var contact = params.data
+ ? $('<li data-id="'+params.data.id+'" data-bookid="'+params.data.addressbookid+'" role="button"><a href="'+OC.linkTo('contacts', 'index.php')+'&id='+params.data.id+'" style="background: url('+OC.filePath('contacts', '', 'thumbnail.php')+'?id='+params.data.id+') no-repeat scroll 0% 0% transparent;">'+params.data.displayname+'</a></li>')
+ : params.contact;
+ var added = false;
+ var name = params.data ? params.data.displayname.toLowerCase() : contact.find('a').text().toLowerCase();
+ if(params.contacts) {
+ params.contacts.each(function() {
+ if ($(this).text().toLowerCase() > name) {
+ $(this).before(contact);
+ added = true;
+ return false;
+ }
+ });
+ }
+ if(!added || !params.contacts) {
+ params.contactlist.append(contact);
+ }
+ //this.contacts[id] = contact;
+ return contact;
+ },
+ next:function(reverse) {
+ // TODO: Check if we're last-child/first-child and jump to next/prev address book.
+ var curlistitem = $('#contacts li[data-id="'+Contacts.UI.Card.id+'"]');
+ var newlistitem = reverse ? curlistitem.prev('li') : curlistitem.next('li');
+ if(newlistitem) {
+ curlistitem.removeClass('active');
+ Contacts.UI.Card.update({
+ cid:newlistitem.data('id'),
+ aid:newlistitem.data('bookid')
+ });
+ }
+ },
+ previous:function() {
+ this.next(true);
+ },
// Reload the contacts list.
- update:function(id, aid, start){
+ update:function(params){
+ if(!params) { params = {}; }
+ if(!params.start) {
+ if(params.aid) {
+ $('#contacts h3[data-id="'+params.aid+'"],#contacts ul[data-id="'+params.aid+'"]').remove();
+ } else {
+ $('#contacts').empty();
+ }
+ }
self = this;
- console.log('update: ' + aid + ' ' + start);
+ console.log('update: ' + params.cid + ' ' + params.aid + ' ' + params.start);
var firstrun = false;
var opts = {};
- opts['startat'] = (start?start:0);
- if(aid) {
- opts['aid'] = aid;
+ opts['startat'] = (params.start?params.start:0);
+ if(params.aid) {
+ opts['aid'] = params.aid;
}
$.getJSON(OC.filePath('contacts', 'ajax', 'contacts.php'),opts,function(jsondata){
if(jsondata.status == 'success'){
var books = jsondata.data.entries;
- $.each(jsondata.data.entries, function(b, book) {
+ $.each(books, function(b, book) {
if($('#contacts h3[data-id="'+b+'"]').length == 0) {
firstrun = true;
-
if($('#contacts h3').length == 0) {
- $('#contacts').html('<h3 class="addressbook" data-id="'+b+'">'+book.displayname+'</h3><ul class="contacts hidden" data-id="'+b+'"></ul>');
+ $('#contacts').html('<h3 class="addressbook" contextmenu="addressbookmenu" data-id="'+b+'">'+book.displayname+'</h3><ul class="contacts hidden" data-id="'+b+'"></ul>');
} else {
if(!$('#contacts h3[data-id="'+b+'"]').length) {
- $('<h3 class="addressbook" data-id="'+b+'">'+book.displayname+'</h3><ul class="contacts hidden" data-id="'+b+'"></ul>')
- .appendTo('#contacts');
+ var item = $('<h3 class="addressbook" contextmenu="addressbookmenu" data-id="'+b+'">'+book.displayname+'</h3><ul class="contacts hidden" data-id="'+b+'"></ul>')
+ var added = false;
+ $('#contacts h3').each(function(){
+ if ($(this).text().toLowerCase() > book.displayname.toLowerCase()) {
+ $(this).before(item).fadeIn('fast');
+ added = true;
+ return false;
+ }
+ });
+ if(!added) {
+ $('#contacts').append(item);
+ }
+
}
}
$('#contacts h3[data-id="'+b+'"]').on('click', function(event) {
@@ -1620,45 +1629,46 @@ Contacts={
$('#contacts ul[data-id="'+b+'"]').slideToggle(300);
return false;
});
- var accept = 'li:not([data-bookid="'+b+'"])';
- $('#contacts h3[data-id="'+b+'"]').droppable({
+ var accept = 'li:not([data-bookid="'+b+'"]),h3:not([data-id="'+b+'"])';
+ $('#contacts h3[data-id="'+b+'"],#contacts ul[data-id="'+b+'"]').droppable({
drop: Contacts.UI.Contacts.drop,
activeClass: 'ui-state-hover',
accept: accept
});
}
var contactlist = $('#contacts ul[data-id="'+b+'"]');
+ var contacts = $('#contacts ul[data-id="'+b+'"] li');
for(var c in book.contacts) {
if(book.contacts[c].id == undefined) { continue; }
- if($('#contacts li[data-id="'+book.contacts[c]['id']+'"][data-id="'+book.contacts[c]['bookid']+'"]').length == 0) {
- var contact = Contacts.UI.Card.createEntry(book.contacts[c]);
- if(c == self.batchnum-5) {
+ if(!$('#contacts li[data-id="'+book.contacts[c]['id']+'"]').length) {
+ var contact = Contacts.UI.Contacts.insertContact({contactlist:contactlist, contacts:contacts, data:book.contacts[c]});
+ if(c == self.batchnum-10) {
contact.bind('inview', function(event, isInView, visiblePartX, visiblePartY) {
$(this).unbind(event);
var bookid = $(this).data('bookid');
var numsiblings = $('.contacts li[data-bookid="'+bookid+'"]').length;
if (isInView && numsiblings >= self.batchnum) {
console.log('This would be a good time to load more contacts.');
- Contacts.UI.Contacts.update(id, bookid, $('#contacts li[data-bookid="'+bookid+'"]').length);
+ Contacts.UI.Contacts.update({cid:params.cid, aid:bookid, start:$('#contacts li[data-bookid="'+bookid+'"]').length});
}
});
}
- contactlist.append(contact);
}
}
});
if($('#contacts h3').length > 1) {
- $('#contacts li').draggable({
+ $('#contacts li,#contacts h3').draggable({
+ distance: 10,
revert: 'invalid',
axis: 'y', containment: '#contacts',
- scroll: true, scrollSensitivity: 100,
+ scroll: true, scrollSensitivity: 40,
opacity: 0.7, helper: 'clone'
});
} else {
$('#contacts h3').first().addClass('active');
}
if(opts['startat'] == 0) { // only update card on first load.
- Contacts.UI.Card.update();
+ Contacts.UI.Card.update(params);
}
}
else{
@@ -1673,9 +1683,10 @@ Contacts={
},
scrollTo:function(id){
var item = $('#contacts li[data-id="'+id+'"]');
- if(item) {
- $('.contacts').animate({
- scrollTop: $('#contacts li[data-id="'+id+'"]').offset().top-20}, 'slow','swing');
+ if(item && $.isNumeric(item.offset().top)) {
+ console.log('scrollTo ' + parseInt(item.offset().top));
+ $('#contacts').animate({
+ scrollTop: parseInt(item.offset()).top-40}, 'slow','swing');
}
}
}
@@ -1686,23 +1697,90 @@ $(document).ready(function(){
OCCategories.changed = Contacts.UI.Card.categoriesChanged;
OCCategories.app = 'contacts';
- $('#notification').click(function(){
- $('#notification').fadeOut();
+ $('#chooseaddressbook').on('click keydown', Contacts.UI.Addressbooks.overview);
+ $('#contacts_newcontact').on('click keydown', Contacts.UI.Card.editNew);
+
+ var ninjahelp = $('#ninjahelp');
+
+ ninjahelp.find('.close').on('click keydown',function() {
+ ninjahelp.hide();
});
-
- $('#chooseaddressbook').click(Contacts.UI.Addressbooks.overview);
- $('#chooseaddressbook').keydown(Contacts.UI.Addressbooks.overview);
- $('#contacts_newcontact').click(Contacts.UI.Card.editNew);
- $('#contacts_newcontact').keydown(Contacts.UI.Card.editNew);
-
- // Load a contact.
+ $(document).on('keyup', function(event) {
+ console.log(event.which + ' ' + event.target.nodeName);
+ if(event.target.nodeName.toUpperCase() != 'BODY'
+ || $('#contacts li').length == 0
+ || !Contacts.UI.Card.id) {
+ return;
+ }
+ /**
+ * To add:
+ * (Shift)n/p: next/prev addressbook
+ * u (85): hide/show leftcontent
+ * f (70): add field
+ */
+ switch(event.which) {
+ case 27: // Esc
+ ninjahelp.hide();
+ break;
+ case 46:
+ if(event.shiftKey) {
+ Contacts.UI.Card.delayedDelete();
+ }
+ break;
+ case 32: // space
+ if(event.shiftKey) {
+ Contacts.UI.Contacts.previous();
+ break;
+ }
+ case 40: // down
+ case 75: // k
+ Contacts.UI.Contacts.next();
+ break;
+ case 65: // a
+ if(event.shiftKey) {
+ // add addressbook
+ Contacts.UI.notImplemented();
+ break;
+ }
+ Contacts.UI.Card.editNew();
+ break;
+ case 38: // up
+ case 74: // j
+ Contacts.UI.Contacts.previous();
+ break;
+ case 78: // n
+ // next addressbook
+ Contacts.UI.notImplemented();
+ break;
+ case 13: // Enter
+ case 79: // o
+ var aid = $('#contacts h3.active').first().data('id');
+ if(aid) {
+ $('#contacts ul[data-id="'+aid+'"]').slideToggle(300);
+ }
+ break;
+ case 80: // p
+ // prev addressbook
+ Contacts.UI.notImplemented();
+ break;
+ case 82: // r
+ Contacts.UI.Contacts.update({cid:Contacts.UI.Card.id});
+ break;
+ case 191: // ?
+ ninjahelp.toggle('fast');
+ break;
+ }
+
+ });
+
+ // Load a contact.
$('.contacts').keydown(function(event) {
- if(event.which == 13) {
+ if(event.which == 13 || event.which == 32) {
$('.contacts').click();
}
});
- $(document).on('click', '.contacts', function(event){
+ $(document).on('click', '#contacts', function(event){
var $tgt = $(event.target);
if ($tgt.is('li') || $tgt.is('a')) {
var item = $tgt.is('li')?$($tgt):($tgt).parent();
@@ -1711,7 +1789,13 @@ $(document).ready(function(){
item.addClass('active');
var oldid = $('#rightcontent').data('id');
if(oldid != 0){
- $('.contacts li[data-id="'+oldid+'"]').removeClass('active');
+ var olditem = $('.contacts li[data-id="'+oldid+'"]');
+ var oldbookid = olditem.data('bookid');
+ olditem.removeClass('active');
+ if(oldbookid != bookid) {
+ $('#contacts h3[data-id="'+oldbookid+'"]').removeClass('active');
+ $('#contacts h3[data-id="'+bookid+'"]').addClass('active');
+ }
}
$.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':id},function(jsondata){
if(jsondata.status == 'success'){
@@ -1725,81 +1809,267 @@ $(document).ready(function(){
return false;
});
- /*$('.contacts li').bind('inview', function(event, isInView, visiblePartX, visiblePartY) {
- if (isInView) { //NOTE: I've kept all conditions for future reference ;-)
- // element is now visible in the viewport
- if (visiblePartY == 'top') {
- // top part of element is visible
- } else if (visiblePartY == 'bottom') {
- // bottom part of element is visible
- } else {
- // whole part of element is visible
- if (!$(this).find('a').attr('style')) {
- //alert($(this).data('id') + ' has background: ' + $(this).attr('style'));
- $(this).find('a').css('background','url('+OC.filePath('contacts', '', 'thumbnail.php')+'?id='+$(this).data('id')+') no-repeat');
- }// else {
- // alert($(this).data('id') + ' has style ' + $(this).attr('style').match('url'));
- //}
- }
- } else {
- // element has gone out of viewport
- }
- });*/
-
$('.contacts_property').live('change', function(){
Contacts.UI.Card.saveProperty(this);
});
- /**
- * Upload function for dropped files. Should go in the Contacts class/object.
- */
- $.fileUpload = function(files){
- var file = files[0];
- if(file.size > $('#max_upload').val()){
- OC.dialogs.alert(t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'), t('contacts','Upload too large'));
- return;
- }
- if (file.type.indexOf("image") != 0) {
- OC.dialogs.alert(t('contacts','Only image files can be used as profile picture.'), t('contacts','Wrong file type'));
- return;
- }
- var xhr = new XMLHttpRequest();
+ $(function() {
+ // Upload function for dropped contact photos files. Should go in the Contacts class/object.
+ $.fileUpload = function(files){
+ var file = files[0];
+ if(file.size > $('#max_upload').val()){
+ OC.dialogs.alert(t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'), t('contacts','Upload too large'));
+ return;
+ }
+ if (file.type.indexOf("image") != 0) {
+ OC.dialogs.alert(t('contacts','Only image files can be used as profile picture.'), t('contacts','Wrong file type'));
+ return;
+ }
+ var xhr = new XMLHttpRequest();
+
+ if (!xhr.upload) {
+ OC.dialogs.alert(t('contacts', 'Your browser doesn\'t support AJAX upload. Please click on the profile picture to select a photo to upload.'), t('contacts', 'Error'))
+ }
+ fileUpload = xhr.upload,
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4){
+ response = $.parseJSON(xhr.responseText);
+ if(response.status == 'success') {
+ if(xhr.status == 200) {
+ Contacts.UI.Card.editPhoto(response.data.id, response.data.tmp);
+ } else {
+ OC.dialogs.alert(xhr.status + ': ' + xhr.responseText, t('contacts', 'Error'));
+ }
+ } else {
+ OC.dialogs.alert(response.data.message, t('contacts', 'Error'));
+ }
+ }
+ };
- if (!xhr.upload) {
- OC.dialogs.alert(t('contacts', 'Your browser doesn\'t support AJAX upload. Please click on the profile picture to select a photo to upload.'), t('contacts', 'Error'))
+ fileUpload.onprogress = function(e){
+ if (e.lengthComputable){
+ var _progress = Math.round((e.loaded * 100) / e.total);
+ //if (_progress != 100){
+ //}
+ }
+ };
+ xhr.open('POST', OC.filePath('contacts', 'ajax', 'uploadphoto.php')+'?id='+Contacts.UI.Card.id+'&requesttoken='+requesttoken+'&imagefile='+encodeURIComponent(file.name), true);
+ xhr.setRequestHeader('Cache-Control', 'no-cache');
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ xhr.setRequestHeader('X_FILE_NAME', encodeURIComponent(file.name));
+ xhr.setRequestHeader('X-File-Size', file.size);
+ xhr.setRequestHeader('Content-Type', file.type);
+ xhr.send(file);
}
- fileUpload = xhr.upload,
- xhr.onreadystatechange = function() {
- if (xhr.readyState == 4){
- response = $.parseJSON(xhr.responseText);
- if(response.status == 'success') {
- if(xhr.status == 200) {
- Contacts.UI.Card.editPhoto(response.data.id, response.data.tmp);
+ });
+
+ $(document).bind('drop dragover', function (e) {
+ e.preventDefault(); // prevent browser from doing anything, if file isn't dropped in dropZone
+ });
+
+ //add multiply file upload attribute to all browsers except konqueror (which crashes when it's used)
+ if(navigator.userAgent.search(/konqueror/i)==-1){
+ $('#import_upload_start').attr('multiple','multiple')
+ }
+ // Import using jquery.fileupload
+ $(function() {
+ var uploadingFiles = {}, numfiles = 0, uploadedfiles = 0, retries = 0;
+ var aid;
+
+ $('#import_upload_start').fileupload({
+ dropZone: $('#contacts'), // restrict dropZone to contacts list.
+ acceptFileTypes: /^text\/(directory|vcard|x-vcard)$/i,
+ add: function(e, data) {
+ var files = data.files;
+ var totalSize=0;
+ if(files) {
+ numfiles += files.length; uploadedfiles = 0;
+ for(var i=0;i<files.length;i++) {
+ if(files[i].size ==0 && files[i].type== '') {
+ OC.dialogs.alert(t('files', 'Unable to upload your file as it is a directory or has 0 bytes'), t('files', 'Upload Error'));
+ return;
+ }
+ totalSize+=files[i].size;
+ }
+ }
+ if(totalSize>$('#max_upload').val()){
+ OC.dialogs.alert(t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'), t('contacts','Upload too large'));
+ numfiles = uploadedfiles = retries = aid = 0;
+ uploadingFiles = {};
+ return;
+ }else{
+ if($.support.xhrFileUpload) {
+ for(var i=0;i<files.length;i++){
+ var fileName = files[i].name;
+ var dropTarget;
+ if($(e.originalEvent.target).is('h3')) {
+ dropTarget = $(e.originalEvent.target).next('ul');
+ } else {
+ dropTarget = $(e.originalEvent.target).closest('ul');
+ }
+ if(dropTarget && dropTarget.hasClass('contacts')) { // TODO: More thorough check for where we are.
+ aid = dropTarget.attr('data-id');
+ } else {
+ aid = undefined;
+ }
+ var jqXHR = $('#import_upload_start').fileupload('send', {files: files[i],
+ formData: function(form) {
+ var formArray = form.serializeArray();
+ formArray['aid'] = aid;
+ return formArray;
+ }})
+ .success(function(result, textStatus, jqXHR) {
+ if(result.status == 'success') {
+ // import the file
+ uploadedfiles += 1;
+ } else {
+ Contacts.UI.notify({message:jsondata.data.message});
+ }
+ return false;
+ })
+ .error(function(jqXHR, textStatus, errorThrown) {
+ console.log(textStatus);
+ Contacts.UI.notify({message:errorThrown + ': ' + textStatus,});
+ });
+ uploadingFiles[fileName] = jqXHR;
+ }
} else {
- OC.dialogs.alert(xhr.status + ': ' + xhr.responseText, t('contacts', 'Error'));
+ data.submit().success(function(data, status) {
+ response = jQuery.parseJSON(data[0].body.innerText);
+ if(response[0] != undefined && response[0].status == 'success') {
+ var file=response[0];
+ delete uploadingFiles[file.name];
+ $('tr').filterAttr('data-file',file.name).data('mime',file.mime);
+ var size = $('tr').filterAttr('data-file',file.name).find('td.filesize').text();
+ if(size==t('files','Pending')){
+ $('tr').filterAttr('data-file',file.name).find('td.filesize').text(file.size);
+ }
+ FileList.loadingDone(file.name);
+ } else {
+ Contacts.UI.notify({message:response.data.message});
+ }
+ });
+ }
+ }
+ },
+ fail: function(e, data) {
+ console.log('fail');
+ Contacts.UI.notify({message:data.errorThrown + ': ' + data.textStatus});
+ // TODO: Remove file from upload queue.
+ },
+ progressall: function(e, data) {
+ var progress = (data.loaded/data.total)*50;
+ $('#uploadprogressbar').progressbar('value',progress);
+ },
+ start: function(e, data) {
+ $('#uploadprogressbar').progressbar({value:0});
+ $('#uploadprogressbar').fadeIn();
+ if(data.dataType != 'iframe ') {
+ $('#upload input.stop').show();
+ }
+ },
+ stop: function(e, data) {
+ // stop only gets fired once so we collect uploaded items here.
+ var importFiles = function(aid, fileList) {
+ // Create a closure that can be called from different places.
+ if(numfiles != uploadedfiles) {
+ Contacts.UI.notify({message:t('contacts', 'Not all files uploaded. Retrying...')});
+ retries += 1;
+ if(retries > 3) {
+ numfiles = uploadedfiles = retries = aid = 0;
+ uploadingFiles = {};
+ $('#uploadprogressbar').fadeOut();
+ OC.dialogs.alert(t('contacts', 'Something went wrong with the upload, please retry.'), t('contacts', 'Error'));
+ return;
+ }
+ setTimeout(function() { // Just to let any uploads finish
+ importFiles(aid, uploadingFiles);
+ }, 1000);
}
+ $('#uploadprogressbar').progressbar('value',50);
+ var todo = uploadedfiles;
+ $.each(fileList, function(fileName, data) {
+ Contacts.UI.Addressbooks.doImport(fileName, aid);
+ delete fileList[fileName];
+ numfiles -= 1; uploadedfiles -= 1;
+ $('#uploadprogressbar').progressbar('value',50+(50/(todo-uploadedfiles)));
+ })
+ $('#uploadprogressbar').progressbar('value',100);
+ $('#uploadprogressbar').fadeOut();
+ setTimeout(function() {
+ Contacts.UI.Contacts.update({aid:aid});
+ numfiles = uploadedfiles = retries = aid = 0;
+ }, 1000);
+ }
+ if(!aid) {
+ // Either selected with filepicker or dropped outside of an address book.
+ $.getJSON(OC.filePath('contacts', 'ajax', 'selectaddressbook.php'),{},function(jsondata) {
+ if(jsondata.status == 'success') {
+ if($('#selectaddressbook_dialog').dialog('isOpen') == true) {
+ $('#selectaddressbook_dialog').dialog('moveToTop');
+ } else {
+ $('#dialog_holder').html(jsondata.data.page).ready(function($) {
+ var select_dlg = $('#selectaddressbook_dialog');
+ select_dlg.dialog({
+ modal: true, height: 'auto', width: 'auto',
+ buttons: {
+ 'Ok':function() {
+ aid = select_dlg.find('input:checked').val();
+ if(aid == 'new') {
+ var displayname = select_dlg.find('input.name').val();
+ var description = select_dlg.find('input.desc').val();
+ if(!displayname.trim()) {
+ OC.dialogs.alert(t('contacts', 'The address book name cannot be empty.'), t('contacts', 'Error'));
+ return false;
+ }
+ $(this).dialog('close');
+ Contacts.UI.Addressbooks.addAddressbook(displayname, description, function(addressbook){
+ aid = addressbook.id;
+ setTimeout(function() {
+ importFiles(aid, uploadingFiles);
+ }, 500);
+ console.log('aid ' + aid);
+ });
+ } else {
+ setTimeout(function() {
+ importFiles(aid, uploadingFiles);
+ }, 500);
+ console.log('aid ' + aid);
+ $(this).dialog('close');
+ }
+ },
+ 'Cancel':function() {
+ $(this).dialog('close');
+ numfiles = uploadedfiles = retries = aid = 0;
+ uploadingFiles = {};
+ $('#uploadprogressbar').fadeOut();
+ }
+ },
+ close: function(event, ui) {
+ // TODO: If numfiles != 0 delete tmp files after a timeout.
+ $(this).dialog('destroy').remove();
+ }
+ });
+ });
+ }
+ } else {
+ $('#uploadprogressbar').fadeOut();
+ OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
+ }
+ });
} else {
- OC.dialogs.alert(response.data.message, t('contacts', 'Error'));
+ // Dropped on an address book or it's list.
+ setTimeout(function() { // Just to let any uploads finish
+ importFiles(aid, uploadingFiles);
+ }, 1000);
+ }
+ if(data.dataType != 'iframe ') {
+ $('#upload input.stop').hide();
}
}
- };
-
- fileUpload.onprogress = function(e){
- if (e.lengthComputable){
- var _progress = Math.round((e.loaded * 100) / e.total);
- //if (_progress != 100){
- //}
- }
- };
- xhr.open('POST', OC.filePath('contacts', 'ajax', 'uploadphoto.php')+'?id='+Contacts.UI.Card.id+'&requesttoken='+requesttoken+'&imagefile='+encodeURIComponent(file.name), true);
- xhr.setRequestHeader('Cache-Control', 'no-cache');
- xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
- xhr.setRequestHeader('X_FILE_NAME', encodeURIComponent(file.name));
- xhr.setRequestHeader('X-File-Size', file.size);
- xhr.setRequestHeader('Content-Type', file.type);
- xhr.send(file);
- }
+ })
+ });
Contacts.UI.loadHandlers();
- Contacts.UI.Contacts.update(id);
+ Contacts.UI.Contacts.update({cid:id});
});
diff --git a/apps/contacts/js/jquery.inview.js b/apps/contacts/js/jquery.inview.js
index 01900b0b4b4..9687cd83368 100644
--- a/apps/contacts/js/jquery.inview.js
+++ b/apps/contacts/js/jquery.inview.js
@@ -62,8 +62,8 @@
function getViewportOffset() {
return {
- top: w.pageYOffset || documentElement.scrollTop || d.body.scrollTop,
- left: w.pageXOffset || documentElement.scrollLeft || d.body.scrollLeft
+ top: w.pageYOffset || documentElement.scrollTop || (d.body?d.body.scrollTop:0),
+ left: w.pageXOffset || documentElement.scrollLeft || (d.body?d.body.scrollLeft:0)
};
}
diff --git a/apps/contacts/lib/VCFExportPlugin.php b/apps/contacts/lib/VCFExportPlugin.php
index 6554cb258e8..9a64c964b06 100644
--- a/apps/contacts/lib/VCFExportPlugin.php
+++ b/apps/contacts/lib/VCFExportPlugin.php
@@ -31,7 +31,7 @@ class Sabre_CardDAV_VCFExportPlugin extends Sabre_DAV_ServerPlugin {
public function initialize(Sabre_DAV_Server $server) {
$this->server = $server;
- $this->server->subscribeEvent('beforeMethod',array($this,'beforeMethod'), 90);
+ $this->server->subscribeEvent('beforeMethod', array($this,'beforeMethod'), 90);
}
@@ -49,7 +49,7 @@ class Sabre_CardDAV_VCFExportPlugin extends Sabre_DAV_ServerPlugin {
if ($this->server->httpRequest->getQueryString()!='export') return;
// splitting uri
- list($uri) = explode('?',$uri,2);
+ list($uri) = explode('?', $uri, 2);
$node = $this->server->tree->getNodeForPath($uri);
@@ -60,12 +60,12 @@ class Sabre_CardDAV_VCFExportPlugin extends Sabre_DAV_ServerPlugin {
$aclPlugin->checkPrivileges($uri, '{DAV:}read');
}
- $this->server->httpResponse->setHeader('Content-Type','text/directory');
+ $this->server->httpResponse->setHeader('Content-Type', 'text/directory');
$this->server->httpResponse->sendStatus(200);
$nodes = $this->server->getPropertiesForPath($uri, array(
'{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}address-data',
- ),1);
+ ), 1);
$this->server->httpResponse->sendBody($this->generateVCF($nodes));
diff --git a/apps/contacts/lib/addressbook.php b/apps/contacts/lib/addressbook.php
index 4077d26e58a..6e082c5e2e9 100644
--- a/apps/contacts/lib/addressbook.php
+++ b/apps/contacts/lib/addressbook.php
@@ -37,17 +37,17 @@
/**
* This class manages our addressbooks.
*/
-class OC_Contacts_Addressbook{
+class OC_Contacts_Addressbook {
/**
* @brief Returns the list of addressbooks for a specific user.
* @param string $uid
* @param boolean $active Only return addressbooks with this $active state, default(=false) is don't care
* @return array or false.
*/
- public static function all($uid, $active=false){
+ public static function all($uid, $active=false) {
$values = array($uid);
$active_where = '';
- if ($active){
+ if ($active) {
$active_where = ' AND active = ?';
$values[] = 1;
}
@@ -55,13 +55,13 @@ class OC_Contacts_Addressbook{
$stmt = OCP\DB::prepare( 'SELECT * FROM *PREFIX*contacts_addressbooks WHERE userid = ? ' . $active_where . ' ORDER BY displayname' );
$result = $stmt->execute($values);
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.' exception: '.$e->getMessage(),OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.' uid: '.$uid,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.' exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.' uid: '.$uid, OCP\Util::DEBUG);
return false;
}
$addressbooks = array();
- while( $row = $result->fetchRow()){
+ while( $row = $result->fetchRow()) {
$addressbooks[] = $row;
}
$addressbooks = array_merge($addressbooks, OCP\Share::getItemsSharedWith('addressbook', OC_Contacts_Share::FORMAT_ADDRESSBOOKS));
@@ -76,8 +76,8 @@ class OC_Contacts_Addressbook{
* @param integer $uid User id. If null current user will be used.
* @return array
*/
- public static function activeIds($uid = null){
- if(is_null($uid)){
+ public static function activeIds($uid = null) {
+ if(is_null($uid)) {
$uid = OCP\USER::getUser();
}
$activeaddressbooks = self::all($uid, true);
@@ -93,7 +93,7 @@ class OC_Contacts_Addressbook{
* @param string $uid
* @return array
*/
- public static function active($uid){
+ public static function active($uid) {
return self::all($uid, true);
}
@@ -112,13 +112,13 @@ class OC_Contacts_Addressbook{
* @param integer $id
* @return associative array or false.
*/
- public static function find($id){
+ public static function find($id) {
try {
$stmt = OCP\DB::prepare( 'SELECT * FROM *PREFIX*contacts_addressbooks WHERE id = ?' );
$result = $stmt->execute(array($id));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(),OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', id: '.$id,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', id: '.$id, OCP\Util::DEBUG);
return false;
}
@@ -129,11 +129,11 @@ class OC_Contacts_Addressbook{
* @brief Adds default address book
* @return $id ID of the newly created addressbook or false on error.
*/
- public static function addDefault($uid = null){
+ public static function addDefault($uid = null) {
if(is_null($uid)) {
$uid = OCP\USER::getUser();
}
- $id = self::add($uid,'default','Default Address Book');
+ $id = self::add($uid, 'Contacts', 'Default Address Book');
if($id !== false) {
self::setActive($id, true);
}
@@ -147,13 +147,13 @@ class OC_Contacts_Addressbook{
* @param string $description
* @return insertid
*/
- public static function add($uid,$name,$description=''){
+ public static function add($uid,$name,$description='') {
try {
$stmt = OCP\DB::prepare( 'SELECT uri FROM *PREFIX*contacts_addressbooks WHERE userid = ? ' );
$result = $stmt->execute(array($uid));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.' exception: '.$e->getMessage(),OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.' uid: '.$uid,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.' exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.' uid: '.$uid, OCP\Util::DEBUG);
return false;
}
$uris = array();
@@ -166,8 +166,8 @@ class OC_Contacts_Addressbook{
$stmt = OCP\DB::prepare( 'INSERT INTO *PREFIX*contacts_addressbooks (userid,displayname,uri,description,ctag) VALUES(?,?,?,?,?)' );
$result = $stmt->execute(array($uid,$name,$uri,$description,1));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(),OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', uid: '.$uid,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', uid: '.$uid, OCP\Util::DEBUG);
return false;
}
@@ -182,16 +182,16 @@ class OC_Contacts_Addressbook{
* @param string $description
* @return insertid or false
*/
- public static function addFromDAVData($principaluri,$uri,$name,$description){
+ public static function addFromDAVData($principaluri,$uri,$name,$description) {
$uid = self::extractUserID($principaluri);
try {
$stmt = OCP\DB::prepare('INSERT INTO *PREFIX*contacts_addressbooks (userid,displayname,uri,description,ctag) VALUES(?,?,?,?,?)');
$result = $stmt->execute(array($uid,$name,$uri,$description,1));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(),OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', uid: '.$uid,OCP\Util::DEBUG);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', uri: '.$uri,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', uid: '.$uid, OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', uri: '.$uri, OCP\Util::DEBUG);
return false;
}
@@ -205,14 +205,14 @@ class OC_Contacts_Addressbook{
* @param string $description
* @return boolean
*/
- public static function edit($id,$name,$description){
+ public static function edit($id,$name,$description) {
// Need these ones for checking uri
$addressbook = self::find($id);
- if(is_null($name)){
+ if(is_null($name)) {
$name = $addressbook['name'];
}
- if(is_null($description)){
+ if(is_null($description)) {
$description = $addressbook['description'];
}
@@ -220,8 +220,8 @@ class OC_Contacts_Addressbook{
$stmt = OCP\DB::prepare('UPDATE *PREFIX*contacts_addressbooks SET displayname=?,description=?, ctag=ctag+1 WHERE id=?');
$result = $stmt->execute(array($name,$description,$id));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(),OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', id: '.$id,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', id: '.$id, OCP\Util::DEBUG);
return false;
}
@@ -234,15 +234,15 @@ class OC_Contacts_Addressbook{
* @param boolean $active
* @return boolean
*/
- public static function setActive($id,$active){
+ public static function setActive($id,$active) {
$sql = 'UPDATE *PREFIX*contacts_addressbooks SET active = ? WHERE id = ?';
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', id: '.$id.', active: '.intval($active),OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', id: '.$id.', active: '.intval($active), OCP\Util::ERROR);
try {
$stmt = OCP\DB::prepare($sql);
$stmt->execute(array(intval($active), $id));
return true;
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception for '.$id.': '.$e->getMessage(),OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception for '.$id.': '.$e->getMessage(), OCP\Util::ERROR);
return false;
}
}
@@ -252,7 +252,7 @@ class OC_Contacts_Addressbook{
* @param integer $id ID of the address book.
* @return boolean
*/
- public static function isActive($id){
+ public static function isActive($id) {
$sql = 'SELECT active FROM *PREFIX*contacts_addressbooks WHERE id = ?';
try {
$stmt = OCP\DB::prepare( $sql );
@@ -260,7 +260,7 @@ class OC_Contacts_Addressbook{
$row = $result->fetchRow();
return (bool)$row['active'];
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(),OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
}
}
@@ -269,13 +269,13 @@ class OC_Contacts_Addressbook{
* @param integer $id
* @return boolean
*/
- public static function delete($id){
+ public static function delete($id) {
self::setActive($id, false);
try {
$stmt = OCP\DB::prepare( 'DELETE FROM *PREFIX*contacts_addressbooks WHERE id = ?' );
$stmt->execute(array($id));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception for '.$id.': '.$e->getMessage(),OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception for '.$id.': '.$e->getMessage(), OCP\Util::ERROR);
return false;
}
@@ -292,7 +292,7 @@ class OC_Contacts_Addressbook{
* @param integer $id
* @return boolean
*/
- public static function touch($id){
+ public static function touch($id) {
$stmt = OCP\DB::prepare( 'UPDATE *PREFIX*contacts_addressbooks SET ctag = ctag + 1 WHERE id = ?' );
$stmt->execute(array($id));
@@ -305,11 +305,11 @@ class OC_Contacts_Addressbook{
* @param array $existing existing addressbook URIs
* @return string new name
*/
- public static function createURI($name,$existing){
- $name = strtolower($name);
+ public static function createURI($name,$existing) {
+ $name = str_replace(' ', '_', strtolower($name));
$newname = $name;
$i = 1;
- while(in_array($newname,$existing)){
+ while(in_array($newname, $existing)) {
$newname = $name.$i;
$i = $i + 1;
}
@@ -320,8 +320,8 @@ class OC_Contacts_Addressbook{
* @brief gets the userid from a principal path
* @return string
*/
- public static function extractUserID($principaluri){
- list($prefix,$userid) = Sabre_DAV_URLUtil::splitPath($principaluri);
+ public static function extractUserID($principaluri) {
+ list($prefix, $userid) = Sabre_DAV_URLUtil::splitPath($principaluri);
return $userid;
}
}
diff --git a/apps/contacts/lib/app.php b/apps/contacts/lib/app.php
index ed2300adae0..80141bb7240 100644
--- a/apps/contacts/lib/app.php
+++ b/apps/contacts/lib/app.php
@@ -43,7 +43,7 @@ class OC_Contacts_App {
$card = OC_Contacts_VCard::find( $id );
if( $card === false ) {
OCP\Util::writeLog('contacts', 'Contact could not be found: '.$id, OCP\Util::ERROR);
- OCP\JSON::error(array('data' => array( 'message' => self::$l10n->t('Contact could not be found.').' '.$id)));
+ OCP\JSON::error(array('data' => array( 'message' => self::$l10n->t('Contact could not be found.').' '.print_r($id, true))));
exit();
}
@@ -63,11 +63,11 @@ class OC_Contacts_App {
if(!is_null($vcard) && !$vcard->__isset('N')) {
$version = OCP\App::getAppVersion('contacts');
if($version >= 5) {
- OCP\Util::writeLog('contacts','OC_Contacts_App::getContactVCard. Deprecated check for missing N field', OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', 'OC_Contacts_App::getContactVCard. Deprecated check for missing N field', OCP\Util::DEBUG);
}
- OCP\Util::writeLog('contacts','getContactVCard, Missing N field', OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', 'getContactVCard, Missing N field', OCP\Util::DEBUG);
if($vcard->__isset('FN')) {
- OCP\Util::writeLog('contacts','getContactVCard, found FN field: '.$vcard->__get('FN'), OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', 'getContactVCard, found FN field: '.$vcard->__get('FN'), OCP\Util::DEBUG);
$n = implode(';', array_reverse(array_slice(explode(' ', $vcard->__get('FN')), 0, 2))).';;;';
$vcard->setString('N', $n);
OC_Contacts_VCard::edit( $id, $vcard);
@@ -205,9 +205,9 @@ class OC_Contacts_App {
foreach($vccontacts as $vccontact) {
$cards[] = $vccontact['carddata'];
}
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', scanning: '.$batchsize.' starting from '.$start,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', scanning: '.$batchsize.' starting from '.$start, OCP\Util::DEBUG);
// only reset on first batch.
- self::getVCategories()->rescan($cards, true, ($start==0?true:false));
+ self::getVCategories()->rescan($cards, true, ($start == 0 ? true : false));
$start += $batchsize;
}
}
diff --git a/apps/contacts/lib/connector_sabre.php b/apps/contacts/lib/connector_sabre.php
index 99b94fc767e..9fcfff08fa8 100644
--- a/apps/contacts/lib/connector_sabre.php
+++ b/apps/contacts/lib/connector_sabre.php
@@ -79,7 +79,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract {
}
}
- OC_Contacts_Addressbook::edit($addressbookid,$name,$description);
+ OC_Contacts_Addressbook::edit($addressbookid, $name, $description);
return true;
@@ -113,7 +113,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract {
}
- OC_Contacts_Addressbook::addFromDAVData($principaluri,$url,$name,$description);
+ OC_Contacts_Addressbook::addFromDAVData($principaluri, $url, $name, $description);
}
/**
@@ -156,7 +156,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract {
* @return array
*/
public function getCard($addressbookid, $carduri) {
- return OC_Contacts_VCard::findWhereDAVDataIs($addressbookid,$carduri);
+ return OC_Contacts_VCard::findWhereDAVDataIs($addressbookid, $carduri);
}
diff --git a/apps/contacts/lib/hooks.php b/apps/contacts/lib/hooks.php
index d91d3c565b5..3344e3d6939 100644
--- a/apps/contacts/lib/hooks.php
+++ b/apps/contacts/lib/hooks.php
@@ -34,12 +34,12 @@
*/
class OC_Contacts_Hooks{
/**
- * @brief Add default Addressbooks of a certain user
- * @param paramters parameters from postDeleteUser-Hook
+ * @brief Add default Addressbook for a certain user
+ * @param paramters parameters from postCreateUser-Hook
* @return array
*/
static public function createUser($parameters) {
- OC_Contacts_Addressbook::addDefault($parameters['uid'],'default','Default Address Book');
+ OC_Contacts_Addressbook::addDefault($parameters['uid']);
return true;
}
@@ -61,8 +61,8 @@ class OC_Contacts_Hooks{
static public function getCalenderSources($parameters) {
$base_url = OCP\Util::linkTo('calendar', 'ajax/events.php').'?calendar_id=';
foreach(OC_Contacts_Addressbook::all(OCP\USER::getUser()) as $addressbook) {
- $parameters['sources'][] =
- array(
+ $parameters['sources'][]
+ = array(
'url' => $base_url.'birthday_'. $addressbook['id'],
'backgroundColor' => '#cccccc',
'borderColor' => '#888',
@@ -91,18 +91,24 @@ class OC_Contacts_Hooks{
$date = new DateTime($birthday);
$vevent = new OC_VObject('VEVENT');
//$vevent->setDateTime('LAST-MODIFIED', new DateTime($vcard->REV));
- $vevent->setDateTime('DTSTART', $date, Sabre_VObject_Element_DateTime::DATE);
+ $vevent->setDateTime('DTSTART', $date,
+ Sabre_VObject_Element_DateTime::DATE);
$vevent->setString('DURATION', 'P1D');
- $vevent->setString('UID', substr(md5(rand().time()),0,10));
+ $vevent->setString('UID', substr(md5(rand().time()), 0, 10));
// DESCRIPTION?
$vevent->setString('RRULE', 'FREQ=YEARLY');
- $title = str_replace('{name}', $vcard->getAsString('FN'), OC_Contacts_App::$l10n->t('{name}\'s Birthday'));
+ $title = str_replace('{name}',
+ $vcard->getAsString('FN'),
+ OC_Contacts_App::$l10n->t('{name}\'s Birthday'));
$parameters['events'][] = array(
'id' => 0,//$card['id'],
'vevent' => $vevent,
'repeating' => true,
'summary' => $title,
- 'calendardata' => "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:ownCloud Contacts " . OCP\App::getAppVersion('contacts') . "\n" . $vevent->serialize() . "END:VCALENDAR"
+ 'calendardata' => "BEGIN:VCALENDAR\nVERSION:2.0\n"
+ . "PRODID:ownCloud Contacts "
+ . OCP\App::getAppVersion('contacts') . "\n"
+ . $vevent->serialize() . "END:VCALENDAR"
);
}
}
diff --git a/apps/contacts/lib/search.php b/apps/contacts/lib/search.php
index 5d9ca97e761..53aa2b48496 100644
--- a/apps/contacts/lib/search.php
+++ b/apps/contacts/lib/search.php
@@ -2,7 +2,7 @@
class OC_Search_Provider_Contacts extends OC_Search_Provider{
function search($query){
$addressbooks = OC_Contacts_Addressbook::all(OCP\USER::getUser(), 1);
- if(count($addressbooks)==0 || !OCP\App::isEnabled('contacts')){
+ if(count($addressbooks)==0 || !OCP\App::isEnabled('contacts')) {
return array();
}
$results=array();
@@ -10,9 +10,9 @@ class OC_Search_Provider_Contacts extends OC_Search_Provider{
foreach($addressbooks as $addressbook){
$vcards = OC_Contacts_VCard::all($addressbook['id']);
foreach($vcards as $vcard){
- if(substr_count(strtolower($vcard['fullname']), strtolower($query)) > 0){
+ if(substr_count(strtolower($vcard['fullname']), strtolower($query)) > 0) {
$link = OCP\Util::linkTo('contacts', 'index.php').'&id='.urlencode($vcard['id']);
- $results[]=new OC_Search_Result($vcard['fullname'],'', $link,(string)$l->t('Contact'));//$name,$text,$link,$type
+ $results[]=new OC_Search_Result($vcard['fullname'], '', $link, (string)$l->t('Contact'));//$name,$text,$link,$type
}
}
}
diff --git a/apps/contacts/lib/vcard.php b/apps/contacts/lib/vcard.php
index e3b65605624..ca171e792fc 100644
--- a/apps/contacts/lib/vcard.php
+++ b/apps/contacts/lib/vcard.php
@@ -63,9 +63,9 @@ class OC_Contacts_VCard{
$stmt = OCP\DB::prepare( $prep );
$result = $stmt->execute($id);
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(),OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', ids: '.join(',', $id),OCP\Util::DEBUG);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.'SQL:'.$prep,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', ids: '.join(',', $id), OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.'SQL:'.$prep, OCP\Util::DEBUG);
return false;
}
} elseif(is_int($id) || is_string($id)) {
@@ -74,12 +74,12 @@ class OC_Contacts_VCard{
$stmt = OCP\DB::prepare( $sql );
$result = $stmt->execute(array($id));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(),OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', ids: '. $id,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', ids: '. $id, OCP\Util::DEBUG);
return false;
}
} else {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.'. Addressbook id(s) argument is empty: '. $id,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.'. Addressbook id(s) argument is empty: '. $id, OCP\Util::DEBUG);
return false;
}
$cards = array();
@@ -102,8 +102,8 @@ class OC_Contacts_VCard{
$stmt = OCP\DB::prepare( 'SELECT * FROM *PREFIX*contacts_cards WHERE id = ?' );
$result = $stmt->execute(array($id));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(),OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', id: '. $id,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', id: '. $id, OCP\Util::DEBUG);
return false;
}
@@ -121,8 +121,8 @@ class OC_Contacts_VCard{
$stmt = OCP\DB::prepare( 'SELECT * FROM *PREFIX*contacts_cards WHERE addressbookid = ? AND uri = ?' );
$result = $stmt->execute(array($aid,$uri));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', aid: '.$aid.' uri'.$uri, OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', aid: '.$aid.' uri'.$uri, OCP\Util::DEBUG);
return false;
}
@@ -177,16 +177,16 @@ class OC_Contacts_VCard{
try {
$result = $stmt->execute(array($aid,$uri));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', aid: '.$aid.' uid'.$uid, OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', aid: '.$aid.' uid'.$uid, OCP\Util::DEBUG);
return false;
}
- if($result->numRows() > 0){
+ if($result->numRows() > 0) {
while(true) {
- $tmpuid = substr(md5(rand().time()),0,10);
+ $tmpuid = substr(md5(rand().time()), 0, 10);
$uri = $tmpuid.'.vcf';
- $result = $stmt->execute(array($aid,$uri));
- if($result->numRows() > 0){
+ $result = $stmt->execute(array($aid, $uri));
+ if($result->numRows() > 0) {
continue;
} else {
$uid = $tmpuid;
@@ -212,7 +212,7 @@ class OC_Contacts_VCard{
// Add version if needed
if($version && $version < '3.0') {
$upgrade = true;
- OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateValuesFromAdd. Updating from version: '.$version,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', 'OC_Contacts_VCard::updateValuesFromAdd. Updating from version: '.$version, OCP\Util::DEBUG);
}
foreach($vcard->children as &$property){
// Decode string properties and remove obsolete properties.
@@ -225,29 +225,29 @@ class OC_Contacts_VCard{
}
// Fix format of type parameters.
if($upgrade && in_array($property->name, $typeprops)) {
- OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateValuesFromAdd. before: '.$property->serialize(),OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', 'OC_Contacts_VCard::updateValuesFromAdd. before: '.$property->serialize(), OCP\Util::DEBUG);
self::formatPropertyTypes($property);
- OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateValuesFromAdd. after: '.$property->serialize(),OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', 'OC_Contacts_VCard::updateValuesFromAdd. after: '.$property->serialize(), OCP\Util::DEBUG);
}
- if($property->name == 'FN'){
+ if($property->name == 'FN') {
$fn = $property->value;
}
- if($property->name == 'N'){
+ if($property->name == 'N') {
$n = $property->value;
}
- if($property->name == 'UID'){
+ if($property->name == 'UID') {
$uid = $property->value;
}
- if($property->name == 'ORG'){
+ if($property->name == 'ORG') {
$org = $property->value;
}
- if($property->name == 'EMAIL' && is_null($email)){ // only use the first email as substitute for missing N or FN.
+ if($property->name == 'EMAIL' && is_null($email)) { // only use the first email as substitute for missing N or FN.
$email = $property->value;
}
}
// Check for missing 'N', 'FN' and 'UID' properties
if(!$fn) {
- if($n && $n != ';;;;'){
+ if($n && $n != ';;;;') {
$fn = join(' ', array_reverse(array_slice(explode(';', $n), 0, 2)));
} elseif($email) {
$fn = $email;
@@ -257,21 +257,21 @@ class OC_Contacts_VCard{
$fn = 'Unknown Name';
}
$vcard->setString('FN', $fn);
- OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateValuesFromAdd. Added missing \'FN\' field: '.$fn,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', 'OC_Contacts_VCard::updateValuesFromAdd. Added missing \'FN\' field: '.$fn, OCP\Util::DEBUG);
}
- if(!$n || $n == ';;;;'){ // Fix missing 'N' field. Ugly hack ahead ;-)
+ if(!$n || $n == ';;;;') { // Fix missing 'N' field. Ugly hack ahead ;-)
$slice = array_reverse(array_slice(explode(' ', $fn), 0, 2)); // Take 2 first name parts of 'FN' and reverse.
if(count($slice) < 2) { // If not enought, add one more...
$slice[] = "";
}
$n = implode(';', $slice).';;;';
$vcard->setString('N', $n);
- OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateValuesFromAdd. Added missing \'N\' field: '.$n,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', 'OC_Contacts_VCard::updateValuesFromAdd. Added missing \'N\' field: '.$n, OCP\Util::DEBUG);
}
if(!$uid) {
$vcard->setUID();
$uid = $vcard->getAsString('UID');
- OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateValuesFromAdd. Added missing \'UID\' field: '.$uid,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', 'OC_Contacts_VCard::updateValuesFromAdd. Added missing \'UID\' field: '.$uid, OCP\Util::DEBUG);
}
if(self::trueUID($aid, $uid)) {
$vcard->setString('UID', $uid);
@@ -288,8 +288,8 @@ class OC_Contacts_VCard{
* @return insertid on success or false.
*/
public static function add($aid, OC_VObject $card, $uri=null, $isnew=false){
- if(is_null($card)){
- OCP\Util::writeLog('contacts','OC_Contacts_VCard::add. No vCard supplied', OCP\Util::ERROR);
+ if(is_null($card)) {
+ OCP\Util::writeLog('contacts', 'OC_Contacts_VCard::add. No vCard supplied', OCP\Util::ERROR);
return null;
};
@@ -298,7 +298,7 @@ class OC_Contacts_VCard{
self::updateValuesFromAdd($aid, $card);
}
- $card->setString('VERSION','3.0');
+ $card->setString('VERSION', '3.0');
// Add product ID is missing.
$prodid = trim($card->getAsString('PRODID'));
if(!$prodid) {
@@ -323,8 +323,8 @@ class OC_Contacts_VCard{
try {
$result = $stmt->execute(array($aid,$fn,$data,$uri,time()));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', aid: '.$aid.' uri'.$uri, OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', aid: '.$aid.' uri'.$uri, OCP\Util::DEBUG);
return false;
}
$newid = OCP\DB::insertid('*PREFIX*contacts_cards');
@@ -355,15 +355,15 @@ class OC_Contacts_VCard{
$now = new DateTime;
foreach($objects as $object) {
$vcard = OC_VObject::parse($object[1]);
- if(!is_null($vcard)){
+ if(!is_null($vcard)) {
$vcard->setString('REV', $now->format(DateTime::W3C));
$data = $vcard->serialize();
try {
$result = $stmt->execute(array($data,time(),$object[0]));
//OCP\Util::writeLog('contacts','OC_Contacts_VCard::updateDataByID, id: '.$object[0].': '.$object[1],OCP\Util::DEBUG);
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(),OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', id: '.$object[0],OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', id: '.$object[0], OCP\Util::DEBUG);
}
}
}
@@ -397,8 +397,8 @@ class OC_Contacts_VCard{
try {
$result = $stmt->execute(array($fn,$data,time(),$id));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', id'.$id, OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', id'.$id, OCP\Util::DEBUG);
return false;
}
@@ -414,11 +414,11 @@ class OC_Contacts_VCard{
* @param string $data vCard file
* @return boolean
*/
- public static function editFromDAVData($aid,$uri,$data){
- $oldcard = self::findWhereDAVDataIs($aid,$uri);
+ public static function editFromDAVData($aid, $uri, $data){
+ $oldcard = self::findWhereDAVDataIs($aid, $uri);
$card = OC_VObject::parse($data);
if(!$card) {
- OCP\Util::writeLog('contacts','OC_Contacts_VCard::editFromDAVData. Unable to parse VCARD, uri: '.$uri,OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', Unable to parse VCARD, uri: '.$uri, OCP\Util::ERROR);
return false;
}
return self::edit($oldcard['id'], $card);
@@ -430,14 +430,13 @@ class OC_Contacts_VCard{
* @return boolean
*/
public static function delete($id){
- // FIXME: Add error checking.
OC_Hook::emit('OC_Contacts_VCard', 'pre_deleteVCard', array('aid' => null, 'id' => $id, 'uri' => null));
$stmt = OCP\DB::prepare( 'DELETE FROM *PREFIX*contacts_cards WHERE id = ?' );
try {
$stmt->execute(array($id));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', id: '.$id, OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', id: '.$id, OCP\Util::DEBUG);
return false;
}
@@ -451,14 +450,13 @@ class OC_Contacts_VCard{
* @return boolean
*/
public static function deleteFromDAVData($aid,$uri){
- // FIXME: Add error checking. Deleting a card gives an Kontact/Akonadi error.
OC_Hook::emit('OC_Contacts_VCard', 'pre_deleteVCard', array('aid' => $aid, 'id' => null, 'uri' => $uri));
$stmt = OCP\DB::prepare( 'DELETE FROM *PREFIX*contacts_cards WHERE addressbookid = ? AND uri=?' );
try {
$stmt->execute(array($aid,$uri));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', aid: '.$aid.' uri: '.$uri, OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', aid: '.$aid.' uri: '.$uri, OCP\Util::DEBUG);
return false;
}
OC_Contacts_Addressbook::touch($aid);
@@ -487,14 +485,14 @@ class OC_Contacts_VCard{
* @return array
*/
public static function unescapeDelimiters($value, $delimiter=';') {
- $array = explode($delimiter,$value);
+ $array = explode($delimiter, $value);
for($i=0;$i<count($array);$i++) {
- if(substr($array[$i],-1,1)=="\\") {
+ if(substr($array[$i], -1, 1)=="\\") {
if(isset($array[$i+1])) {
- $array[$i] = substr($array[$i],0,count($array[$i])-2).$delimiter.$array[$i+1];
+ $array[$i] = substr($array[$i], 0, count($array[$i])-2).$delimiter.$array[$i+1];
unset($array[$i+1]);
} else {
- $array[$i] = substr($array[$i],0,count($array[$i])-2).$delimiter;
+ $array[$i] = substr($array[$i], 0, count($array[$i])-2).$delimiter;
}
$i = $i - 1;
}
@@ -510,12 +508,12 @@ class OC_Contacts_VCard{
*
* look at code ...
*/
- public static function structureContact($object){
+ public static function structureContact($object) {
$details = array();
foreach($object->children as $property){
$temp = self::structureProperty($property);
if(!is_null($temp)) {
- if(array_key_exists($property->name,$details)){
+ if(array_key_exists($property->name, $details)) {
$details[$property->name][] = $temp;
}
else{
@@ -539,10 +537,10 @@ class OC_Contacts_VCard{
* NOTE: $value is not escaped anymore. It shouldn't make any difference
* but we should look out for any problems.
*/
- public static function structureProperty($property){
+ public static function structureProperty($property) {
$value = $property->value;
//$value = htmlspecialchars($value);
- if($property->name == 'ADR' || $property->name == 'N'){
+ if($property->name == 'ADR' || $property->name == 'N') {
$value = self::unescapeDelimiters($value);
} elseif($property->name == 'BDAY') {
if(strpos($value, '-') === false) {
@@ -562,17 +560,17 @@ class OC_Contacts_VCard{
// Faulty entries by kaddressbook
// Actually TYPE=PREF is correct according to RFC 2426
// but this way is more handy in the UI. Tanghus.
- if($parameter->name == 'TYPE' && $parameter->value == 'PREF'){
+ if($parameter->name == 'TYPE' && $parameter->value == 'PREF') {
$parameter->name = 'PREF';
$parameter->value = '1';
}
// NOTE: Apparently Sabre_VObject_Reader can't always deal with value list parameters
// like TYPE=HOME,CELL,VOICE. Tanghus.
- if (in_array($property->name, array('TEL', 'EMAIL')) && $parameter->name == 'TYPE'){
- if (isset($temp['parameters'][$parameter->name])){
+ if (in_array($property->name, array('TEL', 'EMAIL')) && $parameter->name == 'TYPE') {
+ if (isset($temp['parameters'][$parameter->name])) {
$temp['parameters'][$parameter->name][] = $parameter->value;
}
- else{
+ else {
$temp['parameters'][$parameter->name] = array($parameter->value);
}
}
@@ -590,7 +588,7 @@ class OC_Contacts_VCard{
* @return boolean
*
*/
- public static function moveToAddressBook($aid, $id){
+ public static function moveToAddressBook($aid, $id) {
OC_Contacts_App::getAddressbook($aid); // check for user ownership.
if(is_array($id)) {
$id_sql = join(',', array_fill(0, count($id), '?'));
@@ -601,9 +599,9 @@ class OC_Contacts_VCard{
$vals = array_merge((array)$aid, $id);
$result = $stmt->execute($vals);
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(),OCP\Util::ERROR);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', ids: '.join(',', $vals),OCP\Util::DEBUG);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', SQL:'.$prep,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', ids: '.join(',', $vals), OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', SQL:'.$prep, OCP\Util::DEBUG);
return false;
}
} else {
@@ -611,8 +609,8 @@ class OC_Contacts_VCard{
$stmt = OCP\DB::prepare( 'UPDATE *PREFIX*contacts_cards SET addressbookid = ? WHERE id = ?' );
$result = $stmt->execute(array($aid, $id));
} catch(Exception $e) {
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(),OCP\Util::DEBUG);
- OCP\Util::writeLog('contacts',__CLASS__.'::'.__METHOD__.' id: '.$id,OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.', exception: '.$e->getMessage(), OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts', __CLASS__.'::'.__METHOD__.' id: '.$id, OCP\Util::DEBUG);
return false;
}
}
diff --git a/apps/contacts/photo.php b/apps/contacts/photo.php
index 4660d61f618..efdf157cd95 100644
--- a/apps/contacts/photo.php
+++ b/apps/contacts/photo.php
@@ -13,7 +13,7 @@
OCP\User::checkLoggedIn();
OCP\App::checkAppEnabled('contacts');
-function getStandardImage(){
+function getStandardImage() {
//OCP\Response::setExpiresHeader('P10D');
OCP\Response::enableCaching();
OCP\Response::redirect(OCP\Util::imagePath('contacts', 'person_large.png'));
@@ -27,37 +27,39 @@ if(is_null($id)) {
}
if(!extension_loaded('gd') || !function_exists('gd_info')) {
- OCP\Util::writeLog('contacts','photo.php. GD module not installed',OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts',
+ 'photo.php. GD module not installed', OCP\Util::DEBUG);
getStandardImage();
}
$contact = OC_Contacts_App::getContactVCard($id);
$image = new OC_Image();
-if(!$image) {
+if (!$image) {
getStandardImage();
}
// invalid vcard
-if( is_null($contact)) {
- OCP\Util::writeLog('contacts','photo.php. The VCard for ID '.$id.' is not RFC compatible',OCP\Util::ERROR);
+if (is_null($contact)) {
+ OCP\Util::writeLog('contacts',
+ 'photo.php. The VCard for ID ' . $id . ' is not RFC compatible',
+ OCP\Util::ERROR);
} else {
OCP\Response::enableCaching($caching);
OC_Contacts_App::setLastModifiedHeader($contact);
// Photo :-)
- if($image->loadFromBase64($contact->getAsString('PHOTO'))) {
+ if ($image->loadFromBase64($contact->getAsString('PHOTO'))) {
// OK
OCP\Response::setETagHeader(md5($contact->getAsString('PHOTO')));
}
else
// Logo :-/
- if($image->loadFromBase64($contact->getAsString('LOGO'))) {
+ if ($image->loadFromBase64($contact->getAsString('LOGO'))) {
// OK
OCP\Response::setETagHeader(md5($contact->getAsString('LOGO')));
}
if ($image->valid()) {
$max_size = 200;
- if($image->width() > $max_size ||
- $image->height() > $max_size) {
+ if ($image->width() > $max_size || $image->height() > $max_size) {
$image->resize($max_size);
}
}
@@ -65,8 +67,7 @@ if( is_null($contact)) {
if (!$image->valid()) {
// Not found :-(
getStandardImage();
- //$image->loadFromFile('img/person_large.png');
}
header('Content-Type: '.$image->mimeType());
$image->show();
-//echo OC_Contacts_App::$l10n->t('This card does not contain a photo.');
+
diff --git a/apps/contacts/templates/index.php b/apps/contacts/templates/index.php
index 5b49b68e954..b2dde12684c 100644
--- a/apps/contacts/templates/index.php
+++ b/apps/contacts/templates/index.php
@@ -1,3 +1,4 @@
+<div id='notification'></div>
<script type='text/javascript'>
var totalurl = '<?php echo OCP\Util::linkToRemote('carddav'); ?>addressbooks';
var categories = <?php echo json_encode($_['categories']); ?>;
@@ -5,25 +6,69 @@
var lang = '<?php echo OCP\Config::getUserValue(OCP\USER::getUser(), 'core', 'lang', 'en'); ?>';
</script>
<div id="leftcontent">
+ <div class="hidden" id="statusbar"></div>
<div id="contacts">
</div>
+ <div id="uploadprogressbar"></div>
<div id="bottomcontrols">
- <form>
- <button class="svg" id="contacts_newcontact" title="<?php echo $l->t('Add Contact'); ?>"><img class="svg" src="<?php echo OCP\Util::imagePath('contacts', 'contact-new.svg'); ?>" alt="<?php echo $l->t('Add Contact'); ?>" /></button>
+ <form id="import_upload_form" action="<?php echo OCP\Util::linkTo('contacts', 'ajax/uploadimport.php'); ?>" method="post" enctype="multipart/form-data" target="import_upload_target">
+ <button class="svg" id="contacts_newcontact" title="<?php echo $l->t('Add Contact'); ?>"><img class="svg" src="<?php echo OCP\Util::imagePath('contacts', 'contact-new.svg'); ?>" alt="<?php echo $l->t('Add Contact'); ?>" /></button>
+ <span class="svg" id="contacts_import" title="<?php echo $l->t('Import'); ?>">
+ <input class="float" id="import_upload_start" type="file" accept="text/directory,text/vcard,text/x-vcard" name="importfile" />
+ <img class="svg" src="core/img/actions/upload.svg" alt="<?php echo $l->t('Import'); ?>" />
+ </span>
<button class="svg" id="chooseaddressbook" title="<?php echo $l->t('Addressbooks'); ?>"><img class="svg" src="core/img/actions/settings.svg" alt="<?php echo $l->t('Addressbooks'); ?>" /></button>
+ <input type="hidden" name="MAX_FILE_SIZE" value="<?php echo $_['uploadMaxFilesize'] ?>" id="max_upload">
</form>
+ <iframe name="import_upload_target" id='import_upload_target' src=""></iframe>
</div>
</div>
<div id="rightcontent" class="rightcontent" data-id="<?php echo $_['id']; ?>">
<?php
- if($_['has_contacts']){
+ if($_['has_contacts']) {
echo $this->inc('part.contact');
}
else{
echo $this->inc('part.no_contacts');
}
?>
+ <div class="hidden" id="ninjahelp">
+ <a class="close" tabindex="0" role="button">
+ <img class="svg" src="core/img/actions/delete.svg" alt="<?php echo $l->t('Close'); ?>" />
+ </a>
+ <h2><?php echo $l->t('Keyboard shortcuts'); ?></h2>
+ <div class="help-section">
+ <h3><?php echo $l->t('Navigation'); ?></h3>
+ <dl>
+ <dt>j/Down/Space</dt>
+ <dd><?php echo $l->t('Next contact in list'); ?></dd>
+ <dt>k/Up/Shift-Space</dt>
+ <dd><?php echo $l->t('Previous contact in list'); ?></dd>
+ <dt>o/Enter</dt>
+ <dd><?php echo $l->t('Expand/collapse current addressbook'); ?></dd>
+ <dt>n/p</dt>
+ <dd><?php echo $l->t('Next/previous addressbook'); ?></dd>
+ </dl>
+ </div>
+ <div class="help-section">
+ <h3><?php echo $l->t('Actions'); ?></h3>
+ <dl>
+ <dt>r</dt>
+ <dd><?php echo $l->t('Refresh contacts list'); ?></dd>
+ <dt>a</dt>
+ <dd><?php echo $l->t('Add new contact'); ?></dd>
+ <dt>Shift-a</dt>
+ <dd><?php echo $l->t('Add new addressbook'); ?></dd>
+ <dt>Shift-Delete</dt>
+ <dd><?php echo $l->t('Delete current contact'); ?></dd>
+ </dl>
+ </div>
+ </div>
</div>
<!-- Dialogs -->
<div id="dialog_holder"></div>
<!-- End of Dialogs -->
+<menu type="context" id="addressbookmenu">
+ <menuitem label="Delete" icon="core/img/actions/delete.svg" onclick="alert('Really? ' + $(this).attr('data-id'))"></menuitem>
+ <menuitem label="Rename" icon="core/img/actions/rename.svg" onclick="alert('Can\'t do that')"></menuitem>
+</menu>
diff --git a/apps/contacts/templates/part.chooseaddressbook.php b/apps/contacts/templates/part.chooseaddressbook.php
index a0ec053ab91..caed67736c5 100644
--- a/apps/contacts/templates/part.chooseaddressbook.php
+++ b/apps/contacts/templates/part.chooseaddressbook.php
@@ -14,7 +14,6 @@ for($i = 0; $i < count($option_addressbooks); $i++){
<tr>
<td colspan="5" style="padding: 0.5em;">
<a class="button" href="#" onclick="Contacts.UI.Addressbooks.newAddressbook(this);"><?php echo $l->t('New Address Book') ?></a>
- <a class="button" href="#" onclick="Contacts.UI.Addressbooks.importAddressbook(this);"><?php echo $l->t('Import from VCF') ?></a>
</td>
</tr>
<tr>
diff --git a/apps/contacts/templates/part.cropphoto.php b/apps/contacts/templates/part.cropphoto.php
index 6d7b1e44777..3f5817622b2 100644
--- a/apps/contacts/templates/part.cropphoto.php
+++ b/apps/contacts/templates/part.cropphoto.php
@@ -2,7 +2,6 @@
$id = $_['id'];
$tmpkey = $_['tmpkey'];
$requesttoken = $_['requesttoken'];
-OCP\Util::writeLog('contacts','templates/part.cropphoto.php: tmpkey: '.$tmpkey, OCP\Util::DEBUG);
?>
<script type="text/javascript">
jQuery(function($) {
diff --git a/apps/contacts/templates/part.edit_categories_dialog.php b/apps/contacts/templates/part.edit_categories_dialog.php
deleted file mode 100644
index 8997fa586bd..00000000000
--- a/apps/contacts/templates/part.edit_categories_dialog.php
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-$categories = isset($_['categories'])?$_['categories']:array();
-?>
-<div id="edit_categories_dialog" title="<?php echo $l->t('Edit categories'); ?>">
-<!-- ?php print_r($types); ? -->
- <form method="post" id="categoryform">
- <div class="scrollarea">
- <ul id="categorylist">
- <?php foreach($categories as $category) { ?>
- <li><input type="checkbox" name="categories[]" value="<?php echo $category; ?>" /><?php echo $category; ?></li>
- <?php } ?>
- </ul>
- </div>
- <div class="bottombuttons"><input type="text" id="category_addinput" name="category" /><button id="category_addbutton" disabled="disabled"><?php echo $l->t('Add'); ?></button></div>
- </form>
-</div>
diff --git a/apps/contacts/templates/part.importaddressbook.php b/apps/contacts/templates/part.importaddressbook.php
deleted file mode 100644
index 8ceb5f3538b..00000000000
--- a/apps/contacts/templates/part.importaddressbook.php
+++ /dev/null
@@ -1,38 +0,0 @@
-<?php
-/**
- * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net>
- * This file is licensed under the Affero General Public License version 3 or
- * later.
- * See the COPYING-README file.
- */
-?>
-<td id="importaddressbook_dialog" colspan="6">
-<table>
-<tr>
- <th><?php echo $l->t('Select address book to import to:') ?></th>
- <td>
- <form id="import_upload_form" action="<?php echo OCP\Util::linkTo('contacts', 'ajax/uploadimport.php'); ?>" method="post" enctype="multipart/form-data" target="import_upload_target">
- <input type="hidden" name="requesttoken" value="<?php echo $_['requesttoken'] ?>">
- <select id="book" name="book" class="float">
- <?php
- $contacts_options = OC_Contacts_Addressbook::all(OCP\USER::getUser());
- echo OCP\html_select_options($contacts_options, $contacts_options[0]['id'], array('value'=>'id', 'label'=>'displayname'));
- ?>
- </select>
- <span id="import_drop_target" class="droptarget float"><?php echo $l->t("Drop a VCF file<br />to import contacts."); ?> (Max. <?php echo $_['uploadMaxHumanFilesize']; ?>)</span>
- <a class="svg upload float" title="<?php echo $l->t('Select from HD'); ?>">
- <input class="float" id="import_upload_start" type="file" accept="text/*" name="importfile" /></a>
- <input type="hidden" name="MAX_FILE_SIZE" value="<?php echo $_['uploadMaxFilesize'] ?>" id="max_upload">
- </form>
- </td>
-</tr>
-</table>
-
-<input id="close_button" style="float: left;" type="button" onclick="Contacts.UI.Addressbooks.cancel(this);" value="<?php echo $l->t("Cancel"); ?>">
-<iframe name="import_upload_target" id='import_upload_target' src=""></iframe>
-</td>
-<script type="text/javascript">
-$(document).ready(function(){
- Contacts.UI.Addressbooks.loadImportHandlers();
-});
-</script> \ No newline at end of file
diff --git a/apps/contacts/templates/part.selectaddressbook.php b/apps/contacts/templates/part.selectaddressbook.php
new file mode 100644
index 00000000000..c54ddaf2e67
--- /dev/null
+++ b/apps/contacts/templates/part.selectaddressbook.php
@@ -0,0 +1,27 @@
+<div id="selectaddressbook_dialog" title="<?php echo $l->t("Select Address Books"); ?>">
+<form>
+<table style="width: 100%">
+ <?php foreach($_['addressbooks'] as $idx => $addressbook) { ?>
+ <tr>
+ <td>
+ <input id="book_<?php echo $addressbook['id']; ?>" name="book" type="radio" value="<?php echo $addressbook['id']; ?>" <?php echo ($idx==0?'checked="checked"':'')?>>
+ </td>
+ <td>
+ <label for="book_<?php echo $addressbook['id']; ?>"><?php echo $addressbook['displayname']; ?></label>
+ </td>
+ <td><?php echo $addressbook['description']; ?></td>
+ </tr>
+ <?php } ?>
+ <tr>
+ <td>
+ <input id="book_new" name="book" type="radio" value="new">
+ </td>
+ <th>
+ <input type="text" class="name" name="displayname" placeholder="<?php echo $l->t("Enter name"); ?>" />
+ </th>
+ <td><input type="text" class="desc" name="description" placeholder="<?php echo $l->t("Enter description"); ?>" /></td>
+ </tr>
+</table>
+</form>
+</div>
+
diff --git a/apps/contacts/thumbnail.php b/apps/contacts/thumbnail.php
index a69e9c74f72..6deb5ca379e 100644
--- a/apps/contacts/thumbnail.php
+++ b/apps/contacts/thumbnail.php
@@ -25,14 +25,15 @@ OCP\JSON::checkLoggedIn();
OCP\App::checkAppEnabled('contacts');
session_write_close();
-function getStandardImage(){
+function getStandardImage() {
//OCP\Response::setExpiresHeader('P10D');
OCP\Response::enableCaching();
OCP\Response::redirect(OCP\Util::imagePath('contacts', 'person.png'));
}
if(!extension_loaded('gd') || !function_exists('gd_info')) {
- OCP\Util::writeLog('contacts','thumbnail.php. GD module not installed',OCP\Util::DEBUG);
+ OCP\Util::writeLog('contacts',
+ 'thumbnail.php. GD module not installed', OCP\Util::DEBUG);
getStandardImage();
exit();
}
@@ -43,8 +44,10 @@ $caching = isset($_GET['refresh']) ? 0 : null;
$contact = OC_Contacts_App::getContactVCard($id);
// invalid vcard
-if(is_null($contact)){
- OCP\Util::writeLog('contacts','thumbnail.php. The VCard for ID '.$id.' is not RFC compatible',OCP\Util::ERROR);
+if(is_null($contact)) {
+ OCP\Util::writeLog('contacts',
+ 'thumbnail.php. The VCard for ID ' . $id . ' is not RFC compatible',
+ OCP\Util::ERROR);
getStandardImage();
exit();
}
@@ -64,16 +67,24 @@ if($photo) {
if($image->show()) {
exit();
} else {
- OCP\Util::writeLog('contacts','thumbnail.php. Couldn\'t display thumbnail for ID '.$id,OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts',
+ 'thumbnail.php. Couldn\'t display thumbnail for ID ' . $id,
+ OCP\Util::ERROR);
}
} else {
- OCP\Util::writeLog('contacts','thumbnail.php. Couldn\'t resize thumbnail for ID '.$id,OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts',
+ 'thumbnail.php. Couldn\'t resize thumbnail for ID ' . $id,
+ OCP\Util::ERROR);
}
}else{
- OCP\Util::writeLog('contacts','thumbnail.php. Couldn\'t crop thumbnail for ID '.$id,OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts',
+ 'thumbnail.php. Couldn\'t crop thumbnail for ID ' . $id,
+ OCP\Util::ERROR);
}
} else {
- OCP\Util::writeLog('contacts','thumbnail.php. Couldn\'t load image string for ID '.$id,OCP\Util::ERROR);
+ OCP\Util::writeLog('contacts',
+ 'thumbnail.php. Couldn\'t load image string for ID ' . $id,
+ OCP\Util::ERROR);
}
}
getStandardImage();
diff --git a/apps/contacts/tmpphoto.php b/apps/contacts/tmpphoto.php
index 5fde8de9977..156d5c80308 100644
--- a/apps/contacts/tmpphoto.php
+++ b/apps/contacts/tmpphoto.php
@@ -24,7 +24,7 @@ $tmpkey = $_GET['tmpkey'];
$maxsize = isset($_GET['maxsize']) ? $_GET['maxsize'] : -1;
header("Cache-Control: no-cache, no-store, must-revalidate");
-OCP\Util::writeLog('contacts','tmpphoto.php: tmpkey: '.$tmpkey, OCP\Util::DEBUG);
+OCP\Util::writeLog('contacts', 'tmpphoto.php: tmpkey: '.$tmpkey, OCP\Util::DEBUG);
$image = new OC_Image();
$image->loadFromData(OC_Cache::get($tmpkey));
diff --git a/apps/files/ajax/newfile.php b/apps/files/ajax/newfile.php
index 7236deb65c9..cc9208ad08f 100644
--- a/apps/files/ajax/newfile.php
+++ b/apps/files/ajax/newfile.php
@@ -1,16 +1,25 @@
<?php
// Init owncloud
+global $eventSource;
+if(!OC_User::isLoggedIn()){
+ exit;
+}
-OCP\JSON::checkLoggedIn();
-OCP\JSON::callCheck();
+session_write_close();
// Get the params
-$dir = isset( $_POST['dir'] ) ? stripslashes($_POST['dir']) : '';
-$filename = isset( $_POST['filename'] ) ? stripslashes($_POST['filename']) : '';
-$content = isset( $_POST['content'] ) ? $_POST['content'] : '';
-$source = isset( $_POST['source'] ) ? stripslashes($_POST['source']) : '';
+$dir = isset( $_REQUEST['dir'] ) ? stripslashes($_REQUEST['dir']) : '';
+$filename = isset( $_REQUEST['filename'] ) ? stripslashes($_REQUEST['filename']) : '';
+$content = isset( $_REQUEST['content'] ) ? $_REQUEST['content'] : '';
+$source = isset( $_REQUEST['source'] ) ? stripslashes($_REQUEST['source']) : '';
+
+if($source){
+ $eventSource=new OC_EventSource();
+}else{
+ OC_JSON::callCheck();
+}
if($filename == '') {
OCP\JSON::error(array("data" => array( "message" => "Empty Filename" )));
@@ -21,22 +30,49 @@ if(strpos($filename,'/')!==false){
exit();
}
+function progress($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max){
+ static $filesize = 0;
+ static $lastsize = 0;
+ global $eventSource;
+
+ switch($notification_code) {
+ case STREAM_NOTIFY_FILE_SIZE_IS:
+ $filesize = $bytes_max;
+ break;
+
+ case STREAM_NOTIFY_PROGRESS:
+ if ($bytes_transferred > 0) {
+ if (!isset($filesize)) {
+ } else {
+ $progress = (int)(($bytes_transferred/$filesize)*100);
+ if($progress>$lastsize){//limit the number or messages send
+ $eventSource->send('progress',$progress);
+ }
+ $lastsize=$progress;
+ }
+ }
+ break;
+ }
+}
+
if($source){
if(substr($source,0,8)!='https://' and substr($source,0,7)!='http://'){
OCP\JSON::error(array("data" => array( "message" => "Not a valid source" )));
exit();
}
- $sourceStream=fopen($source,'rb');
+
+ $ctx = stream_context_create(null, array('notification' =>'progress'));
+ $sourceStream=fopen($source,'rb', false, $ctx);
$target=$dir.'/'.$filename;
$result=OC_Filesystem::file_put_contents($target,$sourceStream);
if($result){
$mime=OC_Filesystem::getMimetype($target);
- OCP\JSON::success(array("data" => array('mime'=>$mime)));
- exit();
+ $eventSource->send('success',$mime);
}else{
- OCP\JSON::error(array("data" => array( "message" => "Error while downloading ".$source. ' to '.$target )));
- exit();
+ $eventSource->send('error',"Error while downloading ".$source. ' to '.$target);
}
+ $eventSource->close();
+ exit();
}else{
if($content){
if(OC_Filesystem::file_put_contents($dir.'/'.$filename,$content)){
diff --git a/apps/files/ajax/scan.php b/apps/files/ajax/scan.php
index 6fcf97688c2..eef38858516 100644
--- a/apps/files/ajax/scan.php
+++ b/apps/files/ajax/scan.php
@@ -16,6 +16,11 @@ session_write_close();
if($force or !OC_FileCache::inCache('')){
if(!$checkOnly){
OCP\DB::beginTransaction();
+
+ if(OC_Cache::isFast()){
+ OC_Cache::clear('fileid/'); //make sure the old fileid's don't mess things up
+ }
+
OC_FileCache::scan($dir,$eventSource);
OC_FileCache::clean();
OCP\DB::commit();
diff --git a/apps/files/appinfo/update.php b/apps/files/appinfo/update.php
index f9953ba4de5..5514aed197f 100644
--- a/apps/files/appinfo/update.php
+++ b/apps/files/appinfo/update.php
@@ -1,5 +1,16 @@
<?php
+// fix webdav properties, remove namespace information between curly bracket (update from OC4 to OC5)
+$installedVersion=OCP\Config::getAppValue('files', 'installed_version');
+if (version_compare($installedVersion, '1.1.4', '<')) {
+ $query = OC_DB::prepare( "SELECT propertyname, propertypath, userid FROM `*PREFIX*properties`" );
+ $result = $query->execute();
+ while( $row = $result->fetchRow()){
+ $query = OC_DB::prepare( 'UPDATE *PREFIX*properties SET propertyname = ? WHERE userid = ? AND propertypath = ?' );
+ $query->execute( array( preg_replace("/^{.*}/", "", $row["propertyname"]),$row["userid"], $row["propertypath"] ));
+ }
+}
+
//update from OC 3
//try to remove remaining files.
diff --git a/apps/files/appinfo/version b/apps/files/appinfo/version
index 8cfbc905b39..1b87bcd0b09 100644
--- a/apps/files/appinfo/version
+++ b/apps/files/appinfo/version
@@ -1 +1 @@
-1.1.1 \ No newline at end of file
+1.1.4 \ No newline at end of file
diff --git a/apps/files/js/files.js b/apps/files/js/files.js
index 2f06f15f896..daa250c9bd4 100644
--- a/apps/files/js/files.js
+++ b/apps/files/js/files.js
@@ -506,23 +506,27 @@ $(document).ready(function() {
localName=(localName.match(/:\/\/(.[^/]+)/)[1]).replace('www.','');
}
localName = getUniqueName(localName);
- $.post(
- OC.filePath('files','ajax','newfile.php'),
- {dir:$('#dir').val(),source:name,filename:localName},
- function(result){
- if(result.status == 'success'){
- var date=new Date();
- FileList.addFile(localName,0,date);
- var tr=$('tr').filterAttr('data-file',localName);
- tr.data('mime',result.data.mime);
- getMimeIcon(result.data.mime,function(path){
- tr.find('td.filename').attr('style','background-image:url('+path+')');
- });
- }else{
- OC.dialogs.alert(result.data.message, 'Error');
- }
- }
- );
+ $('#uploadprogressbar').progressbar({value:0});
+ $('#uploadprogressbar').fadeIn();
+
+ var eventSource=new OC.EventSource(OC.filePath('files','ajax','newfile.php'),{dir:$('#dir').val(),source:name,filename:localName});
+ eventSource.listen('progress',function(progress){
+ $('#uploadprogressbar').progressbar('value',progress);
+ });
+ eventSource.listen('success',function(mime){
+ $('#uploadprogressbar').fadeOut();
+ var date=new Date();
+ FileList.addFile(localName,0,date);
+ var tr=$('tr').filterAttr('data-file',localName);
+ tr.data('mime',mime);
+ getMimeIcon(mime,function(path){
+ tr.find('td.filename').attr('style','background-image:url('+path+')');
+ });
+ });
+ eventSource.listen('error',function(error){
+ $('#uploadprogressbar').fadeOut();
+ alert(error);
+ });
break;
}
var li=$(this).parent();
diff --git a/apps/files/share.php b/apps/files/share.php
new file mode 100755
index 00000000000..4f7c1f55fcf
--- /dev/null
+++ b/apps/files/share.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+* ownCloud
+*
+* @author Michael Gapczynski
+* @copyright 2012 Michael Gapczynski mtgap@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/>.
+*/
+
+class OC_Share_Backend_Files extends OCP\Share_Backend {
+
+ public function getSource($item, $uid) {
+ return $item;
+ }
+}
+
+class OC_Share_Backend_Folders extends OC_Share_Backend_Files {
+
+ public function share($
+ public function getDirectoryContent($folder) {
+
+ }
+}
+
+?> \ No newline at end of file
diff --git a/apps/files_encryption/lib/cryptstream.php b/apps/files_encryption/lib/cryptstream.php
index e0020537563..46471911d94 100644
--- a/apps/files_encryption/lib/cryptstream.php
+++ b/apps/files_encryption/lib/cryptstream.php
@@ -31,9 +31,7 @@ class OC_CryptStream{
public static $sourceStreams=array();
private $source;
private $path;
- private $readBuffer;//for streams that dont support seeking
private $meta=array();//header/meta for source stream
- private $count;
private $writeCache;
private $size;
private static $rootView;
@@ -100,7 +98,6 @@ class OC_CryptStream{
public function stream_write($data){
$length=strlen($data);
- $written=0;
$currentPos=ftell($this->source);
if($this->writeCache){
$data=$this->writeCache.$data;
diff --git a/apps/files_external/lib/dropbox.php b/apps/files_external/lib/dropbox.php
index 5d726feddc5..15446ff0bc3 100755
--- a/apps/files_external/lib/dropbox.php
+++ b/apps/files_external/lib/dropbox.php
@@ -208,7 +208,7 @@ class OC_Filestorage_Dropbox extends OC_Filestorage_Common {
if (isset(self::$tempFiles[$tmpFile])) {
$handle = fopen($tmpFile, 'r');
try {
- $response = $this->dropbox->putFile(self::$tempFiles[$tmpFile], $handle);
+ $this->dropbox->putFile(self::$tempFiles[$tmpFile], $handle);
unlink($tmpFile);
} catch (Exception $exception) {
diff --git a/apps/files_external/lib/dropboxtest.php b/apps/files_external/lib/dropboxtest.php
new file mode 100644
index 00000000000..686549e16b8
--- /dev/null
+++ b/apps/files_external/lib/dropboxtest.php
@@ -0,0 +1,71 @@
+<?php
+
+require_once 'dropbox.php';
+// $oauth = new Dropbox_OAuth_Curl('526ar3qlrtzmv65', '3bbn0wo5lzgpjty');
+$dropbox = new OC_Filestorage_Dropbox(array('app_key' => '526ar3qlrtzmv65', 'app_secret' => '3bbn0wo5lzgpjty', 'token' => 'a3ben02jb1y538a', 'token_secret' => 'x60h3fsky21r1b0'));
+$dropbox->rename('/652072main_2012-2897_full (1).jpg', '/test.jpg');
+// $dropbox->test();
+// print_r($dropbox->mkdir('Again'));
+
+// GET&https%3A%2F%2Fapi.dropbox.com%2F1%2Fmetadata%2Fdropbox%2FownCloud&list%3D0%26
+
+// uzpi8oo2rbax1po
+// th9uoso3xxny3ca
+//Step 3: Acquiring access tokens Array ( [token] => 37my637p88ng967 [token_secret] => t49fmgp3omucnnr )
+//The user is authenticated You should really save the oauth tokens somewhere, so the first steps will no longer be needed Array ( [token] => 37my637p88ng967 [token_secret] => t49fmgp3omucnnr )
+
+// For convenience, definitely not required
+// header('Content-Type: text/plain');
+
+// // We need to start a session
+// session_start();
+
+// There are multiple steps in this workflow, we keep a 'state number' here
+// if (isset($_SESSION['state'])) {
+// $state = 2;
+// } else {
+// $state = 1;
+// }
+//
+// switch($state) {
+//
+// /* In this phase we grab the initial request tokens
+// and redirect the user to the 'authorize' page hosted
+// on dropbox */
+// case 1 :
+// echo "Step 1: Acquire request tokens\n";
+// $tokens = $oauth->getRequestToken();
+// print_r($tokens);
+//
+// // Note that if you want the user to automatically redirect back, you can
+// // add the 'callback' argument to getAuthorizeUrl.
+// echo "Step 2: You must now redirect the user to:\n";
+// echo $oauth->getAuthorizeUrl() . "\n";
+// $_SESSION['state'] = 2;
+// $_SESSION['oauth_tokens'] = $tokens;
+// die();
+//
+// /* In this phase, the user just came back from authorizing
+// and we're going to fetch the real access tokens */
+// case 2 :
+// echo "Step 3: Acquiring access tokens\n";
+// $oauth->setToken($_SESSION['oauth_tokens']);
+// $tokens = $oauth->getAccessToken();
+// print_r($tokens);
+// $_SESSION['state'] = 3;
+// $_SESSION['oauth_tokens'] = $tokens;
+// // There is no break here, intentional
+//
+// /* This part gets called if the authentication process
+// already succeeded. We can use our stored tokens and the api
+// should work. Store these tokens somewhere, like a database */
+// case 3 :
+// echo "The user is authenticated\n";
+// echo "You should really save the oauth tokens somewhere, so the first steps will no longer be needed\n";
+// print_r($_SESSION['oauth_tokens']);
+// $oauth->setToken($_SESSION['oauth_tokens']);
+// break;
+//
+// }
+
+?> \ No newline at end of file
diff --git a/apps/files_external/lib/google.php b/apps/files_external/lib/google.php
index 1346d899269..02c52ffe04c 100644
--- a/apps/files_external/lib/google.php
+++ b/apps/files_external/lib/google.php
@@ -178,7 +178,7 @@ class OC_Filestorage_Google extends OC_Filestorage_Common {
if ($collection == '/' || $collection == '\.' || $collection == '.') {
$uri = 'https://docs.google.com/feeds/default/private/full';
// Get parent content link
- } else if ($dom = $this->getResource(basename($dir))) {
+ } else if ($dom = $this->getResource(basename($collection))) {
$uri = $dom->getElementsByTagName('content')->item(0)->getAttribute('src');
}
if (isset($uri)) {
@@ -341,7 +341,7 @@ class OC_Filestorage_Google extends OC_Filestorage_Common {
break;
}
}
- $title = basename($path);
+ $title = basename($path2);
// Construct post data
$postData = '<?xml version="1.0" encoding="UTF-8"?>';
$postData .= '<entry xmlns="http://www.w3.org/2005/Atom" xmlns:docs="http://schemas.google.com/docs/2007" xmlns:gd="http://schemas.google.com/g/2005" gd:etag='.$etag.'>';
@@ -352,13 +352,13 @@ class OC_Filestorage_Google extends OC_Filestorage_Common {
} else {
// Move to different collection
if ($collectionEntry = $this->getResource($collection)) {
- $feedUri = $colelctionEntry->getElementsByTagName('content')->item(0)->getAttribute('src');
+ $feedUri = $collectionEntry->getElementsByTagName('content')->item(0)->getAttribute('src');
// Construct post data
$postData = '<?xml version="1.0" encoding="UTF-8"?>';
$postData .= '<entry xmlns="http://www.w3.org/2005/Atom">';
$postData .= '<id>'.$entry->getElementsByTagName('id')->item(0).'</id>';
$postData .= '</entry>';
- $this->sendRequest($uri, 'POST', $postData);
+ $this->sendRequest($feedUri, 'POST', $postData);
return true;
}
}
@@ -424,7 +424,6 @@ class OC_Filestorage_Google extends OC_Filestorage_Common {
}
}
if (!isset($uploadUri) && $entry) {
- $etag = $entry->getAttribute('gd:etag');
$links = $entry->getElementsByTagName('link');
foreach ($links as $link) {
if ($link->getAttribute('rel') == 'http://schemas.google.com/g/2005#resumable-create-media') {
diff --git a/apps/files_external/lib/googletest.php b/apps/files_external/lib/googletest.php
new file mode 100644
index 00000000000..a80af6a0978
--- /dev/null
+++ b/apps/files_external/lib/googletest.php
@@ -0,0 +1,8 @@
+<?php
+
+require_once 'google.php';
+
+// $drive = new OC_Filestorage_Google(array('token' => '4/7nZMlRLlAEeXdY0AeH-eHNCL0YaK', 'token_secret' => 'NqO5VMGUVkwFtOYqHsex4257'));
+// $drive = new OC_Filestorage_Google(array('token' => '1/4Xo84YtTxL2MkQst-Ti3nqF1Isy70NUHDRk-BwsLMf4', 'token_secret' => 'jYnyJ_4-ITNxlX9f9RDNoRW-'));
+// var_export($drive->getFeed('https://docs.google.com/feeds/metadata/default', 'GET'));
+
diff --git a/apps/files_external/lib/smb.php b/apps/files_external/lib/smb.php
index 5e34deb2337..8a5e993b1d0 100644
--- a/apps/files_external/lib/smb.php
+++ b/apps/files_external/lib/smb.php
@@ -15,8 +15,6 @@ class OC_FileStorage_SMB extends OC_FileStorage_StreamWrapper{
private $root;
private $share;
- private static $tempFiles=array();
-
public function __construct($params){
$this->host=$params['host'];
$this->user=$params['user'];
diff --git a/apps/files_external/lib/webdav.php b/apps/files_external/lib/webdav.php
index 675fb9518b1..e3f73c5c0a7 100644
--- a/apps/files_external/lib/webdav.php
+++ b/apps/files_external/lib/webdav.php
@@ -77,16 +77,11 @@ class OC_FileStorage_DAV extends OC_Filestorage_Common{
$path=$this->cleanPath($path);
try{
$response=$this->client->propfind($path, array(),1);
- $stripLength=strlen($this->root)+strlen($path);
$id=md5('webdav'.$this->root.$path);
OC_FakeDirStream::$dirs[$id]=array();
- $skip = true;
- foreach($response as $file=>$data){
- // Skip the first file, because it is the current directory
- if ($skip) {
- $skip = false;
- continue;
- }
+ $files=array_keys($response);
+ array_shift($files);//the first entry is the current directory
+ foreach($files as $file){
$file = urldecode(basename($file));
OC_FakeDirStream::$dirs[$id][]=$file;
}
@@ -120,7 +115,7 @@ class OC_FileStorage_DAV extends OC_Filestorage_Common{
public function file_exists($path){
$path=$this->cleanPath($path);
try{
- $response=$this->client->propfind($path, array('{DAV:}resourcetype'));
+ $this->client->propfind($path, array('{DAV:}resourcetype'));
return true;//no 404 exception
}catch(Exception $e){
return false;
@@ -201,7 +196,7 @@ class OC_FileStorage_DAV extends OC_Filestorage_Common{
$mtime=time();
}
$path=$this->cleanPath($path);
- $this->client->proppatch($path, array('{DAV:}lastmodified' => $mtime,));
+ $this->client->proppatch($path, array('{DAV:}lastmodified' => $mtime));
}
public function getFile($path,$target){
diff --git a/apps/files_external/tests/ftp.php b/apps/files_external/tests/ftp.php
index 68481b4e66b..97796bca128 100644
--- a/apps/files_external/tests/ftp.php
+++ b/apps/files_external/tests/ftp.php
@@ -13,7 +13,6 @@ if(!is_array($config) or !isset($config['ftp']) or !$config['ftp']['run']){
}else{
class Test_Filestorage_FTP extends Test_FileStorage {
private $config;
- private $id;
public function setUp(){
$id=uniqid();
diff --git a/apps/files_external/tests/google.php b/apps/files_external/tests/google.php
index 08116f0e748..806db5a6aaa 100644
--- a/apps/files_external/tests/google.php
+++ b/apps/files_external/tests/google.php
@@ -28,7 +28,6 @@ if(!is_array($config) or !isset($config['google']) or !$config['google']['run'])
class Test_Filestorage_Google extends Test_FileStorage {
private $config;
- private $id;
public function setUp(){
$id=uniqid();
diff --git a/apps/files_external/tests/smb.php b/apps/files_external/tests/smb.php
index e1495b7480d..001ef842276 100644
--- a/apps/files_external/tests/smb.php
+++ b/apps/files_external/tests/smb.php
@@ -14,7 +14,6 @@ if(!is_array($config) or !isset($config['smb']) or !$config['smb']['run']){
}else{
class Test_Filestorage_SMB extends Test_FileStorage {
private $config;
- private $id;
public function setUp(){
$id=uniqid();
diff --git a/apps/files_external/tests/swift.php b/apps/files_external/tests/swift.php
index f0bde6ed605..1520c9473d3 100644
--- a/apps/files_external/tests/swift.php
+++ b/apps/files_external/tests/swift.php
@@ -13,7 +13,6 @@ if(!is_array($config) or !isset($config['swift']) or !$config['swift']['run']){
}else{
class Test_Filestorage_SWIFT extends Test_FileStorage {
private $config;
- private $id;
public function setUp(){
$id=uniqid();
diff --git a/apps/files_external/tests/test.php b/apps/files_external/tests/test.php
new file mode 100644
index 00000000000..bd24404f3b9
--- /dev/null
+++ b/apps/files_external/tests/test.php
@@ -0,0 +1,7 @@
+<?php
+require_once 'files_external/lib/config.php';
+echo "<pre>";
+print_r(OC_Mount_Config::getSystemMountPoints());
+echo "</pre>";
+// OC_Mount_Config::addMountPoint('Photos', 'OC_Filestorage_SWIFT', array('host' => 'gapinthecloud.com', 'user' => 'Gap', 'token' => '23423afdasFJEW22', 'secure' => 'true', 'root' => ''), OC_Mount_Config::MOUNT_TYPE_GROUP, 'admin', false);
+?>
diff --git a/apps/files_external/tests/webdav.php b/apps/files_external/tests/webdav.php
index 144659819b6..14abbef2cbf 100644
--- a/apps/files_external/tests/webdav.php
+++ b/apps/files_external/tests/webdav.php
@@ -13,7 +13,6 @@ if(!is_array($config) or !isset($config['webdav']) or !$config['webdav']['run'])
}else{
class Test_Filestorage_DAV extends Test_FileStorage {
private $config;
- private $id;
public function setUp(){
$id=uniqid();
diff --git a/apps/files_sharing/appinfo/version b/apps/files_sharing/appinfo/version
index 7dff5b89211..f4778493c50 100644
--- a/apps/files_sharing/appinfo/version
+++ b/apps/files_sharing/appinfo/version
@@ -1 +1 @@
-0.2.1 \ No newline at end of file
+0.2.2 \ No newline at end of file
diff --git a/apps/files_sharing/sharedstorage.php b/apps/files_sharing/sharedstorage.php
index 38d5f10e286..df884834cae 100644
--- a/apps/files_sharing/sharedstorage.php
+++ b/apps/files_sharing/sharedstorage.php
@@ -295,9 +295,6 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common {
OCP\Util::emitHook('OC_Filestorage_Shared', 'file_put_contents', $info);
$storage = OC_Filesystem::getStorage($source);
$result = $storage->file_put_contents($this->getInternalPath($source), $data);
- if ($result) {
- $this->clearFolderSizeCache($path);
- }
return $result;
}
}
@@ -341,9 +338,9 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common {
$target = $this->fopen($path2, 'w');
return OC_Helper::streamCopy($source, $target);
}
- return false;
+ return true;
}
-
+
public function fopen($path, $mode) {
if ($source = $this->getSourcePath($path)) {
$info = array(
@@ -369,7 +366,7 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common {
return false;
}
- public function hash($type, $path, $raw) {
+ public function hash($type, $path, $raw = false) {
if ($source = $this->getSourcePath($path)) {
$storage = OC_Filesystem::getStorage($source);
return $storage->hash($type, $this->getInternalPath($source), $raw);
@@ -413,4 +410,4 @@ class OC_Filestorage_Shared extends OC_Filestorage_Common {
//TODO
return false;
}
-} \ No newline at end of file
+}
diff --git a/apps/files_sharing/sharetest.php b/apps/files_sharing/sharetest.php
new file mode 100755
index 00000000000..bcff10ca0ec
--- /dev/null
+++ b/apps/files_sharing/sharetest.php
@@ -0,0 +1,7 @@
+<?php
+
+
+OCP\Share::setPermissions('file', '/01 - Genesis.mp3', OCP\Share::SHARE_TYPE_USER, 'Genie2', OCP\Share::PERMISSION_READ | OCP\Share::PERMISSION_UPDATE);
+
+
+?> \ No newline at end of file
diff --git a/apps/files_sharing/templates/get.php b/apps/files_sharing/templates/get.php
new file mode 100755
index 00000000000..57275f07a3d
--- /dev/null
+++ b/apps/files_sharing/templates/get.php
@@ -0,0 +1,11 @@
+<table>
+ <thead>
+ <tr>
+ <th id="headerSize"><?php echo $l->t( 'Size' ); ?></th>
+ <th id="headerDate"><span id="modified"><?php echo $l->t( 'Modified' ); ?></span><span class="selectedActions"><a href="" class="delete"><?php echo $l->t('Delete all')?> <img class="svg" alt="<?php echo $l->t('Delete')?>" src="<?php echo OCP\image_path("core", "actions/delete.svg"); ?>" /></a></span></th>
+ </tr>
+ </thead>
+ <tbody id="fileList" data-readonly="<?php echo $_['readonly'];?>">
+ <?php echo($_['fileList']); ?>
+ </tbody>
+</table> \ No newline at end of file
diff --git a/apps/files_sharing_log/appinfo/database.xml b/apps/files_sharing_log/appinfo/database.xml
index 92e5f0125bd..dae811f87fa 100644
--- a/apps/files_sharing_log/appinfo/database.xml
+++ b/apps/files_sharing_log/appinfo/database.xml
@@ -3,7 +3,7 @@
<name>*dbname*</name>
<create>true</create>
<overwrite>false</overwrite>
- <charset>latin1</charset>
+ <charset>utf8</charset>
<table>
<name>*dbprefix*sharing_log</name>
<declaration>
diff --git a/apps/files_versions/ajax/expireAll.php b/apps/files_versions/ajax/expireAll.php
index 4f165be0ae9..2a678c7f0a5 100644
--- a/apps/files_versions/ajax/expireAll.php
+++ b/apps/files_versions/ajax/expireAll.php
@@ -27,6 +27,7 @@
// Check user and app status
OCP\JSON::checkLoggedIn();
OCP\App::checkAppEnabled('files_versions');
+OCP\JSON::callCheck();
$versions = new OCA_Versions\Storage();
diff --git a/apps/files_versions/ajax/rollbackVersion.php b/apps/files_versions/ajax/rollbackVersion.php
index 8d1092f8b8e..24d71a914a4 100644
--- a/apps/files_versions/ajax/rollbackVersion.php
+++ b/apps/files_versions/ajax/rollbackVersion.php
@@ -1,6 +1,7 @@
<?php
OCP\JSON::checkAppEnabled('files_versions');
+OCP\JSON::callCheck();
$userDirectory = "/".OCP\USER::getUser()."/files";
diff --git a/apps/files_versions/ajax/togglesettings.php b/apps/files_versions/ajax/togglesettings.php
index 86f614c5c89..546b37ae1aa 100644
--- a/apps/files_versions/ajax/togglesettings.php
+++ b/apps/files_versions/ajax/togglesettings.php
@@ -2,6 +2,7 @@
OCP\JSON::checkAppEnabled('files_versions');
OCP\JSON::checkAdminUser();
+OCP\JSON::callCheck();
if (OCP\Config::getSystemValue('versions', 'true')=='true') {
OCP\Config::setSystemValue('versions', 'false');
} else {
diff --git a/apps/files_versions/appinfo/api.php b/apps/files_versions/appinfo/api.php
new file mode 100644
index 00000000000..a7386bc2c9f
--- /dev/null
+++ b/apps/files_versions/appinfo/api.php
@@ -0,0 +1,36 @@
+<?php
+/**
+* ownCloud
+*
+* @author Michael Gapczynski
+* @copyright 2012 Michael Gapczynski mtgap@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/>.
+*/
+
+return array(
+ 'list' => array('method' => 'GET', 'class' => 'Storage', 'function' => 'getVersions',
+ 'parameters' => array(
+ 'file' => array('required' => true, 'type' => 'string')
+ )
+ ),
+ 'revert' => array('method' => 'POST', 'class' => 'Storage', 'function' => 'rollback',
+ 'parameters' => array(
+ 'file' => array('required' => true, 'type' => 'string'),
+ 'time' => array('required' => true, 'type' => 'int')
+ )
+ )
+);
+
+?> \ No newline at end of file
diff --git a/apps/files_versions/lib/versions.php b/apps/files_versions/lib/versions.php
index 0ce884c3ea0..f146676757d 100644
--- a/apps/files_versions/lib/versions.php
+++ b/apps/files_versions/lib/versions.php
@@ -227,13 +227,13 @@ class Storage {
}
$versions = array_reverse( $versions );
-
+
foreach( $versions as $key => $value ) {
// flag the first matched file in array (which will have latest modification date) as current version
- if ( $versions[$key]['fileMatch'] ) {
+ if ( $value['fileMatch'] ) {
- $versions[$key]['cur'] = 1;
+ $value['cur'] = 1;
break;
}
diff --git a/apps/gallery/appinfo/version b/apps/gallery/appinfo/version
index 8f0916f768f..4b9fcbec101 100644
--- a/apps/gallery/appinfo/version
+++ b/apps/gallery/appinfo/version
@@ -1 +1 @@
-0.5.0
+0.5.1
diff --git a/apps/gallery/l10n/de.php b/apps/gallery/l10n/de.php
index 6c3d9fc7389..cd580cf303c 100644
--- a/apps/gallery/l10n/de.php
+++ b/apps/gallery/l10n/de.php
@@ -1,9 +1,9 @@
<?php $TRANSLATIONS = array(
"Pictures" => "Bilder",
-"Settings" => "Einstellungen",
-"Rescan" => "Erneut Scannen",
-"Stop" => "Stopp",
-"Share" => "Teilen",
+"Share gallery" => "Galerie teilen",
+"Error: " => "Fehler:",
+"Internal error" => "Interner Fehler",
+"Slideshow" => "Slideshow",
"Back" => "Zurück",
"Remove confirmation" => "Bestätigung entfernen",
"Do you want to remove album" => "Soll das Album entfernt werden",
diff --git a/apps/gallery/lib/album.php b/apps/gallery/lib/album.php
index 39d6d3aded1..701949d4d80 100644
--- a/apps/gallery/lib/album.php
+++ b/apps/gallery/lib/album.php
@@ -58,7 +58,7 @@ class OC_Gallery_Album {
return $stmt->execute($args);
}
- public static function removeByName($owner, $name) { self::remove($ownmer, $name); }
+ public static function removeByName($owner, $name) { self::remove($owner, $name); }
public static function removeByPath($owner, $path) { self::remove($owner, null, $path); }
public static function removeByParentPath($owner, $parent) { self::remove($owner, null, null, $parent); }
diff --git a/apps/gallery/lib/managers.php b/apps/gallery/lib/managers.php
index b6ade3d1b1e..575d962dbe3 100644
--- a/apps/gallery/lib/managers.php
+++ b/apps/gallery/lib/managers.php
@@ -29,7 +29,6 @@ class DatabaseManager {
$stmt = \OCP\DB::prepare('INSERT INTO *PREFIX*pictures_images_cache (uid_owner, path, width, height) VALUES (?, ?, ?, ?)');
$stmt->execute(array(\OCP\USER::getUser(), $path, $width, $height));
$ret = array('path' => $path, 'width' => $width, 'height' => $height);
- unset($image);
$dir = dirname($path);
$this->cache[$dir][$path] = $ret;
return $ret;
diff --git a/apps/gallery/lib/share.php b/apps/gallery/lib/share.php
new file mode 100644
index 00000000000..6f3db45375f
--- /dev/null
+++ b/apps/gallery/lib/share.php
@@ -0,0 +1,42 @@
+<?php
+/**
+* ownCloud
+*
+* @author Michael Gapczynski
+* @copyright 2012 Michael Gapczynski mtgap@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/>.
+*/
+
+class OC_Share_Backend_Photo extends OCP\Share_Backend {
+
+ public $dependsOn = 'file';
+ public $supportedFileExtensions = array('jpg', 'png', 'gif');
+
+ public function getSource($item, $uid) {
+ return array('item' => 'blah.jpg', 'file' => $item);
+ }
+
+ public function generateTarget($item, $uid, $exclude = null) {
+ // TODO Make sure target path doesn't exist already
+ return $item;
+ }
+
+ public function formatItems($items, $format, $parameters = null) {
+
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/apps/gallery/lib/tiles.php b/apps/gallery/lib/tiles.php
index 754734e609e..e36d26d3191 100644
--- a/apps/gallery/lib/tiles.php
+++ b/apps/gallery/lib/tiles.php
@@ -33,7 +33,7 @@ class TilesLine {
}
public function setAvailableSpace($space) {
- $available_space = $space;
+ $this->available_space = $space;
}
public function getTilesCount() {
diff --git a/apps/journal/.gitignore b/apps/journal/.gitignore
new file mode 100644
index 00000000000..e2ff07d14d8
--- /dev/null
+++ b/apps/journal/.gitignore
@@ -0,0 +1,51 @@
+# the default generated dir + db file
+data
+owncloud
+config/config.php
+config/mount.php
+apps/inc.php
+
+# just sane ignores
+.*.sw[po]
+*.bak
+*.BAK
+*~
+*.orig
+*.class
+.cvsignore
+Thumbs.db
+*.py[co]
+_darcs/*
+CVS/*
+.svn/*
+RCS/*
+
+# kdevelop
+.kdev
+*.kdev4
+
+# Lokalize
+*lokalize*
+
+# eclipse
+.project
+.settings
+
+# netbeans
+nbproject
+
+# geany
+*.geany
+
+# Cloud9IDE
+.settings.xml
+
+# vim ex mode
+.vimrc
+
+# Mac OS
+.DS_Store
+
+# WebFinger
+.well-known
+/.buildpath
diff --git a/apps/journal/COPYING-AGPL b/apps/journal/COPYING-AGPL
new file mode 100644
index 00000000000..dba13ed2ddf
--- /dev/null
+++ b/apps/journal/COPYING-AGPL
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/apps/journal/COPYING-README b/apps/journal/COPYING-README
new file mode 100644
index 00000000000..18f06caa3ec
--- /dev/null
+++ b/apps/journal/COPYING-README
@@ -0,0 +1,10 @@
+Files in TAL Templating System for ownCloud are licensed under the Affero General Public License version 3,
+the text of which can be found in COPYING-AGPL, or any later version of the AGPL,
+unless otherwise noted.
+
+Licensing of components:
+* PHPTAL - http://phptal.org/ : LGPL
+
+All unmodified files from these and other sources retain their original copyright
+and license notices: see the relevant individual files.
+
diff --git a/apps/journal/Changelog b/apps/journal/Changelog
new file mode 100644
index 00000000000..08b20831bed
--- /dev/null
+++ b/apps/journal/Changelog
@@ -0,0 +1,5 @@
+# Changelog
+
+### 0.1
+
+- First release. \ No newline at end of file
diff --git a/apps/journal/README.md b/apps/journal/README.md
new file mode 100644
index 00000000000..7c06e4f0389
--- /dev/null
+++ b/apps/journal/README.md
@@ -0,0 +1,38 @@
+# Journal/Notes app for ownCloud
+
+## Features
+
+- Saves notes/journal entries as VJOURNAL records in the ownCloud Calendar.
+
+- Integrates with ownClouds search backend.
+
+- Sort entries by date/time ascending/descending or summary ascending/descending.
+
+- Plain text or rich text editing (rich text editing is still buggy and immature).
+
+- Syncs with KDEPIMs Journal part.
+
+- Completed tasks from the Task app can be automatically added as journal entries.
+
+- Stores entry data as json objects in each element for quich access and to minimize ajax calls.
+
+To install this app you will first have to install the [TAL Page Templates for ownCloud](/tanghus/tal#readme) app.
+
+## Installation from git
+
+1. Go to your ownCloud apps dir and clone the repo there:
+ <pre>
+ cd owncloud/apps
+ git clone git://github.com/tanghus/journal.git</pre>
+
+2. From your browser go to the ownCloud apps page (`/settings/apps.php`) and enable the Journal app.
+
+3. After a page refresh you should see the Journal app in the main menu.
+
+
+## DISCLAIMER
+
+There's no garantee this app won't eat your data, chew it up and spit it out. It works directly on the calendar app data
+though not touching anything but VJOURNAL entries. [Always backup!](http://tanghus.net/2012/04/backup-owncloud-calendar-and-contacts/)
+
+Please report any issues at the [github issue tracker](https://github.com/tanghus/journal/issues) \ No newline at end of file
diff --git a/apps/journal/TODO b/apps/journal/TODO
new file mode 100644
index 00000000000..28271365813
--- /dev/null
+++ b/apps/journal/TODO
@@ -0,0 +1,13 @@
+
+- Fix categories.
+
+- Hide empty elements?
+
+- Check if entered text in one mode gets transfered when changing modes.
+
+- Make edit area fit size and scrollable.
+
+- Add an "only read from this calendar" checkbox.
+
+- Test, test, test in other browser as well.
+
diff --git a/apps/journal/ajax/categories/categoriesfor.php b/apps/journal/ajax/categories/categoriesfor.php
new file mode 100644
index 00000000000..846af300de8
--- /dev/null
+++ b/apps/journal/ajax/categories/categoriesfor.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('contacts');
+
+$id = isset($_GET['id'])?$_GET['id']:null;
+if(is_null($id)) {
+ OCP\JSON::error(array('data' => array('message' => OC_Contacts_App::$l10n->t('No ID provided'))));
+ exit();
+}
+$vcard = OC_Contacts_App::getContactVCard( $id );
+foreach($vcard->children as $property){
+ //OCP\Util::writeLog('contacts','ajax/categories/checksumfor.php: '.$property->name, OCP\Util::DEBUG);
+ if($property->name == 'CATEGORIES') {
+ $checksum = md5($property->serialize());
+ OCP\JSON::success(array('data' => array('value'=>$property->value, 'checksum'=>$checksum)));
+ exit();
+ }
+}
+OCP\JSON::error(array('data' => array('message' => OC_Contacts_App::$l10n->t('Error setting checksum.'))));
+?>
diff --git a/apps/journal/ajax/categories/delete.php b/apps/journal/ajax/categories/delete.php
new file mode 100644
index 00000000000..e68d608c5da
--- /dev/null
+++ b/apps/journal/ajax/categories/delete.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('contacts');
+OCP\JSON::callCheck();
+
+foreach ($_POST as $key=>$element) {
+ debug('_POST: '.$key.'=>'.print_r($element, true));
+}
+
+require_once('../util.php');
+
+$categories = isset($_POST['categories'])?$_POST['categories']:null;
+
+if(is_null($categories)) {
+ bailOut(OC_Contacts_App::$l10n->t('No categories selected for deletion.'));
+}
+
+debug(print_r($categories, true));
+
+$addressbooks = OC_Contacts_Addressbook::all(OCP\USER::getUser());
+if(count($addressbooks) == 0) {
+ bailOut(OC_Contacts_App::$l10n->t('No address books found.'));
+}
+$addressbookids = array();
+foreach($addressbooks as $addressbook) {
+ $addressbookids[] = $addressbook['id'];
+}
+$contacts = OC_Contacts_VCard::all($addressbookids);
+if(count($contacts) == 0) {
+ bailOut(OC_Contacts_App::$l10n->t('No contacts found.'));
+}
+
+$cards = array();
+foreach($contacts as $contact) {
+ $cards[] = array($contact['id'], $contact['carddata']);
+}
+
+debug('Before delete: '.print_r($categories, true));
+
+$catman = new OC_VCategories('contacts');
+$catman->delete($categories, $cards);
+debug('After delete: '.print_r($catman->categories(), true));
+OC_Contacts_VCard::updateDataByID($cards);
+OCP\JSON::success(array('data' => array('categories'=>$catman->categories())));
+
+?>
diff --git a/apps/journal/ajax/categories/list.php b/apps/journal/ajax/categories/list.php
new file mode 100644
index 00000000000..ac8976fa0ec
--- /dev/null
+++ b/apps/journal/ajax/categories/list.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('journal');
+
+$categories = OC_Journal_App::getCategories();
+
+OCP\JSON::success(array('data' => array('categories'=>$categories)));
+
+?>
diff --git a/apps/journal/ajax/categories/rescan.php b/apps/journal/ajax/categories/rescan.php
new file mode 100644
index 00000000000..d746f9179fe
--- /dev/null
+++ b/apps/journal/ajax/categories/rescan.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('journal');
+
+foreach ($_POST as $key=>$element) {
+ debug('_POST: '.$key.'=>'.print_r($element, true));
+}
+
+function bailOut($msg) {
+ OCP\JSON::error(array('data' => array('message' => $msg)));
+ OCP\Util::writeLog('journal','ajax/categories/rescan.php: '.$msg, OCP\Util::DEBUG);
+ exit();
+}
+function debug($msg) {
+ OCP\Util::writeLog('journal','ajax/categories/rescan.php: '.$msg, OCP\Util::DEBUG);
+}
+
+$calendars = OC_Calendar_Calendar::allCalendars(OCP\USER::getUser());
+if(count($calendars) == 0) {
+ bailOut(OC_Calendar_App::$l10n->t('No calendars found.'));
+}
+$events = array();
+foreach($calendars as $calendar) {
+ $calendar_events = OC_Calendar_Object::all($calendar['id']);
+ $events = $events + $calendar_events;
+}
+if(count($events) == 0) {
+ bailOut(OC_Calendar_App::$l10n->t('No events found.'));
+}
+
+OC_Calendar_App::scanCategories($events);
+$categories = OC_Calendar_App::getCategoryOptions();
+
+OCP\JSON::success(array('data' => array('categories'=>$categories))); \ No newline at end of file
diff --git a/apps/journal/ajax/delete.php b/apps/journal/ajax/delete.php
new file mode 100644
index 00000000000..4013e4440a6
--- /dev/null
+++ b/apps/journal/ajax/delete.php
@@ -0,0 +1,37 @@
+<?php
+/**
+ * ownCloud - Journal
+ *
+ * @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
+ *
+ * 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/>.
+ *
+ */
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('journal');
+OCP\JSON::callCheck();
+
+$id = isset($_POST['id'])?$_POST['id']:null;
+if(is_null($id)) {
+ OCP\JSON::error(array('data'=>array('message' => OC_Journal_App::$l10n->t('ID is not set!'))));
+ exit;
+}
+$journal = OC_Calendar_App::getEventObject($id);
+if($journal) {
+ OC_Calendar_Object::delete($id);
+ OCP\JSON::success(array('data' => array( 'id' => $id )));
+} else {
+ OCP\JSON::error(array('data' => array('id' => $id, 'message' => OC_Journal_App::$l10n->t('Could not find journal entry: '.$id))));
+} \ No newline at end of file
diff --git a/apps/journal/ajax/entries.php b/apps/journal/ajax/entries.php
new file mode 100644
index 00000000000..03f2366d33b
--- /dev/null
+++ b/apps/journal/ajax/entries.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Copyright (c) 2011 Georg Ehrke <ownclouddev at georgswebsite dot de>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+// Init owncloud
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('journal');
+
+$calendars = OC_Calendar_Calendar::allCalendars(OCP\User::getUser(), true);
+$user_timezone = OCP\Config::getUserValue(OCP\User::getUser(), 'calendar', 'timezone', date_default_timezone_get());
+session_write_close();
+$journals = array();
+foreach( $calendars as $calendar ){
+ $calendar_journals = OC_Calendar_Object::all($calendar['id']);
+ foreach( $calendar_journals as $journal ) {
+ if($journal['objecttype']!='VJOURNAL') {
+ continue;
+ }
+ if(is_null($journal['summary'])) {
+ continue;
+ }
+ $object = OC_VObject::parse($journal['calendardata']);
+ $vjournalobj = $object->VJOURNAL;
+ try {
+ $journals[] = OC_Journal_App::arrayForJSON($journal['id'], $vjournalobj, $user_timezone);
+ } catch(Exception $e) {
+ OCP\Util::writeLog('journal', 'ajax/getentries.php. id: '.$journal['id'].' '.$e->getMessage(), OCP\Util::ERROR);
+ }
+ }
+}
+
+OCP\JSON::success(array('data' => array('entries' => $journals)));
diff --git a/apps/journal/ajax/saveproperty.php b/apps/journal/ajax/saveproperty.php
new file mode 100644
index 00000000000..016f922a9af
--- /dev/null
+++ b/apps/journal/ajax/saveproperty.php
@@ -0,0 +1,138 @@
+<?php
+/**
+ * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+$htmlwrap = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"><html><head></head><body>%s</body></html>';
+$divwrap = '<div>%s</div>';
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('journal');
+OCP\JSON::callCheck();
+
+$id = isset($_POST['id'])?$_POST['id']:null;
+$property = isset($_POST['type'])?$_POST['type']:null;
+$value = isset($_POST['value'])?$_POST['value']:null;
+if(is_null($id)) {
+ OCP\JSON::error(array('data'=>array('message' => OC_Journal_App::$l10n->t('ID is not set!'))));
+ exit;
+}
+if(is_null($property)) {
+ OCP\JSON::error(array('data'=>array('message' => OC_Journal_App::$l10n->t('Property name is not set!'))));
+ exit;
+}
+if(is_null($value)) {
+ OCP\JSON::error(array('data'=>array('message' => OC_Journal_App::$l10n->t('Property value is not set!'))));
+ exit;
+}
+
+foreach($_POST as $key => $val) {
+ error_log($key.': '.print_r($val, true));
+}
+
+$parameters = isset($_POST['parameters'])? $_POST['parameters']:null;
+if($id == 'new') {
+ $vcalendar = OC_Journal_App::createVCalendar();
+} else {
+ $vcalendar = OC_Calendar_App::getVCalendar( $id );
+}
+error_log('saveproperty: '.$property.': '.print_r($value, true));
+$vjournal = $vcalendar->VJOURNAL;
+switch($property) {
+ case 'DESCRIPTION':
+ $hasgenericformat = false;
+ $haskdeformat = false;
+ if(!$vjournal->DESCRIPTION) {
+ $vjournal->setString('DESCRIPTION', $value);
+ }
+ if($parameters && isset($parameters['FORMAT']) && strtoupper($parameters['FORMAT']) == 'HTML') {
+ if($value[0] != '<') { // Fugly hack coming up
+ $value = sprintf($divwrap, $value);
+ }
+ $vjournal->DESCRIPTION->value = sprintf($htmlwrap, $value);
+ foreach($vjournal->DESCRIPTION->parameters as $parameter){
+ if(stripos($parameter->name, 'X-KDE-TEXTFORMAT') !== false && stripos($parameter->value, 'HTML') !== false){
+ $haskdeformat = true;
+ }
+ if(stripos($parameter->name, 'X-TEXTFORMAT') !== false && stripos($parameter->value, 'HTML') !== false){
+ $hasgenericformat = true;
+ }
+ }
+ if(!$haskdeformat) {
+ try {
+ $vjournal->DESCRIPTION->add(new Sabre_VObject_Parameter('X-KDE-TEXTFORMAT', 'HTML'));
+ } catch (Exception $e) {
+ OCP\JSON::error(array('data'=>array('message'=>OC_Journal_App::$l10n->t('Error setting rich text format parameter: '.$e->getMessage()))));
+ exit();
+ }
+ }
+ if(!$hasgenericformat) { // Add a more generic text formatting parameter in case other clients would use VJOURNAL this way.
+ try {
+ $vjournal->DESCRIPTION->add(new Sabre_VObject_Parameter('X-TEXTFORMAT', 'HTML'));
+ } catch (Exception $e) {
+ OCP\JSON::error(array('data'=>array('message'=>OC_Journal_App::$l10n->t('Error setting rich text format parameter: '.$e->getMessage()))));
+ exit();
+ }
+ }
+ } else {
+ $vjournal->DESCRIPTION->value = $value;
+ }
+ break;
+ case 'DTSTART':
+ try {
+ $date_only = isset($_POST['date_only']) && (bool)$_POST['date_only'] == true?true:false;
+ $timezone = OCP\Config::getUserValue(OCP\User::getUser(), 'calendar', 'timezone', date_default_timezone_get());
+ $timezone = new DateTimeZone($timezone);
+ //$dtstart = new DateTime($value, $timezone);
+ $dtstart = new DateTime('@'.$value);
+ $dtstart->setTimezone($timezone);
+ $type = Sabre_VObject_Property_DateTime::LOCALTZ;
+ if ($date_only) {
+ $type = Sabre_VObject_Property_DateTime::DATE;
+ }
+ $vjournal->setDateTime('DTSTART', $value, $type);
+ } catch (Exception $e) {
+ OCP\JSON::error(array('data'=>array('message'=>OC_Journal_App::$l10n->t('Invalid date/time: '.$e->getMessage()))));
+ exit();
+ }
+ break;
+ case 'ORGANIZER':
+ case 'SUMMARY':
+ case 'CATEGORIES':
+ $vobject = $vjournal->getVObject();
+ if(isset($vobject[$property])) {
+ $vobject[$property]['value'] = $value;
+ } else {
+ $vjournal->setString($property, $value);
+ }
+ break;
+ $vjournal->setString($property, $value);
+ break;
+ default:
+ OCP\JSON::error(array('data'=>array('message'=>'Unknown type: '.$property)));
+ exit();
+}
+
+$vjournal->setDateTime('LAST-MODIFIED', 'now', Sabre_VObject_Property_DateTime::UTC);
+$vjournal->setDateTime('DTSTAMP', 'now', Sabre_VObject_Property_DateTime::UTC);
+
+if($id == 'new') {
+ // TODO: Have a calendar ID parameter in request.
+ $cid = OCP\Config::getUserValue(OCP\User::getUser(), 'journal', 'default_calendar', null);
+ // Check that the calendar exists and that it's ours.
+ $cid = OC_Calendar_App::getCalendar($cid, true);
+ if(!$cid) {
+ OCP\Util::writeLog('journal', 'The default calendar '.$cid.' is either not owned by '.OCP\User::getUser().' or doesn\'t exist.', OCP\Util::WARN);
+ $calendars = OC_Calendar_Calendar::allCalendars(OCP\User::getUser(), true);
+ $first_calendar = reset($calendars);
+ $cid = $first_calendar['id'];
+ }
+ $id = OC_Calendar_Object::add($cid, $vcalendar->serialize());
+} else {
+ OC_Calendar_Object::edit($id, $vcalendar->serialize());
+}
+$user_timezone = OCP\Config::getUserValue(OCP\User::getUser(), 'calendar', 'timezone', date_default_timezone_get());
+$journal_info = OC_Journal_App::arrayForJSON($id, $vjournal, $user_timezone);
+OCP\JSON::success(array('data' => $journal_info));
diff --git a/apps/contacts/ajax/loadphoto.php b/apps/journal/ajax/setdefaultcalendar.php
index a35631055eb..a82c1e3cf1f 100644
--- a/apps/contacts/ajax/loadphoto.php
+++ b/apps/journal/ajax/setdefaultcalendar.php
@@ -2,7 +2,6 @@
/**
* ownCloud - Addressbook
*
- * @author Thomas Tanghus
* @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
*
* This library is free software; you can redistribute it and/or
@@ -19,28 +18,19 @@
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
-
-// Check if we are a user
-OCP\JSON::checkLoggedIn();
-OCP\JSON::checkAppEnabled('contacts');
-
-require_once('loghandler.php');
-$id = isset($_GET['id']) ? $_GET['id'] : '';
-$refresh = isset($_GET['refresh']) ? true : false;
-
-if($id == '') {
- bailOut(OC_Contacts_App::$l10n->t('Missing contact id.'));
-}
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('journal');
+OCP\JSON::callCheck();
-$checksum = '';
-$vcard = OC_Contacts_App::getContactVCard( $id );
-foreach($vcard->children as $property){
- if($property->name == 'PHOTO') {
- $checksum = md5($property->serialize());
- break;
- }
+$id = isset($_POST['id'])?$_POST['id']:null;
+if(is_null($id)) {
+ OCP\JSON::error(array('data'=>array('message' => OC_Journal_App::$l10n->t('ID is not set!'))));
+ exit;
}
-
-OCP\JSON::success(array('data' => array('checksum'=>$checksum)));
-
+if(OCP\Config::setUserValue(OCP\USER::getUser(), 'journal', 'default_calendar', $id)) {
+ OC_Calendar_Object::delete($id);
+ OCP\JSON::success(array('data' => array( 'id' => $id )));
+} else {
+ OCP\JSON::error(array('data' => array('id' => $id, 'message' => OC_Journal_App::$l10n->t('Could not set default calendar: '.$id))));
+} \ No newline at end of file
diff --git a/apps/journal/ajax/util.php b/apps/journal/ajax/util.php
new file mode 100644
index 00000000000..b690b2e1948
--- /dev/null
+++ b/apps/journal/ajax/util.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * ownCloud - Addressbook
+ *
+ * @author Thomas Tanghus
+ * @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
+ *
+ * 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/>.
+ *
+ */
+
+function bailOut($msg, $tracelevel=1, $debuglevel=OCP\Util::ERROR) {
+ OCP\JSON::error(array('data' => array('message' => $msg)));
+ debug($msg, $tracelevel, $debuglevel);
+ exit();
+}
+
+function debug($msg, $tracelevel=0, $debuglevel=OCP\Util::DEBUG) {
+ if(PHP_VERSION >= "5.4") {
+ $call = debug_backtrace(false, $tracelevel+1);
+ } else {
+ $call = debug_backtrace(false);
+ }
+ error_log('trace: '.print_r($call, true));
+ $call = $call[$tracelevel];
+ if($debuglevel !== false) {
+ OCP\Util::writeLog('journal', $call['file'].'. Line: '.$call['line'].': '.$msg, $debuglevel);
+ }
+}
diff --git a/apps/journal/appinfo/app.php b/apps/journal/appinfo/app.php
new file mode 100644
index 00000000000..5f33d40399f
--- /dev/null
+++ b/apps/journal/appinfo/app.php
@@ -0,0 +1,18 @@
+<?php
+$l=new OC_L10N('journal');
+OC::$CLASSPATH['OC_Calendar_Calendar'] = 'calendar/lib/calendar.php';
+OC::$CLASSPATH['OC_Journal_App'] = 'journal/lib/app.php';
+OC::$CLASSPATH['OC_Search_Provider_Journal'] = 'journal/lib/search.php';
+OC::$CLASSPATH['OC_Journal_Hooks'] = 'journal/lib/hooks.php';
+
+OCP\Util::connectHook('OC_Task', 'taskCompleted', 'OC_Journal_Hooks', 'taskToJournalEntry');
+
+OCP\App::addNavigationEntry( array(
+ 'id' => 'journal_index',
+ 'order' => 11,
+ 'href' => OCP\Util::linkTo( 'journal', 'index.php' ),
+ 'icon' => OCP\Util::imagePath( 'journal', 'journal.png' ),
+ 'name' => $l->t('Journal')));
+
+OC_Search::registerProvider('OC_Search_Provider_Journal');
+OCP\App::registerPersonal('journal','settings'); \ No newline at end of file
diff --git a/apps/journal/appinfo/info.xml b/apps/journal/appinfo/info.xml
new file mode 100644
index 00000000000..426e38eb9f1
--- /dev/null
+++ b/apps/journal/appinfo/info.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<info>
+ <id>journal</id>
+ <name>Journal</name>
+ <version>0.1</version>
+ <licence>AGPL</licence>
+ <author>Thomas Tanghus</author>
+ <require>5</require>
+ <shipped>true</shipped>
+ <description>Journal view from calendar</description>
+</info>
diff --git a/apps/journal/css/journal.css b/apps/journal/css/journal.css
new file mode 100644
index 00000000000..ac2ae74c65a
--- /dev/null
+++ b/apps/journal/css/journal.css
@@ -0,0 +1,19 @@
+#leftcontent a { font-weight: bold; }
+#metadata { position: fixed; background-color: #ccc; width: 20em; right: 0; top: 6.5em; bottom: 0; overflow: auto; padding: 1em; margin: 0; font-size: 1em; font-weight: bold; }
+#metadata :disabled.property { background-color: #ccc; -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; }
+#entry { position: fixed; background: #fff; top: 6.5em; bottom: 0; left: 32.5em !important; right: 22em; padding: 1em; margin: 0; }
+div.propertycontainer[data-type="DESCRIPTION"] { height: 90%; }
+#description { width: 95%; height: 90%; }
+.rte-content { position: absolute; left: 1em; right: 1em; top: 7em; bottom: 1em; overflow: auto; }
+#summary { width: 95%; font-size: 1.4em; font-weight: bold; height: 1.6em; }
+.editable,.content { font-size: 1.2em; background-color: #fff !important; border: 1px solid #fff !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: 3px; -webkit-border-radius: 3px; border-radius: 3px; cursor: text; }
+.editable:hover,.editable:focus,.editable:hover:active { border: 1px solid #1d2d44 !important; }
+#categories,#organizer,#location,#link { width: 18em; clear: right; }
+#dtstartdate { width: 10em; }
+#dtstarttime { width: 5em; }
+.loading { background: url('%webroot%/core/img/loading.gif') no-repeat center !important; cursor: wait; }
+.action { padding-left: 0.5em; }
+label,dt { color: #aaa; }
+label:hover, dt:hover { color: #333; }
+.required { color: red; border: 3px solid red; }
+input { font-size: 1em; font-weight: bold; } \ No newline at end of file
diff --git a/apps/journal/css/rte.css b/apps/journal/css/rte.css
new file mode 100644
index 00000000000..e786599002a
--- /dev/null
+++ b/apps/journal/css/rte.css
@@ -0,0 +1,7 @@
+.rte-toolbar { margin: 0; width:100%; height:22px; padding:0; list-style-type:none; }
+.rte-toolbar li button, .rte-toolbar li a { float:left; cursor:pointer; opacity: 0.6; padding:0; margin: 0; border: 0; border-radius: 0; outline: 0; background: inherit; }
+.rte-toolbar li button:hover { opacity: 0.8; }
+.rte-content { cursor:text;position:absolute; border-color:#000;border-style:solid;white-space: pre-wrap;word-wrap:break-word; padding: 0.2em; }
+.rte-content ul { list-style: circle inside; }
+.rte-content li { padding-left: 1em; }
+
diff --git a/apps/journal/img/bold.svg b/apps/journal/img/bold.svg
new file mode 100644
index 00000000000..2b06228258a
--- /dev/null
+++ b/apps/journal/img/bold.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
+<defs/>
+<g id="layer4">
+ <g id="g2464" transform="translate(-3.90271, -4.31297)" fill="none">
+ <path id="path2277" transform="translate(4.74736, 5.23002)" fill="#000000" fill-rule="evenodd" stroke="#000000" stroke-width="1.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4.00000000000" d="M5.13684 0L0 13.8281L3.42454 13.8281L4.1862 11.5856L10.1818 11.5856L10.9586 13.8281L14.3831 13.8281L9.24627 0L8.21891 0L6.1642 0ZM7.19155 3.17671L9.10715 8.59582L5.27595 8.59582Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/apps/journal/img/indent-less.svg b/apps/journal/img/indent-less.svg
new file mode 100644
index 00000000000..246089c293c
--- /dev/null
+++ b/apps/journal/img/indent-less.svg
@@ -0,0 +1,300 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg4198"
+ sodipodi:version="0.32"
+ inkscape:version="0.42+devel"
+ sodipodi:docbase="/home/jimmac/gfx/ximian/tango-icon-theme/scalable/actions"
+ sodipodi:docname="format-indent-less.svg">
+ <defs
+ id="defs4200">
+ <linearGradient
+ id="linearGradient5157">
+ <stop
+ style="stop-color:#555753"
+ offset="0"
+ id="stop5159" />
+ <stop
+ style="stop-color:#555753;stop-opacity:0;"
+ offset="1"
+ id="stop5161" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2591">
+ <stop
+ style="stop-color:#9db029;stop-opacity:1;"
+ offset="0"
+ id="stop2593" />
+ <stop
+ style="stop-color:#869623;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop2595" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3558">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3560" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3562" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3558"
+ id="radialGradient3564"
+ cx="22.571428"
+ cy="30.857143"
+ fx="22.571428"
+ fy="30.857143"
+ r="15.571428"
+ gradientTransform="matrix(1.000000,0.000000,0.000000,0.651376,4.638648e-15,10.75754)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient15218">
+ <stop
+ style="stop-color:#f8f8f7;stop-opacity:1;"
+ offset="0"
+ id="stop15220" />
+ <stop
+ id="stop2269"
+ offset="0.59928656"
+ style="stop-color:#e8e8e8;stop-opacity:1;" />
+ <stop
+ style="stop-color:#e2e2de;stop-opacity:1;"
+ offset="1"
+ id="stop15222" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15218"
+ id="linearGradient15224"
+ x1="22.308331"
+ y1="18.992140"
+ x2="35.785294"
+ y2="39.498238"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.060837,0.000000,0.000000,0.987595,4.641161,4.108291)" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2259">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop2261" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop2263" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2259"
+ id="linearGradient2265"
+ x1="26.076092"
+ y1="26.696676"
+ x2="30.811172"
+ y2="42.007351"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.993566,0.000000,0.000000,1.000000,6.219859,4.033411)" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2216">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop2218" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop2220" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2216"
+ id="linearGradient2222"
+ x1="36.8125"
+ y1="39.15625"
+ x2="39.0625"
+ y2="42.0625"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(6.161836,4.033411)" />
+ <linearGradient
+ id="linearGradient2224">
+ <stop
+ style="stop-color:#7c7c7c;stop-opacity:1;"
+ offset="0"
+ id="stop2226" />
+ <stop
+ style="stop-color:#b8b8b8;stop-opacity:1;"
+ offset="1"
+ id="stop2228" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15218"
+ id="linearGradient2240"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.342704,0.000000,0.000000,1.235378,-8.219611,-6.577189)"
+ x1="20.794008"
+ y1="18.378813"
+ x2="35.596001"
+ y2="39.60046" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5157"
+ id="linearGradient5163"
+ x1="10.729691"
+ y1="26.422195"
+ x2="2.3941064"
+ y2="26.422195"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(-1.188000,0.000000,0.000000,1.188000,38.20277,4.174916)" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#bebebe"
+ borderopacity="0.28627451"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.313708"
+ inkscape:cx="32.761989"
+ inkscape:cy="19.079251"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="770"
+ inkscape:window-height="852"
+ inkscape:window-x="395"
+ inkscape:window-y="881"
+ inkscape:showpageshadow="false"
+ fill="#555753" />
+ <metadata
+ id="metadata4203">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Unindent</dc:title>
+ <dc:date></dc:date>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>indent</rdf:li>
+ <rdf:li>less</rdf:li>
+ <rdf:li>unindent</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Andreas Nilsson</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="arc"
+ style="opacity:0.47368421;color:#000000;fill:url(#radialGradient3564);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="path3556"
+ sodipodi:cx="22.571428"
+ sodipodi:cy="30.857143"
+ sodipodi:rx="15.571428"
+ sodipodi:ry="10.142858"
+ d="M 38.142857 30.857143 A 15.571428 10.142858 0 1 1 7,30.857143 A 15.571428 10.142858 0 1 1 38.142857 30.857143 z"
+ transform="matrix(1.472335,0.000000,0.000000,0.266385,-9.159070,37.48203)" />
+ <rect
+ style="opacity:1;fill:url(#linearGradient2240);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4238"
+ width="38.996792"
+ height="45.003101"
+ x="4.5016017"
+ y="1.4968988"
+ rx="0.56650788"
+ ry="0.56650823" />
+ <rect
+ style="color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4248"
+ width="17.63052"
+ height="2"
+ x="10"
+ y="6" />
+ <rect
+ y="10"
+ x="10"
+ height="2"
+ width="16.324554"
+ id="rect4250"
+ style="color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <rect
+ style="color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4252"
+ width="15"
+ height="2"
+ x="10"
+ y="14" />
+ <rect
+ y="18"
+ x="10"
+ height="2"
+ width="17.63052"
+ id="rect4254"
+ style="color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.99999982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect2245"
+ width="37.025566"
+ height="43.022316"
+ x="5.4997125"
+ y="2.4997177"
+ rx="0"
+ ry="0" />
+ <path
+ sodipodi:nodetypes="cccccccc"
+ id="path8643"
+ d="M 35.851425,26.743533 L 35.851425,38.610554 L 22.739004,38.610554 L 22.739004,44.458667 L 9,32.651858 L 22.741562,21 L 22.741562,26.747164 L 35.851425,26.743533 z "
+ style="opacity:1;color:#000000;fill:url(#linearGradient5163);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ </g>
+</svg>
diff --git a/apps/journal/img/indent-more.svg b/apps/journal/img/indent-more.svg
new file mode 100644
index 00000000000..4e137cf4528
--- /dev/null
+++ b/apps/journal/img/indent-more.svg
@@ -0,0 +1,295 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg4198"
+ sodipodi:version="0.32"
+ inkscape:version="0.42+devel"
+ sodipodi:docbase="/home/jimmac/gfx/ximian/tango-icon-theme/scalable/actions"
+ sodipodi:docname="format-indent-more.svg">
+ <defs
+ id="defs4200">
+ <linearGradient
+ id="linearGradient5157">
+ <stop
+ style="stop-color:#555753"
+ offset="0"
+ id="stop5159" />
+ <stop
+ style="stop-color:#555753;stop-opacity:0;"
+ offset="1"
+ id="stop5161" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2591">
+ <stop
+ style="stop-color:#9db029;stop-opacity:1;"
+ offset="0"
+ id="stop2593" />
+ <stop
+ style="stop-color:#869623;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop2595" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3558">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3560" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3562" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3558"
+ id="radialGradient3564"
+ cx="22.571428"
+ cy="30.857143"
+ fx="22.571428"
+ fy="30.857143"
+ r="15.571428"
+ gradientTransform="matrix(1.000000,0.000000,0.000000,0.651376,4.638648e-15,10.75754)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient15218">
+ <stop
+ style="stop-color:#f8f8f7;stop-opacity:1;"
+ offset="0"
+ id="stop15220" />
+ <stop
+ id="stop2269"
+ offset="0.59928656"
+ style="stop-color:#e8e8e8;stop-opacity:1;" />
+ <stop
+ style="stop-color:#e2e2de;stop-opacity:1;"
+ offset="1"
+ id="stop15222" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15218"
+ id="linearGradient15224"
+ x1="22.308331"
+ y1="18.992140"
+ x2="35.785294"
+ y2="39.498238"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.060837,0.000000,0.000000,0.987595,4.641161,4.108291)" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2259">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop2261" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop2263" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2259"
+ id="linearGradient2265"
+ x1="26.076092"
+ y1="26.696676"
+ x2="30.811172"
+ y2="42.007351"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.993566,0.000000,0.000000,1.000000,6.219859,4.033411)" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2216">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop2218" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop2220" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2216"
+ id="linearGradient2222"
+ x1="36.8125"
+ y1="39.15625"
+ x2="39.0625"
+ y2="42.0625"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(6.161836,4.033411)" />
+ <linearGradient
+ id="linearGradient2224">
+ <stop
+ style="stop-color:#7c7c7c;stop-opacity:1;"
+ offset="0"
+ id="stop2226" />
+ <stop
+ style="stop-color:#b8b8b8;stop-opacity:1;"
+ offset="1"
+ id="stop2228" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15218"
+ id="linearGradient2240"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.342704,0.000000,0.000000,1.235378,-8.219611,-6.577189)"
+ x1="20.794008"
+ y1="18.378813"
+ x2="35.596001"
+ y2="39.60046" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5157"
+ id="linearGradient5163"
+ x1="10.729691"
+ y1="26.422195"
+ x2="2.3941064"
+ y2="26.422195"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.188000,0.000000,0.000000,1.188000,6.648659,4.174916)" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#bebebe"
+ borderopacity="0.28627451"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.313708"
+ inkscape:cx="32.761989"
+ inkscape:cy="19.079251"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="770"
+ inkscape:window-height="852"
+ inkscape:window-x="395"
+ inkscape:window-y="881"
+ inkscape:showpageshadow="false"
+ fill="#555753" />
+ <metadata
+ id="metadata4203">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Indent More</dc:title>
+ <dc:date></dc:date>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>indent</rdf:li>
+ <rdf:li>more</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ <dc:identifier>http://jimmac.musichall.cz</dc:identifier>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="arc"
+ style="opacity:0.47368421;color:#000000;fill:url(#radialGradient3564);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="path3556"
+ sodipodi:cx="22.571428"
+ sodipodi:cy="30.857143"
+ sodipodi:rx="15.571428"
+ sodipodi:ry="10.142858"
+ d="M 38.142857 30.857143 A 15.571428 10.142858 0 1 1 7,30.857143 A 15.571428 10.142858 0 1 1 38.142857 30.857143 z"
+ transform="matrix(1.472335,0.000000,0.000000,0.266385,-9.159070,37.48203)" />
+ <rect
+ style="opacity:1;fill:url(#linearGradient2240);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4238"
+ width="38.996792"
+ height="45.003101"
+ x="4.5016017"
+ y="1.4968988"
+ rx="0.56650788"
+ ry="0.56650823" />
+ <rect
+ style="color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4248"
+ width="12.000005"
+ height="2"
+ x="26.00001"
+ y="6" />
+ <rect
+ y="10"
+ x="26.00001"
+ height="2"
+ width="11.111115"
+ id="rect4250"
+ style="color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <rect
+ style="color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4252"
+ width="10.209573"
+ height="2"
+ x="26.00001"
+ y="14" />
+ <rect
+ y="18"
+ x="26.00001"
+ height="2"
+ width="12.000005"
+ id="rect4254"
+ style="color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.99999982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect2245"
+ width="37.025566"
+ height="43.022316"
+ x="5.4997125"
+ y="2.4997177"
+ rx="0"
+ ry="0" />
+ <path
+ sodipodi:nodetypes="cccccccc"
+ id="path8643"
+ d="M 9,26.743533 L 9,38.610554 L 22.112421,38.610554 L 22.112421,44.458667 L 35.851425,32.651858 L 22.109863,21 L 22.109863,26.747164 L 9,26.743533 z "
+ style="opacity:1;color:#000000;fill:url(#linearGradient5163);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ </g>
+</svg>
diff --git a/apps/journal/img/italic.svg b/apps/journal/img/italic.svg
new file mode 100644
index 00000000000..1eeb4fc1321
--- /dev/null
+++ b/apps/journal/img/italic.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
+<g id="layer5"/><g id="layer2">
+ <path id="path2211" transform="translate(1.99698, 0.976621)" fill="#000000" fill-rule="evenodd" stroke="#000000" stroke-width="1.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4.00000000000" d="M9.01429 0L0 13.4464L1.9531 13.4464L3.90334 10.5391L9.76548 10.5391L9.76548 13.4464L11.2679 13.4464L11.2679 0ZM9.76548 1.81708L9.76548 8.72201L5.12224 8.72201Z"/>
+ </g>
+</svg>
diff --git a/apps/journal/img/journal.png b/apps/journal/img/journal.png
new file mode 100644
index 00000000000..f95dd8e9f3a
--- /dev/null
+++ b/apps/journal/img/journal.png
Binary files differ
diff --git a/apps/journal/img/journal.svg b/apps/journal/img/journal.svg
new file mode 100644
index 00000000000..2ee85ef0699
--- /dev/null
+++ b/apps/journal/img/journal.svg
@@ -0,0 +1,104 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.8pt" height="12.8pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="translate(-0.54277, -2.50102)" gradientUnits="objectBoundingBox" x1="0.46156580718" y1="0.26673472489" x2="0.46156580718" y2="1.13340132578" spreadMethod="pad">
+ <stop stop-color="#ffffff" offset="0.00000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#e6e6e6" offset="1.00000000000" stop-opacity="1.00000000000"/>
+ </linearGradient>
+ <linearGradient id="gradient1" gradientTransform="translate(-0.54277, -2.50102)" gradientUnits="objectBoundingBox" x1="0.15385526906" y1="1.20006799200" x2="0.15385526906" y2="0.13340137911" spreadMethod="pad">
+ <stop stop-color="#969696" offset="0.00000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#bebebe" offset="1.00000000000" stop-opacity="1.00000000000"/>
+ </linearGradient>
+ <linearGradient id="gradient2" gradientTransform="matrix(0.415777 -0.417494 0.518983 0.514619 -17.796 1.86487)" gradientUnits="objectBoundingBox" x1="1.77989041379" y1="1.91182142950" x2="1.77989041379" y2="2.35319620460" spreadMethod="pad">
+ <stop stop-color="#181818" offset="0.00000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#dbdbdb" offset="0.13482947648" stop-opacity="1.00000000000"/>
+ <stop stop-color="#a4a4a4" offset="0.20224422216" stop-opacity="1.00000000000"/>
+ <stop stop-color="#ffffff" offset="0.26965895295" stop-opacity="1.00000000000"/>
+ <stop stop-color="#8d8d8d" offset="0.44650277495" stop-opacity="1.00000000000"/>
+ <stop stop-color="#959595" offset="0.57114136219" stop-opacity="1.00000000000"/>
+ <stop stop-color="#cecece" offset="0.72038066387" stop-opacity="1.00000000000"/>
+ <stop stop-color="#181818" offset="1.00000000000" stop-opacity="1.00000000000"/>
+ </linearGradient>
+ <linearGradient id="gradient3" gradientTransform="matrix(0.404023 -0.405692 0.607375 0.602268 -27.0463 -3.00744)" gradientUnits="objectBoundingBox" x1="9.40879825249" y1="7.83967975733" x2="9.40879825249" y2="9.41157739957" spreadMethod="pad">
+ <stop stop-color="#565656" offset="0.00000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#9a9a9a" offset="0.50000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#545454" offset="1.00000000000" stop-opacity="1.00000000000"/>
+ </linearGradient>
+ <linearGradient id="gradient4" gradientTransform="matrix(0.404023 -0.405692 0.607375 0.602268 -27.0463 -3.00744)" gradientUnits="objectBoundingBox" x1="9.40879584896" y1="7.83967765054" x2="9.40879584896" y2="9.41157487035" spreadMethod="pad">
+ <stop stop-color="#b1b1b1" offset="0.00000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#ffffff" offset="0.50000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#8f8f8f" offset="1.00000000000" stop-opacity="1.00000000000"/>
+ </linearGradient>
+ <linearGradient id="gradient5" gradientTransform="matrix(0.404023 -0.405692 0.607375 0.602268 -27.0463 -3.00744)" gradientUnits="objectBoundingBox" x1="9.40879155931" y1="7.83968201339" x2="9.40879155931" y2="9.41158010798" spreadMethod="pad">
+ <stop stop-color="#565656" offset="0.00000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#9a9a9a" offset="0.50000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#545454" offset="1.00000000000" stop-opacity="1.00000000000"/>
+ </linearGradient>
+ <linearGradient id="gradient6" gradientTransform="matrix(0.404023 -0.405692 0.607375 0.602268 -27.0463 -3.00744)" gradientUnits="objectBoundingBox" x1="9.40879343487" y1="7.83967992981" x2="9.40879343487" y2="9.41157760663" spreadMethod="pad">
+ <stop stop-color="#b1b1b1" offset="0.00000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#ffffff" offset="0.50000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#8f8f8f" offset="1.00000000000" stop-opacity="1.00000000000"/>
+ </linearGradient>
+ <linearGradient id="gradient7" gradientTransform="matrix(0.404023 -0.405692 0.607375 0.602268 -27.0463 -3.00744)" gradientUnits="objectBoundingBox" x1="9.40879945977" y1="7.83968103847" x2="9.40879945977" y2="9.41157893758" spreadMethod="pad">
+ <stop stop-color="#565656" offset="0.00000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#9a9a9a" offset="0.50000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#545454" offset="1.00000000000" stop-opacity="1.00000000000"/>
+ </linearGradient>
+ <linearGradient id="gradient8" gradientTransform="matrix(0.404023 -0.405692 0.607375 0.602268 -27.0463 -3.00744)" gradientUnits="objectBoundingBox" x1="9.40879596769" y1="7.83968060850" x2="9.40879596769" y2="9.41157842140" spreadMethod="pad">
+ <stop stop-color="#b1b1b1" offset="0.00000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#ffffff" offset="0.50000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#8f8f8f" offset="1.00000000000" stop-opacity="1.00000000000"/>
+ </linearGradient>
+ <linearGradient id="gradient9" gradientTransform="matrix(0.37638 0.0361526 0.0367 0.374874 -2.47809 -12.1224)" gradientUnits="objectBoundingBox" x1="1.13556603092" y1="7.77791347304" x2="1.95960901921" y2="8.61454878179" spreadMethod="pad">
+ <stop stop-color="#dbce48" offset="0.00000000000" stop-opacity="1.00000000000"/>
+ <stop stop-color="#c5b625" offset="1.00000000000" stop-opacity="1.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="g3433">
+ <g id="layer1-3" transform="translate(0.0427701, 1.4e-07)" fill="none">
+ <path id="path2855" transform="translate(0.5, 0.5)" fill="url(#gradient0)" stroke="url(#gradient1)" stroke-width="1.00000012000" stroke-linecap="round" stroke-linejoin="round" d="M4.44089e-16 0L12.9572 0.01L12.9992 15L1e-08 15L0 1e-07Z"/>
+ <g id="g4198" transform="translate(3, 3)" fill="none">
+ <path id="path6035" transform="matrix(1.01954 0 0 1 0 -5.55112e-16)" fill="#686868" opacity="0.70000000000" d="M0 0L1.12407 0L1.12407 1L0 1Z"/>
+ <path id="path6033" transform="matrix(1.01954 0 0 1 1.30215 0)" fill="#686868" opacity="0.70000000000" d="M0 0L1.06061 0L1.06061 1L0 1Z"/>
+ <path id="path6031" transform="matrix(1.01954 0 0 1 2.5396 0)" fill="#686868" opacity="0.70000000000" d="M0 0L0.933684 0L0.933684 1L0 1Z"/>
+ <path id="path6029" transform="matrix(1.01954 0 0 1 3.64764 0)" fill="#686868" opacity="0.70000000000" d="M0 0L0.410114 0L0.410114 1L0 1Z"/>
+ <path id="path6027" transform="matrix(1.01954 0 0 1 4.22189 0)" fill="#686868" opacity="0.70000000000" d="M0 0L0.917815 0L0.917815 1L0 1Z"/>
+ <path id="path6025" transform="matrix(1.01954 0 0 1 5.31375 0)" fill="#686868" opacity="0.70000000000" d="M0 0L2.37746 0L2.37746 1L0 1Z"/>
+ <path id="path6017" transform="matrix(1.01954 0 0 1 5.55112e-16 6)" fill="#686868" opacity="0.70000000000" d="M0 0L1.12407 0L1.12407 1L0 1Z"/>
+ <path id="path6015" transform="matrix(1.01954 0 0 1 1.30215 6)" fill="#686868" opacity="0.70000000000" d="M0 0L1.06061 0L1.06061 1L0 1Z"/>
+ <path id="path6013" transform="matrix(1.01954 0 0 1 2.5396 6)" fill="#686868" opacity="0.70000000000" d="M0 0L0.933683 0L0.933683 1L0 1Z"/>
+ <path id="path6011" transform="matrix(1.01954 0 0 1 3.64764 6)" fill="#686868" opacity="0.70000000000" d="M0 0L0.410114 0L0.410114 1L0 1Z"/>
+ <path id="path6009" transform="matrix(1.01954 0 0 1 4.22188 6)" fill="#686868" opacity="0.70000000000" d="M0 0L0.917814 0L0.917814 1L0 1Z"/>
+ <path id="path5999" transform="matrix(1.01954 0 0 1 5.55112e-16 2)" fill="#686868" opacity="0.70000000000" d="M0 0L1.74283 0L1.74283 1L0 1Z"/>
+ <path id="path5997" transform="matrix(1.01954 0 0 1 1.93908 2)" fill="#686868" opacity="0.70000000000" d="M0 0L0.838487 0L0.838487 1L0 1Z"/>
+ <path id="path5995" transform="matrix(1.01954 0 0 1 2.95613 2)" fill="#686868" opacity="0.70000000000" d="M0 0L0.394245 0L0.394245 1L0 1Z"/>
+ <path id="path5993" transform="matrix(1.01954 0 0 1 3.52026 2)" fill="#686868" opacity="0.70000000000" d="M0 0L0.759159 0L0.759159 1L0 1Z"/>
+ <path id="path5991" transform="matrix(1.01954 0 0 1 4.45643 2)" fill="#686868" opacity="0.70000000000" d="M0 0L0.759159 0L0.759159 1L0 1Z"/>
+ <path id="path5989" transform="matrix(1.01954 0 0 1 5.39261 2)" fill="#686868" opacity="0.70000000000" d="M0 0L1.21926 0L1.21926 1L0 1Z"/>
+ <path id="path5981" transform="matrix(1.01954 0 0 1 5.55112e-16 4)" fill="#686868" opacity="0.70000000000" d="M0 0L1.99668 0L1.99668 1L0 1Z"/>
+ <path id="path5979" transform="matrix(1.01954 0 0 1 2.19529 4)" fill="#686868" opacity="0.70000000000" d="M0 0L2.13947 0L2.13947 1L0 1Z"/>
+ <path id="path5977" transform="matrix(1.01954 0 0 1 4.53615 4)" fill="#686868" opacity="0.70000000000" d="M0 0L0.854355 0L0.854355 1L0 1Z"/>
+ <path id="path5975" transform="matrix(1.01954 0 0 1 5.56679 4)" fill="#686868" opacity="0.70000000000" d="M0 0L2.01255 0L2.01255 1L0 1Z"/>
+ <path id="path2916" transform="matrix(1.01954 0 0 1 5.55112e-16 8)" fill="#686868" opacity="0.70000000000" d="M0 0L1.12407 0L1.12407 1L0 1Z"/>
+ <path id="path2918" transform="matrix(1.01954 0 0 1 1.30215 8)" fill="#686868" opacity="0.70000000000" d="M0 0L1.06061 0L1.06061 1L0 1Z"/>
+ <path id="path2920" transform="matrix(1.01954 0 0 1 2.5396 8)" fill="#686868" opacity="0.70000000000" d="M0 0L0.933683 0L0.933683 1L0 1Z"/>
+ </g>
+ <path id="path2422" transform="translate(4.99643, 5.67071)" fill="#0c0c0c" fill-rule="evenodd" opacity="0.15000000000" d="M8.25732 0.0337698C8.17317 -0.0162002 8.10805 -0.00320017 8.06503 0.0245698L2.33547 3.70464L1.28095 4.38385L1.24848 4.39759L0 7.23529L3.09408 7.32929L3.11977 7.31195L4.18107 6.63633L9.9092 2.92453C10.0813 2.81332 9.80913 2.07626 9.29872 1.26826C8.91592 0.662266 8.50978 0.183633 8.25732 0.0337318Z"/>
+ </g>
+ <g id="g8626" transform="translate(5.00026, 1.99974)" fill="none">
+ <path id="path3041" transform="matrix(0.702577 0 0 0.704261 1.4709 0.216394)" fill="url(#gradient2)" stroke="#0c0c0c" stroke-width="0.60797864000" stroke-linecap="butt" stroke-linejoin="round" d="M0 10.2519C0.286542 10.0442 1.14795 10.5083 1.96034 11.3138C2.77078 12.1174 3.22054 12.9551 3.0179 13.2444C3.01713 13.2455 3.03767 13.2621 3.03688 13.2632L13.1753 3.08288C13.4327 2.82441 12.9622 1.93926 12.1241 1.10813C11.2859 0.277 10.3959 -0.186931 10.1385 0.0715409Z"/>
+ <path id="path3045" transform="matrix(0.702577 0 0 0.704261 1.4709 2.44622)" fill="#0c0c0c" opacity="0.60000000000" d="M0 7.08568C0.286543 6.87797 1.14795 7.34207 1.96034 8.14762C2.77078 8.95125 3.22054 9.78895 3.0179 10.0782C3.01713 10.0793 3.03767 10.0959 3.03688 10.097L10.0189 3.08615L10.0442 3.0608C10.0449 3.05972 10.0244 3.04308 10.0252 3.04198C10.2278 2.75273 9.77807 1.91503 8.96762 1.1114C8.15523 0.305847 7.29382 -0.158247 7.00728 0.0494597L6.98203 0.0748147L4e-07 7.08568Z"/>
+ <path id="path3047" transform="matrix(0.702577 0 0 0.704261 6.48008 2.26929)" fill="url(#gradient3)" d="M0 0.176239C0.286541 -0.031468 1.14795 0.432627 1.96034 1.23818C2.77078 2.04181 3.22054 2.87951 3.0179 3.16876C3.01712 3.16986 3.03767 3.18649 3.03688 3.18758L3.16313 3.0608C3.16392 3.05972 3.14338 3.04308 3.14415 3.04198C3.3468 2.75273 2.89704 1.91503 2.0866 1.1114C1.27421 0.305848 0.412799 -0.158247 0.126257 0.0494596Z"/>
+ <path id="path3049" transform="matrix(0.702577 0 0 0.704261 6.39879 2.35112)" fill="url(#gradient4)" d="M0 0.176239C0.286544 -0.0314677 1.14795 0.432627 1.96034 1.23818C2.77078 2.04181 3.22054 2.87951 3.0179 3.16876C3.01712 3.16986 3.03767 3.1865 3.03688 3.18758L3.16313 3.0608C3.16392 3.05972 3.14338 3.04308 3.14415 3.04198C3.3468 2.75273 2.89704 1.91503 2.0866 1.1114C1.27421 0.305848 0.4128 -0.158247 0.126257 0.0494597Z"/>
+ <path id="path3051" transform="matrix(0.702577 0 0 0.704261 6.76258 1.98495)" fill="url(#gradient5)" d="M0 0.176239C0.286543 -0.0314683 1.14795 0.432626 1.96034 1.23818C2.77078 2.04181 3.22054 2.87951 3.0179 3.16876C3.01712 3.16986 3.03767 3.18649 3.03688 3.18758L3.16313 3.0608C3.16393 3.05972 3.14338 3.04308 3.14415 3.04198C3.3468 2.75273 2.89704 1.91503 2.0866 1.1114C1.27421 0.305847 0.412801 -0.158247 0.126257 0.0494597Z"/>
+ <path id="path3053" transform="matrix(0.702577 0 0 0.704261 6.68128 2.06678)" fill="url(#gradient6)" d="M0 0.176239C0.286544 -0.0314685 1.14795 0.432626 1.96034 1.23818C2.77078 2.04181 3.22054 2.87951 3.0179 3.16876C3.01713 3.16986 3.03767 3.18649 3.03688 3.18758L3.16313 3.0608C3.16392 3.05972 3.14338 3.04308 3.14415 3.04198C3.3468 2.75273 2.89704 1.91503 2.0866 1.1114C1.27421 0.305848 0.412801 -0.158247 0.126257 0.0494597Z"/>
+ <path id="path3055" transform="matrix(0.702577 0 0 0.704261 7.04643 1.69924)" fill="url(#gradient7)" d="M0 0.176238C0.286541 -0.0314683 1.14795 0.432626 1.96034 1.23818C2.77078 2.04181 3.22054 2.87951 3.0179 3.16876C3.01713 3.16986 3.03767 3.18649 3.03688 3.18758L3.16313 3.0608C3.16392 3.05972 3.14338 3.04308 3.14415 3.04198C3.3468 2.75273 2.89704 1.91503 2.08659 1.1114C1.27421 0.305847 0.412799 -0.158247 0.126256 0.0494597Z"/>
+ <path id="path3057" transform="matrix(0.702577 0 0 0.704261 6.96514 1.78107)" fill="url(#gradient8)" d="M0 0.176238C0.286541 -0.0314687 1.14795 0.432626 1.96034 1.23818C2.77078 2.04181 3.22054 2.87951 3.0179 3.16876C3.01712 3.16986 3.03767 3.18649 3.03688 3.18758L3.16313 3.0608C3.16392 3.05972 3.14338 3.04308 3.14415 3.04198C3.3468 2.75273 2.89704 1.91503 2.0866 1.1114C1.27421 0.305848 0.412799 -0.158247 0.126257 0.0494599Z"/>
+ <path id="path3059" transform="matrix(0.702577 0 0 0.704261 0.213987 7.40255)" fill="#dcdcdc" fill-rule="evenodd" stroke="#cccccc" stroke-width="0.60797864000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4.00000000000" d="M0 4.80486L4.77186 3.07937L4.81116 3.04011C5.01381 2.75087 4.55877 1.91283 3.74832 1.1092C2.93593 0.303642 2.07564 -0.158082 1.7891 0.0496248Z"/>
+ <path id="path3061" transform="matrix(0.702577 0 0 0.704261 0.213576 9.8794)" fill="#0c0c0c" fill-rule="evenodd" stroke="#0c0c0c" stroke-width="0.60797864000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4.00000000000" d="M0.485212 0L0 1.28269L1.30137 0.809298C1.18769 0.676163 1.08209 0.541463 0.947014 0.407525C0.791507 0.253326 0.639934 0.126858 0.485212 0Z"/>
+ <path id="path3043" transform="matrix(0.702577 0 0 0.704261 7.45448 0.214088)" fill="#c0c0c0" stroke="#cccccc" stroke-width="0.60797864000" stroke-linecap="butt" stroke-linejoin="round" opacity="0.80000000000" d="M0 1.70187C0.286542 1.49416 1.14795 1.95826 1.96034 2.76381C2.77078 3.56744 3.22054 4.40514 3.01789 4.69439C3.01713 4.69549 3.03766 4.71213 3.03688 4.71321L4.58881 3.15609C4.99729 2.74751 4.55953 2.06327 3.60744 1.1114C2.79505 0.305848 1.93364 -0.158247 1.6471 0.0494596L1.62185 0.0748155L3e-06 1.70187Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/apps/journal/img/justify-center.svg b/apps/journal/img/justify-center.svg
new file mode 100644
index 00000000000..7a3a7ffb97a
--- /dev/null
+++ b/apps/journal/img/justify-center.svg
@@ -0,0 +1,269 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg4198"
+ sodipodi:version="0.32"
+ inkscape:version="0.42+0.43pre1"
+ sodipodi:docbase="/home/andreas/projekt/bild/tango/scalable"
+ sodipodi:docname="format-justify-center.svg">
+ <defs
+ id="defs4200">
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3558">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3560" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3562" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3558"
+ id="radialGradient3564"
+ cx="22.571428"
+ cy="30.857143"
+ fx="22.571428"
+ fy="30.857143"
+ r="15.571428"
+ gradientTransform="matrix(1.000000,0.000000,0.000000,0.651376,4.638648e-15,10.75754)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient15218">
+ <stop
+ style="stop-color:#f8f8f7;stop-opacity:1;"
+ offset="0"
+ id="stop15220" />
+ <stop
+ id="stop2269"
+ offset="0.59928656"
+ style="stop-color:#e8e8e8;stop-opacity:1;" />
+ <stop
+ style="stop-color:#e2e2de;stop-opacity:1;"
+ offset="1"
+ id="stop15222" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15218"
+ id="linearGradient15224"
+ x1="22.308331"
+ y1="18.992140"
+ x2="35.785294"
+ y2="39.498238"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.060837,0.000000,0.000000,0.987595,4.641161,4.108291)" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2259">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop2261" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop2263" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2259"
+ id="linearGradient2265"
+ x1="26.076092"
+ y1="26.696676"
+ x2="30.811172"
+ y2="42.007351"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.993566,0.000000,0.000000,1.000000,6.219859,4.033411)" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2216">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop2218" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop2220" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2216"
+ id="linearGradient2222"
+ x1="36.8125"
+ y1="39.15625"
+ x2="39.0625"
+ y2="42.0625"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(6.161836,4.033411)" />
+ <linearGradient
+ id="linearGradient2224">
+ <stop
+ style="stop-color:#7c7c7c;stop-opacity:1;"
+ offset="0"
+ id="stop2226" />
+ <stop
+ style="stop-color:#b8b8b8;stop-opacity:1;"
+ offset="1"
+ id="stop2228" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15218"
+ id="linearGradient2240"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.342704,0.000000,0.000000,1.235378,-8.219611,-6.577189)"
+ x1="20.794008"
+ y1="18.378813"
+ x2="35.596001"
+ y2="39.60046" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#bebebe"
+ borderopacity="1.0000000"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.313708"
+ inkscape:cx="26.391074"
+ inkscape:cy="26.321697"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1280"
+ inkscape:window-height="885"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:showpageshadow="false" />
+ <metadata
+ id="metadata4203">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Justify Center</dc:title>
+ <dc:date>2005-10-29</dc:date>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Andreas Nilsson</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>justify</rdf:li>
+ <rdf:li>center</rdf:li>
+ <rdf:li>format</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="arc"
+ style="opacity:0.47368421;color:#000000;fill:url(#radialGradient3564);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="path3556"
+ sodipodi:cx="22.571428"
+ sodipodi:cy="30.857143"
+ sodipodi:rx="15.571428"
+ sodipodi:ry="10.142858"
+ d="M 38.142857 30.857143 A 15.571428 10.142858 0 1 1 7,30.857143 A 15.571428 10.142858 0 1 1 38.142857 30.857143 z"
+ transform="matrix(1.472335,0.000000,0.000000,0.266385,-9.159070,37.48203)" />
+ <rect
+ style="opacity:1;fill:url(#linearGradient2240);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4238"
+ width="38.996792"
+ height="45.003101"
+ x="4.5016017"
+ y="1.4968988"
+ rx="0.56650788"
+ ry="0.56650823" />
+ <rect
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4248"
+ width="26"
+ height="2"
+ x="-37"
+ y="10"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ y="16"
+ x="-35"
+ height="2"
+ width="23"
+ id="rect4250"
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4252"
+ width="21"
+ height="2"
+ x="-34"
+ y="22"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ y="28"
+ x="-37"
+ height="2"
+ width="26"
+ id="rect4254"
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4256"
+ width="17"
+ height="2"
+ x="-32"
+ y="34"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.99999982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect2245"
+ width="37.025566"
+ height="43.022316"
+ x="5.4997125"
+ y="2.4997177"
+ rx="0"
+ ry="0" />
+ </g>
+</svg>
diff --git a/apps/journal/img/justify-fill.svg b/apps/journal/img/justify-fill.svg
new file mode 100644
index 00000000000..f9a530bdae1
--- /dev/null
+++ b/apps/journal/img/justify-fill.svg
@@ -0,0 +1,269 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg4198"
+ sodipodi:version="0.32"
+ inkscape:version="0.42+0.43pre1"
+ sodipodi:docbase="/home/andreas/projekt/bild/tango/scalable"
+ sodipodi:docname="format-justify-fill.svg">
+ <defs
+ id="defs4200">
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3558">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3560" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3562" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3558"
+ id="radialGradient3564"
+ cx="22.571428"
+ cy="30.857143"
+ fx="22.571428"
+ fy="30.857143"
+ r="15.571428"
+ gradientTransform="matrix(1.000000,0.000000,0.000000,0.651376,4.638648e-15,10.75754)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient15218">
+ <stop
+ style="stop-color:#f8f8f7;stop-opacity:1;"
+ offset="0"
+ id="stop15220" />
+ <stop
+ id="stop2269"
+ offset="0.59928656"
+ style="stop-color:#e8e8e8;stop-opacity:1;" />
+ <stop
+ style="stop-color:#e2e2de;stop-opacity:1;"
+ offset="1"
+ id="stop15222" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15218"
+ id="linearGradient15224"
+ x1="22.308331"
+ y1="18.992140"
+ x2="35.785294"
+ y2="39.498238"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.060837,0.000000,0.000000,0.987595,4.641161,4.108291)" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2259">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop2261" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop2263" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2259"
+ id="linearGradient2265"
+ x1="26.076092"
+ y1="26.696676"
+ x2="30.811172"
+ y2="42.007351"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.993566,0.000000,0.000000,1.000000,6.219859,4.033411)" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2216">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop2218" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop2220" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2216"
+ id="linearGradient2222"
+ x1="36.8125"
+ y1="39.15625"
+ x2="39.0625"
+ y2="42.0625"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(6.161836,4.033411)" />
+ <linearGradient
+ id="linearGradient2224">
+ <stop
+ style="stop-color:#7c7c7c;stop-opacity:1;"
+ offset="0"
+ id="stop2226" />
+ <stop
+ style="stop-color:#b8b8b8;stop-opacity:1;"
+ offset="1"
+ id="stop2228" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15218"
+ id="linearGradient2240"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.342704,0.000000,0.000000,1.235378,-8.219611,-6.577189)"
+ x1="20.794008"
+ y1="18.378813"
+ x2="35.596001"
+ y2="39.60046" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#bebebe"
+ borderopacity="1.0000000"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="35.467019"
+ inkscape:cy="15.522316"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1280"
+ inkscape:window-height="885"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:showpageshadow="false" />
+ <metadata
+ id="metadata4203">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Justify Center</dc:title>
+ <dc:date>2005-10-29</dc:date>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Andreas Nilsson</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>justify</rdf:li>
+ <rdf:li>center</rdf:li>
+ <rdf:li>format</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="arc"
+ style="opacity:0.47368421;color:#000000;fill:url(#radialGradient3564);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="path3556"
+ sodipodi:cx="22.571428"
+ sodipodi:cy="30.857143"
+ sodipodi:rx="15.571428"
+ sodipodi:ry="10.142858"
+ d="M 38.142857 30.857143 A 15.571428 10.142858 0 1 1 7,30.857143 A 15.571428 10.142858 0 1 1 38.142857 30.857143 z"
+ transform="matrix(1.472335,0.000000,0.000000,0.266385,-9.159070,37.48203)" />
+ <rect
+ style="opacity:1;fill:url(#linearGradient2240);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4238"
+ width="38.996792"
+ height="45.003101"
+ x="4.5016017"
+ y="1.4968988"
+ rx="0.56650788"
+ ry="0.56650823" />
+ <rect
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4248"
+ width="30"
+ height="2"
+ x="-39"
+ y="10"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ y="16"
+ x="-39"
+ height="2"
+ width="30"
+ id="rect4250"
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4252"
+ width="30"
+ height="2"
+ x="-39"
+ y="22"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ y="28"
+ x="-39"
+ height="2"
+ width="30"
+ id="rect4254"
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4256"
+ width="30"
+ height="2"
+ x="-39"
+ y="34"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.99999982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect2245"
+ width="37.025566"
+ height="43.022316"
+ x="5.4997125"
+ y="2.4997177"
+ rx="0"
+ ry="0" />
+ </g>
+</svg>
diff --git a/apps/journal/img/justify-left.svg b/apps/journal/img/justify-left.svg
new file mode 100644
index 00000000000..ed5bab46516
--- /dev/null
+++ b/apps/journal/img/justify-left.svg
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg4198"
+ sodipodi:version="0.32"
+ inkscape:version="0.43+devel"
+ sodipodi:docbase="/home/tigert/cvs/freedesktop.org/tango-icon-theme/scalable/actions"
+ sodipodi:docname="format-justify-left.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs4200">
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3558">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3560" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3562" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3558"
+ id="radialGradient3564"
+ cx="22.571428"
+ cy="30.857143"
+ fx="22.571428"
+ fy="30.857143"
+ r="15.571428"
+ gradientTransform="matrix(1.000000,0.000000,0.000000,0.651376,4.638648e-15,10.75754)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient15218">
+ <stop
+ style="stop-color:#f8f8f7;stop-opacity:1;"
+ offset="0"
+ id="stop15220" />
+ <stop
+ id="stop2269"
+ offset="0.59928656"
+ style="stop-color:#e8e8e8;stop-opacity:1;" />
+ <stop
+ style="stop-color:#e2e2de;stop-opacity:1;"
+ offset="1"
+ id="stop15222" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15218"
+ id="linearGradient2240"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.342704,0.000000,0.000000,1.235378,-8.219611,-6.577189)"
+ x1="20.794008"
+ y1="18.378813"
+ x2="35.596001"
+ y2="39.60046" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#bebebe"
+ borderopacity="1.0000000"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.313708"
+ inkscape:cx="39.426031"
+ inkscape:cy="27.632093"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1280"
+ inkscape:window-height="885"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:showpageshadow="false" />
+ <metadata
+ id="metadata4203">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Justify Left</dc:title>
+ <dc:date>2005-10-29</dc:date>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Andreas Nilsson</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>justify</rdf:li>
+ <rdf:li>left</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="arc"
+ style="opacity:0.47368421;color:#000000;fill:url(#radialGradient3564);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="path3556"
+ sodipodi:cx="22.571428"
+ sodipodi:cy="30.857143"
+ sodipodi:rx="15.571428"
+ sodipodi:ry="10.142858"
+ d="M 38.142857 30.857143 A 15.571428 10.142858 0 1 1 7,30.857143 A 15.571428 10.142858 0 1 1 38.142857 30.857143 z"
+ transform="matrix(1.472335,0.000000,0.000000,0.266385,-9.159070,37.48203)" />
+ <rect
+ style="opacity:1;fill:url(#linearGradient2240);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4238"
+ width="38.996792"
+ height="45.003101"
+ x="4.5016017"
+ y="1.4968988"
+ rx="0.56650788"
+ ry="0.56650823" />
+ <rect
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4248"
+ width="27"
+ height="2"
+ x="9"
+ y="10" />
+ <rect
+ y="16"
+ x="9"
+ height="2"
+ width="25"
+ id="rect4250"
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <rect
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4252"
+ width="22.971531"
+ height="2"
+ x="9"
+ y="22" />
+ <rect
+ y="28"
+ x="9"
+ height="2"
+ width="27"
+ id="rect4254"
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
+ <rect
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4256"
+ width="17"
+ height="2"
+ x="9"
+ y="34" />
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.99999982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect2245"
+ width="37.025566"
+ height="43.022316"
+ x="5.4997125"
+ y="2.4997177"
+ rx="0"
+ ry="0" />
+ </g>
+</svg>
diff --git a/apps/journal/img/justify-right.svg b/apps/journal/img/justify-right.svg
new file mode 100644
index 00000000000..affb129539f
--- /dev/null
+++ b/apps/journal/img/justify-right.svg
@@ -0,0 +1,269 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg4198"
+ sodipodi:version="0.32"
+ inkscape:version="0.42+0.43pre1"
+ sodipodi:docbase="/home/andreas/projekt/bild/tango/scalable"
+ sodipodi:docname="format-justify-right.svg">
+ <defs
+ id="defs4200">
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3558">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop3560" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop3562" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3558"
+ id="radialGradient3564"
+ cx="22.571428"
+ cy="30.857143"
+ fx="22.571428"
+ fy="30.857143"
+ r="15.571428"
+ gradientTransform="matrix(1.000000,0.000000,0.000000,0.651376,4.638648e-15,10.75754)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient15218">
+ <stop
+ style="stop-color:#f8f8f7;stop-opacity:1;"
+ offset="0"
+ id="stop15220" />
+ <stop
+ id="stop2269"
+ offset="0.59928656"
+ style="stop-color:#e8e8e8;stop-opacity:1;" />
+ <stop
+ style="stop-color:#e2e2de;stop-opacity:1;"
+ offset="1"
+ id="stop15222" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15218"
+ id="linearGradient15224"
+ x1="22.308331"
+ y1="18.992140"
+ x2="35.785294"
+ y2="39.498238"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.060837,0.000000,0.000000,0.987595,4.641161,4.108291)" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2259">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop2261" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop2263" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2259"
+ id="linearGradient2265"
+ x1="26.076092"
+ y1="26.696676"
+ x2="30.811172"
+ y2="42.007351"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.993566,0.000000,0.000000,1.000000,6.219859,4.033411)" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2216">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop2218" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop2220" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2216"
+ id="linearGradient2222"
+ x1="36.8125"
+ y1="39.15625"
+ x2="39.0625"
+ y2="42.0625"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(6.161836,4.033411)" />
+ <linearGradient
+ id="linearGradient2224">
+ <stop
+ style="stop-color:#7c7c7c;stop-opacity:1;"
+ offset="0"
+ id="stop2226" />
+ <stop
+ style="stop-color:#b8b8b8;stop-opacity:1;"
+ offset="1"
+ id="stop2228" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient15218"
+ id="linearGradient2240"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.342704,0.000000,0.000000,1.235378,-8.219611,-6.577189)"
+ x1="20.794008"
+ y1="18.378813"
+ x2="35.596001"
+ y2="39.60046" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#bebebe"
+ borderopacity="1.0000000"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.313708"
+ inkscape:cx="31.951275"
+ inkscape:cy="22.207013"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1280"
+ inkscape:window-height="885"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ inkscape:showpageshadow="false" />
+ <metadata
+ id="metadata4203">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Justify Right</dc:title>
+ <dc:date>2005-10-29</dc:date>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Andreas Nilsson</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>justify</rdf:li>
+ <rdf:li>left</rdf:li>
+ <rdf:li>format</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="arc"
+ style="opacity:0.47368421;color:#000000;fill:url(#radialGradient3564);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="path3556"
+ sodipodi:cx="22.571428"
+ sodipodi:cy="30.857143"
+ sodipodi:rx="15.571428"
+ sodipodi:ry="10.142858"
+ d="M 38.142857 30.857143 A 15.571428 10.142858 0 1 1 7,30.857143 A 15.571428 10.142858 0 1 1 38.142857 30.857143 z"
+ transform="matrix(1.472335,0.000000,0.000000,0.266385,-9.159070,37.48203)" />
+ <rect
+ style="opacity:1;fill:url(#linearGradient2240);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:0.99999976;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4238"
+ width="38.996792"
+ height="45.003101"
+ x="4.5016017"
+ y="1.4968988"
+ rx="0.56650788"
+ ry="0.56650823" />
+ <rect
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4248"
+ width="27"
+ height="2"
+ x="-38.971531"
+ y="10"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ y="16"
+ x="-38.971531"
+ height="2"
+ width="25"
+ id="rect4250"
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4252"
+ width="22.971531"
+ height="2"
+ x="-38.971531"
+ y="22"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ y="28"
+ x="-38.971531"
+ height="2"
+ width="27"
+ id="rect4254"
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ style="opacity:1;color:#000000;fill:#999999;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
+ id="rect4256"
+ width="17"
+ height="2"
+ x="-38.971531"
+ y="34"
+ transform="scale(-1.000000,1.000000)" />
+ <rect
+ style="opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.99999982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect2245"
+ width="37.025566"
+ height="43.022316"
+ x="5.4997125"
+ y="2.4997177"
+ rx="0"
+ ry="0" />
+ </g>
+</svg>
diff --git a/apps/journal/img/list.svg b/apps/journal/img/list.svg
new file mode 100644
index 00000000000..b253c0ab50d
--- /dev/null
+++ b/apps/journal/img/list.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.8pt" height="12.8pt">
+<defs/>
+<g id="layer0">
+ <path id="path2407" transform="translate(6, 4.5)" fill="none" stroke="#000000" stroke-width="0.99999994000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" opacity="1" d="M0 4L8 4M0 0L8 0M0 8L8 8"/>
+ <rect id="rect3728" transform="translate(2, 3)" fill="#000000" opacity="1" width="2pt" height="2pt"/>
+ <rect id="rect2434" transform="translate(2, 11)" fill="#000000" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="0.00000000000" opacity="1" width="2pt" height="2pt"/>
+ <rect id="rect2440" transform="translate(2, 7)" fill="#000000" opacity="1" width="2pt" height="2pt"/>
+ </g>
+</svg>
diff --git a/apps/journal/img/strikethrough.svg b/apps/journal/img/strikethrough.svg
new file mode 100644
index 00000000000..528907cebe1
--- /dev/null
+++ b/apps/journal/img/strikethrough.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
+<defs/>
+<g id="layer5"/><g id="layer2">
+ <path id="path2211" transform="translate(0.833333, 0.627451)" fill="#000000" fill-rule="evenodd" stroke="#000000" stroke-width="1.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4.00000000000" d="M5.67248 0L0 14.3831L2.26899 14.3831L3.49803 11.2732L10.8723 11.2732L12.1013 14.3831L14.3703 14.3831L8.69781 0ZM7.18514 1.94366L10.1041 9.32958L4.26616 9.32958Z"/>
+ <rect id="rect2331" transform="translate(0.603318, 6.51584)" fill="#000000" fill-rule="evenodd" stroke="#000000" stroke-width="1.00000000000" stroke-linecap="round" stroke-linejoin="round" width="11.75746606335pt" height="0.48265460030pt"/>
+ </g>
+</svg>
diff --git a/apps/journal/img/underline.svg b/apps/journal/img/underline.svg
new file mode 100644
index 00000000000..534bed6ea24
--- /dev/null
+++ b/apps/journal/img/underline.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
+<defs/>
+<g id="layer1">
+ <path id="path2211" transform="translate(1.46667, 0.658333)" fill="#000000" fill-rule="evenodd" stroke="#000000" stroke-width="1.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4.00000000000" d="M5.17763 0L0 12.4667L2.07105 12.4667L3.19287 9.77117L9.92379 9.77117L11.0456 12.4667L13.1167 12.4667L7.93904 0ZM6.55833 1.68468L9.22264 8.08649L3.89399 8.08649Z"/>
+ <rect id="rect2331" transform="matrix(1 0 0 -1 1.23333 15)" fill="#000000" fill-rule="evenodd" stroke="#000000" stroke-width="1.00000000000" stroke-linecap="round" stroke-linejoin="round" width="10.90000000000pt" height="0.44664000000pt"/>
+ </g>
+</svg>
diff --git a/apps/journal/index.php b/apps/journal/index.php
new file mode 100644
index 00000000000..bfe882376e3
--- /dev/null
+++ b/apps/journal/index.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * ownCloud - Journal
+ *
+ * @author Thomas Tanghus
+ * @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
+ *
+ * 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/>.
+ *
+ */
+$errors = array();
+$l = new OC_L10N('journal');
+OCP\User::checkLoggedIn();
+
+$required_apps = array(
+ array('id' => 'tal', 'name' => 'TAL Page Templates'),
+ array('id' => 'journal', 'name' => 'Journal'),
+ array('id' => 'contacts', 'name' => 'Contacts'),
+);
+foreach($required_apps as $app) {
+ if(!OCP\App::isEnabled($app['id'])) {
+ $error = (string)$l->t('The %%s app isn\'t enabled! Please enable it here: <strong><a href="%%s?appid=%%s">Enable %%s app</a></strong>');
+ $errors[] = sprintf($error, $app['name'],OCP\Util::linkTo('settings', 'apps'), $app['id'], $app['name']);
+ }
+}
+
+if(count($errors) == 0) {
+ $calendars = OC_Calendar_Calendar::allCalendars(OCP\User::getUser(), true);
+ if( count($calendars) == 0 ) {
+ $error = (string)$l->t('You have no calendars. Please add one at the <strong><a href="%%s">Calendar app</a></strong>');
+ $errors[] = sprintf($error, OCP\Util::linkTo('calendar', 'index.php'));
+ }
+ // Load a specific entry?
+ $id = isset( $_GET['id'] ) ? $_GET['id'] : null;
+
+ OCP\Util::addScript('3rdparty/timepicker', 'jquery.ui.timepicker');
+ OCP\Util::addScript('contacts','jquery.multi-autocomplete');
+ OCP\Util::addScript('','oc-vcategories');
+ OCP\Util::addScript('journal', 'jquery.rte');
+ OCP\Util::addScript('journal', 'jquery.textchange');
+ OCP\Util::addScript('journal', 'journal');
+ OCP\Util::addscript('tal','modernizr');
+ OCP\Util::addStyle('3rdparty/timepicker', 'jquery.ui.timepicker');
+ OCP\Util::addStyle('journal', 'rte');
+ OCP\Util::addStyle('journal', 'journal');
+ OCP\App::setActiveNavigationEntry('journal_index');
+
+ $categories = OC_Calendar_App::getCategoryOptions();
+}
+
+//$tmpl = new OCP\Template('journal', 'journals', 'user');
+if($errors) {
+ $tmpl = new OCP\Template( "journal", "rtfm", "user" );
+ $tmpl->assign('errors',$errors, false);
+} else {
+ $tmpl = new OC_TALTemplate('journal', 'index', 'user');
+ $tmpl->assign('categories', $categories);
+ $tmpl->assign('calendars', $calendars);
+ $tmpl->assign('id',$id);
+}
+$tmpl->printPage();
diff --git a/apps/journal/js/expanding.js b/apps/journal/js/expanding.js
new file mode 100644
index 00000000000..17139f27fff
--- /dev/null
+++ b/apps/journal/js/expanding.js
@@ -0,0 +1,118 @@
+// Expanding Textareas
+// https://github.com/bgrins/ExpandingTextareas
+
+(function(factory) {
+ // Add jQuery via AMD registration or browser globals
+ if (typeof define === 'function' && define.amd) {
+ define([ 'jquery' ], factory);
+ }
+ else {
+ factory(jQuery);
+ }
+}(function ($) {
+ $.expandingTextarea = $.extend({
+ autoInitialize: true,
+ initialSelector: "textarea.expanding",
+ opts: {
+ resize: function() { }
+ }
+ }, $.expandingTextarea || {});
+
+ var cloneCSSProperties = [
+ 'lineHeight', 'textDecoration', 'letterSpacing',
+ 'fontSize', 'fontFamily', 'fontStyle',
+ 'fontWeight', 'textTransform', 'textAlign',
+ 'direction', 'wordSpacing', 'fontSizeAdjust',
+ 'wordWrap',
+ 'borderLeftWidth', 'borderRightWidth',
+ 'borderTopWidth','borderBottomWidth',
+ 'paddingLeft', 'paddingRight',
+ 'paddingTop','paddingBottom',
+ 'marginLeft', 'marginRight',
+ 'marginTop','marginBottom',
+ 'boxSizing', 'webkitBoxSizing', 'mozBoxSizing', 'msBoxSizing'
+ ];
+
+ var textareaCSS = {
+ position: "absolute",
+ height: "100%",
+ resize: "none"
+ };
+
+ var preCSS = {
+ visibility: "hidden",
+ border: "0 solid",
+ whiteSpace: "pre-wrap"
+ };
+
+ var containerCSS = {
+ position: "relative"
+ };
+
+ function resize() {
+ $(this).closest('.expandingText').find("div").text(this.value + ' ');
+ $(this).trigger("resize.expanding");
+ }
+
+ $.fn.expandingTextarea = function(o) {
+
+ var opts = $.extend({ }, $.expandingTextarea.opts, o);
+
+ if (o === "resize") {
+ return this.trigger("input.expanding");
+ }
+
+ if (o === "destroy") {
+ this.filter(".expanding-init").each(function() {
+ var textarea = $(this).removeClass('expanding-init');
+ var container = textarea.closest('.expandingText');
+
+ container.before(textarea).remove();
+ textarea
+ .attr('style', textarea.data('expanding-styles') || '')
+ .removeData('expanding-styles');
+ });
+
+ return this;
+ }
+
+ this.filter("textarea").not(".expanding-init").addClass("expanding-init").each(function() {
+ var textarea = $(this);
+
+ textarea.wrap("<div class='expandingText'></div>");
+ textarea.after("<pre class='textareaClone'><div></div></pre>");
+
+ var container = textarea.parent().css(containerCSS);
+ var pre = container.find("pre").css(preCSS);
+
+ // Store the original styles in case of destroying.
+ textarea.data('expanding-styles', textarea.attr('style'));
+ textarea.css(textareaCSS);
+
+ $.each(cloneCSSProperties, function(i, p) {
+ var val = textarea.css(p);
+
+ // Only set if different to prevent overriding percentage css values.
+ if (pre.css(p) !== val) {
+ pre.css(p, val);
+ }
+ });
+
+ textarea.bind("input.expanding propertychange.expanding", resize);
+ resize.apply(this);
+
+ if (opts.resize) {
+ textarea.bind("resize.expanding", opts.resize);
+ }
+ });
+
+ return this;
+ };
+
+ $(function () {
+ if ($.expandingTextarea.autoInitialize) {
+ $($.expandingTextarea.initialSelector).expandingTextarea();
+ }
+ });
+
+}));
diff --git a/apps/journal/js/journal.js b/apps/journal/js/journal.js
new file mode 100644
index 00000000000..2611b09c550
--- /dev/null
+++ b/apps/journal/js/journal.js
@@ -0,0 +1,432 @@
+String.prototype.unEscape = function(){
+ str = this;
+ return str.replace(/\\"/g, '"');
+};
+String.prototype.stripTags = function(){
+ tags = this;
+ stripped = tags.replace(/<(.|\n)*?>/g, '');
+ return stripped;
+};
+String.prototype.zeroPad = function(digits) {
+ n = this.toString();
+ while (n.length < digits) {
+ n = '0' + n;
+ }
+ return n;
+}
+
+OC.Journal = {
+ init:function() {
+ this.setEnabled(false);
+ // Fetch journal entries. If it's a direct link 'id' will be loaded.
+ OC.Journal.Journals.update(id);
+ },
+ categoriesChanged:function(newcategories) { // Categories added/deleted.
+ categories = $.map(newcategories, function(v) {return v;});
+ $('#categories').multiple_autocomplete('option', 'source', categories);
+ var categorylist = $('#categories_value').find('input');
+ $.getJSON(OC.filePath('journal', 'ajax', 'categories/categoriesfor.php'),{'id':Contacts.UI.Card.id},function(jsondata){
+ if(jsondata.status == 'success'){
+ $('#categories_value').data('checksum', jsondata.data.checksum);
+ categorylist.val(jsondata.data.value);
+ } else {
+ OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
+ }
+ });
+ },
+ propertyContainerFor:function(obj) {
+ if($(obj).hasClass('propertycontainer')) {
+ return $(obj);
+ }
+ return $(obj).parents('.propertycontainer').first();
+ },
+ required:function(event){ // eventhandler for required elements
+ // FIXME: This doesn't seem to work.
+ console.log('blur on required');
+ var obj = $(event.target);
+ $(obj).addClass('required');
+ if($(this).val().trim() == '') {
+ $('<strong>This field is required!</strong>').appendTo($(obj));
+ return;
+ } else {
+ $(obj).removeClass('required');
+ $(obj).off('blur', OC.Journal.required);
+ }
+ },
+ setEnabled:function(state) {
+ if(state == undefined) { state = true; }
+ console.log('OC.Journal.setEnabled: ' + state);
+ if(state) {
+ $('#description').rte('setEnabled', true);
+ if($('#description').rte('mode') == 'html') {
+ $('#editortoolbar li').show();
+ }
+ $('#togglemode').show();
+ $('#summary').addClass('editable');
+ $('.property,#also_time').each(function () {
+ $(this).attr('disabled', false);
+ });
+ } else {
+ $('#description').rte('setEnabled', false);
+ $('#editortoolbar .richtext, #togglemode').hide();
+ $('#summary').removeClass('editable');
+ $('.property,#also_time').each(function () {
+ $(this).attr('disabled', true);
+ });
+ }
+ },
+ toggleMode:function() {
+ console.log('togglemode');
+ $('#description').rte('toggleMode');
+ $('#editortoolbar li.richtext').toggle();
+ },
+ Entry:{
+ id:'',
+ data:undefined,
+ add:function() {
+ // TODO: wrap a DIV around the summary field with a suggestion(?) to fill out this field first. See OC.Journal.required
+ // Remember to reenable all controls.
+ $('#leftcontent lidata-id="'+this.id+'"').removeClass('active');
+ this.id = 'new';
+ this.data = undefined;
+ $('.property').each(function () {
+ switch($(this).get(0).nodeName) {
+ case 'DIV':
+ $(this).html('');
+ break;
+ case 'INPUT':
+ case 'TEXTAREA':
+ $(this).val('');
+ break;
+ default:
+ console.log('OC.Journal.Entry.add. Forgot: ' + $(this).get(0).nodeName);
+ break;
+ }
+ });
+ $('#description').rte('setEnabled', false);
+ $('#editortoolbar li.richtext').hide();
+ $('#editable').attr('checked', true);
+ OC.Journal.setEnabled(true);
+ },
+ createEntry:function(data) {
+ var date = new Date(parseInt(data.dtstart)*1000);
+ var timestring = (data.only_date?'':' ' + date.toLocaleTimeString());
+ return $('<li data-id="'+data.id+'"><a href="'+OC.linkTo('journal', 'index.php')+'&id='+data.id+'">'+data.summary.unEscape()+'</a><br /><em>'+date.toDateString()+timestring+'<em></li>').data('entry', data);
+ },
+ loadEntry:function(id, data) {
+ //$(document).off('change', '.property');
+ console.log('loadEntry: ' + id + ': ' + data.summary);
+ this.id = id;
+ this.data = data;
+ $('#entry').data('id', id);
+ console.log('summary: ' + data.summary.unEscape());
+ $('#summary').val(data.summary.unEscape());
+ $('#organizer').val(data.organizer.value.split(':')[1]);
+ var format = data.description.format;
+ console.log('format: '+format);
+ $('#description').rte(format, data.description.value.unEscape());
+ $('#description').rte('mode', format);
+ //$('#description').expandingTextarea('resize');
+ (format=='html'&&$('#editable').get(0).checked?$('#editortoolbar li.richtext').show():$('#editortoolbar li.richtext').hide());
+ $('#location').val(data.location);
+ $('#categories').val(data.categories.join(','));
+ $('#categories').multiple_autocomplete('option', 'source', categories);
+ console.log('Trying to parse: '+data.dtstart);
+ var date = new Date(parseInt(data.dtstart)*1000);
+ //$('#dtstartdate').val(date.getDate()+'-'+date.getMonth()+'-'+date.getFullYear()); //
+ $('#dtstartdate').datepicker('setDate', date);
+ if(data.only_date) {
+ $('#dtstarttime').hide();
+ $('#also_time').attr('checked', false);
+ //$('#also_time').get(0).checked = false;
+ } else {
+ // timepicker('setTime', ...) triggers a 'change' event, so you have to jump through hoops here ;-)
+ $('#dtstarttime').val(date.getHours().toString().zeroPad(2)+':'+date.getMinutes().toString().zeroPad(2));
+ $('#dtstarttime').show();
+ $('#also_time').attr('checked', true);
+ //$('#also_time').get(0).checked = true;
+ }
+ console.log('dtstart: '+date);
+ },
+ saveproperty:function(obj) {
+ if(!this.id) { // we are adding an entry and want a response back from the server.
+ this.id = 'new';
+ console.log('OC.Journal.Entry.saveproperty: We need to add a new one.');
+ //return;
+ }
+ var container = OC.Journal.propertyContainerFor(obj);
+ var params = {'id':this.id};
+ params['type'] = container.data('type');
+ params['parameters'] = {};
+ switch(params['type']) {
+ case 'ORGANIZER':
+ case 'LOCATION':
+ case 'CATEGORIES':
+ params['value'] = $(obj).val();
+ break;
+ case 'SUMMARY':
+ if(this.id == 'new' && $(obj).val().trim() == '') {
+ $(obj).focus();
+ $(obj).addClass('required');
+ $(obj).on('blur', OC.Journal.required);
+ return;
+ }
+ params['value'] = $(obj).val();
+ break;
+ case 'DESCRIPTION':
+ // Check if we get the description from the textarea or the contenteditable.
+ var format = ($(obj).get(0).nodeName == 'DIV' ? 'html' : 'text'); // FIXME: should check rte instead.
+ var value = $('#description').rte(format); // calls either the 'text' or 'html' method of the rte.
+ //var value = ($(obj).get(0).nodeName == 'DIV' ? $(obj).html() : $(obj).text());
+ console.log('nodeName: ' + $(obj).get(0).nodeName);
+ params['value'] = value;
+ params['parameters']['FORMAT'] = format.toUpperCase();
+ break;
+ case 'DTSTART':
+ var date = $('#dtstartdate').val();
+ var time = $('#dtstarttime').val();
+ var datetime = new Date(parseInt(date.substring(6, 10)), parseInt(date.substring(3, 5)), parseInt(date.substring(0, 2)) , parseInt(time.substring(0, 2)), parseInt(time.substring(3, 5)), 0, 0);
+ params['value'] = datetime.getTime()/1000;
+ break;
+ default:
+ $.extend(1, $(obj).serializeArray(), params);
+ break;
+ }
+ self = this;
+ $.post(OC.filePath('journal', 'ajax', 'saveproperty.php'), params, function(jsondata) {
+ if(jsondata.status == 'success') {
+ if(self.id == 'new') {
+ self.loadEntry(jsondata.data.id, jsondata.data);
+ } else {
+ $('#leftcontent li[data-id="'+self.id+'"]').remove();
+ }
+ var item = self.createEntry(jsondata.data);
+ $('#leftcontent').append(item);
+ OC.Journal.Journals.doSort();
+ OC.Journal.Journals.scrollTo(self.id);
+ // add error checking
+ console.log('successful save');
+ } else {
+ OC.dialogs.alert(jsondata.data.message.text, t('contacts', 'Error'));
+ }
+ });
+ },
+ doExport:function() {
+ document.location.href = OC.linkTo('calendar', 'export.php') + '?eventid=' + this.id;
+ },
+ doDelete:function() {
+ // TODO: Do something when there are no more entries.
+ if(this.id == 'new') { return; }
+ $('#delete').tipsy('hide');
+ self = this;
+ OC.dialogs.confirm(t('contacts', 'Are you sure you want to delete this entry?'), t('journal', 'Warning'), function(answer) {
+ if(answer == true) {
+ $.post(OC.filePath('journal', 'ajax', 'delete.php'), {'id': self.id}, function(jsondata) {
+ if(jsondata.status == 'success') {
+ var curlistitem = $('#leftcontent li[data-id="'+self.id+'"]');
+ var newlistitem = curlistitem.prev('li');
+ if(!$(newlistitem).is('li')) {
+ newlistitem = curlistitem.next('li');
+ }
+ if(!$(newlistitem).is('li')) {
+ alert('No more entries. Do something!!!');
+ }
+ $(newlistitem).addClass('active');
+ console.log('newlistitem: ' + newlistitem.toString());
+ curlistitem.remove();
+ var data = newlistitem.data('entry');
+ self.loadEntry(data.id, data);
+ console.log('successful delete');
+ } else {
+ OC.dialogs.alert(jsondata.data.message.text, t('contacts', 'Error'));
+ }
+ });
+ }
+ });
+ },
+ },
+ Journals:{
+ sortmethod:undefined,
+ doSort:function(method) {
+ if(method) {
+ this.sortmethod = method;
+ } else {
+ method = this.sortmethod;
+ }
+ // Thanks to http://www.java2s.com/Tutorial/JavaScript/0220__Array/Usinganalphabeticalsortmethodonstrings.html
+ // and http://stackoverflow.com/questions/4258974/sort-list-based-on-data-attribute-using-jquery-metadata-plugin#4259074
+ // and http://stackoverflow.com/questions/8882418/jquery-sorting-lib-that-supports-multilanguage-sorting
+ compareDateTimeAsc = function(a, b){
+ return (parseInt(a.dtstart) > parseInt(b.dtstart)?-1:1);
+ }
+ compareDateTimeDesc = function(a, b){
+ return (parseInt(b.dtstart) < parseInt(a.dtstart)?-1:1);
+ }
+ compareSummaryAsc = function(a, b){
+ return b.summary.toLowerCase().localeCompare(a.summary.toLowerCase());
+ }
+ compareSummaryDesc = function(a, b){
+ return a.summary.toLowerCase().localeCompare(b.summary.toLowerCase());
+ }
+ var func;
+ switch(method) {
+ case 'dtasc':
+ func = compareDateTimeAsc;
+ break;
+ case 'dtdesc':
+ func = compareDateTimeDesc;
+ break;
+ case 'sumasc':
+ func = compareSummaryAsc;
+ break;
+ case 'sumdesc':
+ func = compareSummaryDesc;
+ break;
+ default:
+ var func = compareDateTimeDesc;
+ break;
+ }
+
+ var arr = []
+ // loop through each list item and get the metadata
+ $('#leftcontent li').each(function () {
+ var meta = $(this).data('entry');
+ meta.elem = $(this);
+ arr.push(meta);
+ });
+ arr.sort(func);
+
+ //Foreach item append it to the container. The first i arr will then end up in the top
+ $.each(arr, function(index, item){
+ item.elem.appendTo(item.elem.parent());
+ });
+ },
+ update:function(id) {
+ console.log('update: ' + id);
+ self = this;
+ $('#leftcontent').addClass('loading');
+ $.getJSON(OC.filePath('journal', 'ajax', 'entries.php'), function(jsondata) {
+ if(jsondata.status == 'success') {
+ var entries = $('#leftcontent').empty();
+ $(jsondata.data.entries).each(function(i, entry) {
+ entries.append(OC.Journal.Entry.createEntry(entry));
+ });
+ $('#leftcontent').removeClass('loading');
+ self.doSort('dtasc');
+ console.log('Count: ' + $('#leftcontent li').length);
+ if($('#leftcontent li').length > 0 ){
+ var firstitem;
+ if(id) {
+ firstitem = $('#leftcontent li[data-id="'+id+'"]');
+ } else {
+ firstitem = $('#leftcontent li').first();
+ id = firstitem.data('entry').id;
+ }
+ firstitem.addClass('active');
+ self.scrollTo(id);
+ OC.Journal.Entry.loadEntry(firstitem.data('id'), firstitem.data('entry'));
+ }
+ } else {
+ OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error'));
+ }
+ });
+ },
+ scrollTo:function(id){
+ var item = $('#leftcontent li[data-id="'+id+'"]');
+ if(item) {
+ try {
+ $('#leftcontent').animate({scrollTop: $('#leftcontent li[data-id="'+id+'"]').offset().top-70}, 'slow','swing');
+ } catch(e) {}
+ }
+ }
+ }
+};
+
+$(document).ready(function(){
+ OCCategories.changed = OC.Journal.categoriesChanged;
+ OCCategories.app = 'calendar';
+
+ // Initialize controls.
+ $('#categories').multiple_autocomplete({source: categories});
+ //$('#categories').multiple_autocomplete('option', 'source', categories);
+ $('#dtstartdate').datepicker({dateFormat: 'dd-mm-yy'});
+ $('#dtstarttime').timepicker({timeFormat: 'hh:mm', showPeriodLabels:false});
+ $('#description').rte({classes: ['property','content']});
+ $('.tip').tipsy();
+
+ OC.Journal.init();
+
+ // Show the input with a direcy link the journal entry, binds an event to close
+ // it on blur and removes the binding again afterwards.
+ $('#showlink').on('click', function(event){
+ console.log('showlink');
+ $('#link').toggle('slow').val(totalurl+'&id='+OC.Journal.Entry.id).focus().
+ on('blur',function(event) {$(this).hide()}).off('blur', $(this));
+ return false;
+ });
+
+ $('#rightcontent').on('change', '.property', function(event){
+ OC.Journal.Entry.saveproperty(this);
+ });
+
+ $('#controls').on('click', '#add', function(event){
+ OC.Journal.Entry.add();
+ });
+
+ $('#metadata').on('change', '#also_time', function(event){
+ $('#dtstarttime').toggle().trigger('change');
+ });
+
+ $('#metadata').on('click', '#export', function(event){
+ OC.Journal.Entry.doExport();
+ });
+
+ $('#metadata').on('click', '#editcategories', function(event){
+ OCCategories.edit();
+ });
+
+ $('#metadata').on('click', '#delete', function(event){
+ OC.Journal.Entry.doDelete();
+ });
+
+ $('#controls').on('change', '#entrysort', function(event){
+ OC.Journal.Journals.doSort($(this).val());
+ });
+
+ // Proxy click.
+ $('#leftcontent').on('keydown', '#leftcontent', function(event){
+ if(event.which == 13) {
+ $('#leftcontent').click(event);
+ }
+ });
+ // Journal entry clicked
+ $(document).on('click', '#leftcontent', function(event){
+ var $tgt = $(event.target);
+ var item = $tgt.is('li')?$($tgt):($tgt).parents('li').first();
+ var id = item.data('id');
+ item.addClass('active');
+ var oldid = $('#entry').data('id');
+ console.log('oldid: ' + oldid);
+ if(oldid != 0){
+ $('#leftcontent li[data-id="'+oldid+'"]').removeClass('active');
+ }
+ OC.Journal.Entry.loadEntry(id, item.data('entry'));
+ return false;
+ });
+ // Editor command.
+ $('.rte-toolbar button').on('click', function(event){
+ console.log('cmd: ' + $(this).data('cmd'));
+ $('#description').rte('formatText', $(this).data('cmd'));
+ event.preventDefault();
+ return false;
+ });
+ // Toggle text/html editing mode.
+ $('#togglemode').on('click', function(event){
+ OC.Journal.toggleMode(true);
+ return false;
+ });
+ $('#editable').on('change', function(event){
+ OC.Journal.setEnabled($(this).get(0).checked);
+ });
+
+});
diff --git a/apps/journal/js/jquery.rte.js b/apps/journal/js/jquery.rte.js
new file mode 100644
index 00000000000..ca7b61d719c
--- /dev/null
+++ b/apps/journal/js/jquery.rte.js
@@ -0,0 +1,244 @@
+// http://wiki.jqueryui.com/w/page/12138135/Widget%20factory
+
+(function( $ ) {
+
+$.widget( 'ui.rte', {
+ // These options will be used as defaults
+ options: {
+ disabled: true,
+ mode: 'html'
+ },
+ // Set up the widget
+ _create: function() {
+ console.log('_create');
+ var self = this,
+ dirty = false,
+ textarea = this.element; //.hide(),
+ pos = textarea.position();
+ //this.element.text = this.text;
+ this.mirror = $('<div class="rte-content" contenteditable="true" style="top:'+pos.top+';left:'+pos.left+';" ></div>').insertAfter(textarea).show();
+ textarea.hide();
+ this.formatText('styleWithCSS', true);
+ this.validtags = ['A','P','STRONG', 'B', 'I', 'SPAN', 'DIV', 'OL', 'UL', 'LI', 'DL', 'DT', 'DD'];
+ this.enabled = true;
+
+ /*$(window).resize(function() {
+ console.log('resize: ');
+ });*/
+
+ this.mirror.keydown(function(event){
+ if(event.which == 13) {
+ self.insertAtCaret('<br />');
+ }
+ });
+ this.mirror.keyup(function() {
+ console.log('keyup, set dirty.');
+ self.dirty = true;
+ });
+ this.mirror.blur(function() {
+ console.log('blur: ');
+ if(self.dirty) {
+ self.mirror.trigger('change');
+ self.dirty = false;
+ }
+ });
+
+ },
+ _init: function() {
+ console.log('_init');
+ self = this;
+ $.each(this.options, function(key, value) {
+ self._setOption(key, value);
+ });
+ },
+ text: function(str) {
+ console.log('function text');
+ if(str != undefined) {
+ this.mirror.html(str);
+ this.element.text(str);
+ } else {
+ console.log('returning: ' + this.element.val());
+ return this.element.val();
+ }
+ },
+ html: function(str) {
+ console.log('function html');
+ if(str != undefined) {
+ console.log('str: ' + str);
+ var $str;
+ try {
+ $str = $(str);
+ this.mirror.empty().html($str); // Call empty() for IE 8.
+ this.element.text($str.text());
+ } catch(e) {
+ console.log(e.message);
+ this.mirror.empty().html(str);
+ this.element.text(str);
+ }
+ //console.log('length: ' + $str.length);
+ //this.mirror.get(0).contenteditable=false;
+ } else {
+ console.log('returning: ' + this.mirror.html());
+ return this.mirror.html();
+ }
+ },
+ insertAtCaret: function(myValue){
+ // Found this at stackoverflow
+ return this.mirror.each(function(i) {
+ if (document.selection) {
+ console.log('IE');
+ //For browsers like Internet Explorer
+ this.focus();
+ sel = document.selection.createRange();
+ sel.text = myValue;
+ this.focus();
+ }
+ else if (this.selectionStart || this.selectionStart == '0') {
+ console.log('FF');
+ //For browsers like Firefox and Webkit based
+ var startPos = this.selectionStart;
+ var endPos = this.selectionEnd;
+ var scrollTop = this.scrollTop;
+ this.value = this.value.substring(0, startPos)+myValue+this.value.substring(endPos,this.value.length);
+ this.focus();
+ this.selectionStart = startPos + myValue.length;
+ this.selectionEnd = startPos + myValue.length;
+ this.scrollTop = scrollTop;
+ } else {
+ console.log('Smth.');
+ this.value += myValue;
+ this.focus();
+ }
+ })
+ },
+ showSelection: function() {
+ var textComponent = this.mirror.get(0); //document.getElementById('Editor');
+ var selectedText;
+ // IE version
+ if (document.selection != undefined) {
+ textComponent.focus();
+ var sel = document.selection.createRange();
+ selectedText = sel.text;
+ }
+ // Mozilla version
+ else if (textComponent.selectionStart != undefined) {
+ var startPos = textComponent.selectionStart;
+ var endPos = textComponent.selectionEnd;
+ selectedText = textComponent.value.substring(startPos, endPos)
+ }
+ alert("You selected: " + selectedText);
+ },
+ formatText: function(command, option) {
+ self = this, useDialog = false;
+ switch(command) {
+ case 'ulist':
+ command = 'insertUnorderedList';
+ break;
+ case 'olist':
+ command = 'insertOrderedList';
+ break;
+ case 'createlink':
+ self.showSelection();
+ option=prompt('Write the URL here')
+ useDialog = true;
+ default:
+ break;
+ }
+ try{
+ document.execCommand(command, useDialog, option);
+ self.dirty = true; // FIXME: This doesn't work because blur is triggered before dirty is set.
+ self.mirror.trigger('blur'); // Dirty hack to trigger save. Hmm, if it only worked...
+ }catch(e){
+ console.log('Error: ' + e)
+ }
+ },
+ setEnabled: function(state) {
+ console.log('function setEnabled: ' + state);
+ if(state != undefined) {
+ this._setOption('disabled', !state);
+ }
+ return this.options['disabled'];
+ },
+ mode: function(mode) {
+ if(mode != undefined) {
+ this._setOption('mode', mode);
+ }
+ return this.options['mode'];
+ },
+ /*toggle: function() {
+ this._setOption('disabled', !this.options['disabled']);
+ return !this.options['disabled'];
+ },*/
+ toggleMode: function() {
+ this._setOption('mode', (this.options['mode'] == 'html'?'text':'html'));
+ return this.options['mode'];
+ },
+ // Use the _setOption method to respond to changes to options
+ _setOption: function( key, value ) {
+ console.log('option ' + key + ': ' + value);
+ switch( key ) {
+ case 'disabled':
+ if(value) {
+ this.mirror.get(0).contenteditable = false;
+ this.mirror.attr('contenteditable', false);
+ this.mirror.removeClass('editable');
+ this.element.attr('disabled', true);
+ this.element.removeClass('editable');
+ } else {
+ this.mirror.get(0).contenteditable = true;
+ this.mirror.attr('contenteditable', true);
+ this.mirror.addClass('editable');
+ this.element.attr('disabled', false);
+ this.element.addClass('editable');
+ }
+ break;
+ case 'mode':
+ switch(value) {
+ case 'html':
+ this.mirror.show();
+ this.element.hide();
+ break;
+ case 'text':
+ this.mirror.hide();
+ this.element.show();
+ this.element.trigger('resize');
+ break;
+ default:
+ throw { name: 'UnknownMode', message: 'Invalid mode: ' + value }
+ break;
+ }
+ break;
+ case 'classes':
+ if($.isArray(value)) {
+ var mirror = this.mirror;
+ $.each(this.options['classes'], function(key, value) {
+ mirror.addClass(value);
+ });
+ } else {
+ this.mirror.addClass(value);
+ }
+ break;
+ default:
+ this.options[key] = value;
+ break;
+ }
+ // In jQuery UI 1.8, you have to manually invoke the _setOption method from the base widget
+
+ $.Widget.prototype._setOption.apply( this, arguments );
+ // In jQuery UI 1.9 and above, you use the _super method instead
+ //this._super( "_setOption", key, value );
+ },
+ // Use the destroy method to clean up any modifications your widget has made to the DOM
+ destroy: function() {
+ this.mirror.remove();
+ this.element.show();
+ // In jQuery UI 1.8, you must invoke the destroy method from the base widget
+ $.Widget.prototype.destroy.call( this );
+ // In jQuery UI 1.9 and above, you would define _destroy instead of destroy and not call the base method
+ }
+
+});
+
+}( jQuery ) );
+
+
diff --git a/apps/journal/js/jquery.textchange.js b/apps/journal/js/jquery.textchange.js
new file mode 100644
index 00000000000..66a07fc5bcd
--- /dev/null
+++ b/apps/journal/js/jquery.textchange.js
@@ -0,0 +1,76 @@
+/*!
+ * jQuery TextChange Plugin
+ * http://www.zurb.com/playground/jquery-text-change-custom-event
+ *
+ * Copyright 2010, ZURB
+ * Released under the MIT License
+ */
+(function ($) {
+
+ $.event.special.textchange = {
+
+ setup: function (data, namespaces) {
+ $(this).data('lastValue', this.contentEditable === 'true' ? $(this).html() : $(this).val());
+ $(this).bind('keyup.textchange', $.event.special.textchange.handler);
+ $(this).bind('cut.textchange paste.textchange input.textchange', $.event.special.textchange.delayedHandler);
+ },
+
+ teardown: function (namespaces) {
+ $(this).unbind('.textchange');
+ },
+
+ handler: function (event) {
+ $.event.special.textchange.triggerIfChanged($(this));
+ },
+
+ delayedHandler: function (event) {
+ var element = $(this);
+ setTimeout(function () {
+ $.event.special.textchange.triggerIfChanged(element);
+ }, 25);
+ },
+
+ triggerIfChanged: function (element) {
+ var current = element[0].contentEditable === 'true' ? element.html() : element.val();
+ if (current !== element.data('lastValue')) {
+ element.trigger('textchange', [element.data('lastValue')]);
+ element.data('lastValue', current);
+ }
+ }
+ };
+
+ $.event.special.hastext = {
+
+ setup: function (data, namespaces) {
+ $(this).bind('textchange', $.event.special.hastext.handler);
+ },
+
+ teardown: function (namespaces) {
+ $(this).unbind('textchange', $.event.special.hastext.handler);
+ },
+
+ handler: function (event, lastValue) {
+ if ((lastValue === '') && lastValue !== $(this).val()) {
+ $(this).trigger('hastext');
+ }
+ }
+ };
+
+ $.event.special.notext = {
+
+ setup: function (data, namespaces) {
+ $(this).bind('textchange', $.event.special.notext.handler);
+ },
+
+ teardown: function (namespaces) {
+ $(this).unbind('textchange', $.event.special.notext.handler);
+ },
+
+ handler: function (event, lastValue) {
+ if ($(this).val() === '' && $(this).val() !== lastValue) {
+ $(this).trigger('notext');
+ }
+ }
+ };
+
+})(jQuery); \ No newline at end of file
diff --git a/apps/journal/js/settings.js b/apps/journal/js/settings.js
new file mode 100644
index 00000000000..8cd1b25f6e7
--- /dev/null
+++ b/apps/journal/js/settings.js
@@ -0,0 +1,15 @@
+$(document).ready(function(){
+ $('#journal_calendar').on('change', function(event){
+ $.post(OC.filePath('journal', 'ajax', 'setdefaultcalendar.php'), {'id':$('#journal_calendar option:selected').val()}, function(jsondata) {
+ var success = {padding: 0.5em, background-color:green, color: white, font-weight: bold, float: left};
+ var failure = {padding: 0.5em, background-color:red, color: white, font-weight: bold, float: left};
+ if(jsondata.status == 'success') {
+ $('#journal_status');
+ $('#journal_status').css(success).html(t('journal', 'Saved')).fadeIn().fadeOut(5000);
+ } else {
+ $('#journal_status').css(failure);
+ $('#journal_status').html(t('journal', 'Error saving: ')+jsondata.data.message).fadeIn().fadeOut(5000);
+ }
+ });
+ });
+});
diff --git a/apps/journal/lib/app.php b/apps/journal/lib/app.php
new file mode 100644
index 00000000000..d3d9b3bf114
--- /dev/null
+++ b/apps/journal/lib/app.php
@@ -0,0 +1,135 @@
+<?php
+/**
+ * ownCloud - Journal
+ *
+ * @author Bart Visscher
+ * Copyright (c) 2012 Bart Visscher <bartv@thisnet.nl>
+ * @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
+ *
+ * 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/>.
+ *
+ */
+
+/**
+ * This class manages our journal.
+ */
+OC_Journal_App::$l10n = new OC_L10N('journal');
+class OC_Journal_App {
+ public static $l10n;
+ /*
+ * @brief categories of the user
+ */
+ protected static $categories = null;
+
+ public static function arrayForJSON($id, $vjournal, $user_timezone) {
+ // Possible properties: URL
+ $journal = array( 'id' => $id );
+ $journal['summary'] = $vjournal->getAsString('SUMMARY');
+ $format = 'text';
+ if($vjournal->DESCRIPTION) {
+ foreach($vjournal->DESCRIPTION->parameters as $parameter){
+ if(stripos($parameter->name, 'FORMAT') !== false && stripos($parameter->value, 'HTML') !== false){
+ $format = 'html'; // an educated guess ;-)
+ break;
+ }
+ }
+ $desc = $vjournal->getAsString('DESCRIPTION');
+ $journal['description'] = array(
+ 'value' => ($format=='html'?$body = preg_replace("/.*<body[^>]*>|<\/body>.*/si", "", $desc):$desc),
+ 'format' => $format,
+ 'parameters' => self::parametersForProperty($vjournal->DESCRIPTION)
+ );
+ } else {
+ $journal['description'] = array('value' => '', 'format' => 'text');
+ }
+ $journal['organizer'] = array(
+ 'value' => $vjournal->getAsString('ORGANIZER'),
+ 'parameters' => self::parametersForProperty($vjournal->ORGANIZER)
+ );
+ $journal['categories'] = $vjournal->getAsArray('CATEGORIES');
+ //error_log('DTSTART: '.print_r($vjournal->DTSTART, true));
+ $dtprop = $vjournal->DTSTART;
+ if($dtprop) {
+ $dtstart = $vjournal->DTSTART->getDateTime();
+ if($dtstart) {
+ //if(!)
+ $tz = new DateTimeZone($user_timezone);
+ if($tz->getName() != $dtstart->getTimezone()->getName() && !$vjournal->DTSTART->offsetExists('TZID')) {
+ //error_log($tz->getName().' != '.$dtstart->getTimezone()->getName());
+ //error_log('TZ offset: '.$tz->getOffset(new DateTime("now"))/60);
+ $dtstart->setTimezone($tz);
+ }
+ $journal['dtstart'] = $dtstart->format('U');
+ $journal['only_date'] = ($dtprop->getDateType() == Sabre_VObject_Property_DateTime::DATE);
+ } else {
+ OCP\Util::writeLog('journal', 'Could not get DTSTART DateTime for '.$journal['summary'], OCP\Util::ERROR);
+ }
+ } else {
+ OCP\Util::writeLog('journal', 'Could not get DTSTART for '.$journal['summary'], OCP\Util::ERROR);
+ }
+ return $journal;
+ }
+
+ /** Get a map of a properties parameters for JSON
+ * @param $property Sabre_VObject_Property
+ * @return array of parameters in { name => value, } format
+ */
+ public static function parametersForProperty($property) {
+ $temp = array();
+ if(!$property) {
+ return;
+ }
+ foreach($property->parameters as $parameter){
+ $temp[$parameter->name] = $parameter->value;
+ }
+ return $temp;
+ }
+
+ /*
+ * @brief returns the vcategories object of the user
+ * @return (object) $vcategories
+ */
+ protected static function getVCategories() {
+ if (is_null(self::$categories)) {
+ self::$categories = new OC_VCategories('calendar');
+ }
+ return self::$categories;
+ }
+
+
+ /**
+ * Create a stub for a new journal entry.
+ * @return OC_VObject The newly created stub.
+ */
+ public static function createVCalendar() {
+ $vcalendar = new OC_VObject('VCALENDAR');
+ $appinfo = OCP\App::getAppInfo('journal');
+ $appversion = OCP\App::getAppVersion('journal');
+ $prodid = '-//ownCloud//NONSGML '.$appinfo['name'].' '.$appversion.'//EN';
+ $vcalendar->add('PRODID', $prodid);
+ $vcalendar->add('VERSION', '2.0');
+
+ $vjournal = new OC_VObject('VJOURNAL');
+ $vjournal->setDateTime('DTSTART', 'now', Sabre_VObject_Property_DateTime::LOCALTZ);
+ $vjournal->setDateTime('CREATED', 'now', Sabre_VObject_Property_DateTime::UTC);
+ $vjournal->setUID();
+ $email = OCP\Config::getUserValue(OCP\User::getUser(), 'settings', 'email', '');
+ if($email) {
+ $vjournal->setString('ORGANIZER', 'mailto:'.$email);
+ }
+ $vcalendar->add($vjournal);
+ return $vcalendar;
+ }
+
+}
diff --git a/apps/journal/lib/hooks.php b/apps/journal/lib/hooks.php
new file mode 100644
index 00000000000..2298871148c
--- /dev/null
+++ b/apps/journal/lib/hooks.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * ownCloud - Journal
+ *
+ * @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
+ *
+ * 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/>.
+ *
+ */
+
+/**
+ * This class manages our journal.
+ */
+class OC_Journal_Hooks {
+ /**
+ * Hook to convert a completed Task (VTODO) to a journal entry and add it to the calendar.
+ * @param $vtodo An OC_VObject of type VTODO.
+ */
+ public static function taskToJournalEntry($vtodo) {
+ if(!$vtodo) { return; }
+
+ OCP\Util::writeLog('journal', 'Completed task: '.$vtodo->getAsString('SUMMARY'), OCP\Util::DEBUG);
+ $vcalendar = OC_Journal_App::createVCalendar();
+ $vjournal = $vcalendar->VJOURNAL;
+ $vjournal->setDateTime('DTSTART',$vtodo->COMPLETED->getDateTime());
+ $vjournal->SUMMARY = $vtodo->SUMMARY;
+ $vjournal->setString('SUMMARY', OC_Journal_App::$l10n->t('Completed task: ').$vjournal->getAsString('SUMMARY'));
+ $vjournal->DESCRIPTION = $vtodo->DESCRIPTION;
+
+ $cid = OCP\Config::getUserValue(OCP\User::getUser(), 'journal', 'default_calendar', null);
+ if(!$cid) {
+ $calendars = OC_Calendar_Calendar::allCalendars(OCP\User::getUser(), true);
+ $first_calendar = reset($calendars);
+ $cid = $first_calendar['id'];
+ }
+ try {
+ $id = OC_Calendar_Object::add($cid, $vcalendar->serialize());
+ } catch (Exception $e) {
+ OCP\Util::writeLog('journal', 'Error adding completed Task to calendar: "'.$cid.'" '. $e->getMessage(), OCP\Util::ERROR);
+ }
+ }
+}
diff --git a/apps/journal/lib/search.php b/apps/journal/lib/search.php
new file mode 100644
index 00000000000..27b9b87b031
--- /dev/null
+++ b/apps/journal/lib/search.php
@@ -0,0 +1,45 @@
+<?php
+class OC_Search_Provider_Journal extends OC_Search_Provider {
+ function search($query){
+ $calendars = OC_Calendar_Calendar::allCalendars(OCP\USER::getUser(), true);
+ if(count($calendars)==0 || !OCP\App::isEnabled('calendar')) {
+ //return false;
+ }
+ $results=array();
+ $searchquery=array();
+ if(substr_count($query, ' ') > 0) {
+ $searchquery = explode(' ', $query);
+ }else{
+ $searchquery[] = $query;
+ }
+ error_log('search');
+ $user_timezone = OCP\Config::getUserValue(OCP\USER::getUser(), 'calendar', 'timezone', date_default_timezone_get());
+ $l = new OC_l10n('journal');
+ foreach($calendars as $calendar) {
+ $objects = OC_Calendar_Object::all($calendar['id']);
+ foreach($objects as $object) {
+ if($object['objecttype']!='VJOURNAL') {
+ continue;
+ }
+ if(substr_count(strtolower($object['summary']), strtolower($query)) > 0) {
+ $calendardata = OC_VObject::parse($object['calendardata']);
+ $vjournal = $calendardata->VJOURNAL;
+ $dtstart = $vjournal->DTSTART;
+ if($dtstart) {
+ continue;
+ }
+ $start_dt = $dtstart->getDateTime();
+ $start_dt->setTimezone(new DateTimeZone($user_timezone));
+ if ($dtstart->getDateType() == Sabre_VObject_Property_DateTime::DATE) {
+ $info = $l->t('Date') . ': ' . $start_dt->format('d.m.Y');
+ }else{
+ $info = $l->t('Date') . ': ' . $start_dt->format('d.m.y H:i');
+ }
+ $link = OCP\Util::linkTo('journal', 'index.php').'&id='.urlencode($object['id']);
+ $results[]=new OC_Search_Result($object['summary'],$info, $link,(string)$l->t('Journal'));//$name,$text,$link,$type
+ }
+ }
+ }
+ return $results;
+ }
+}
diff --git a/apps/journal/lib/vjournal.php b/apps/journal/lib/vjournal.php
new file mode 100644
index 00000000000..b9aedea041e
--- /dev/null
+++ b/apps/journal/lib/vjournal.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * ownCloud - Journal
+ *
+ * @author Thomas Tanghus
+ * @copyright 2012 Thomas Tanghus <thomas@tanghus.net>
+ *
+ * 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/>.
+ *
+ */
+
+/**
+ * This class manages our journals
+ */
+class OC_Task_VJournal extends OC_Calendar_Object{
+}
diff --git a/apps/journal/settings.php b/apps/journal/settings.php
new file mode 100644
index 00000000000..35f0ee7d6c2
--- /dev/null
+++ b/apps/journal/settings.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+
+OCP\JSON::checkLoggedIn();
+
+$errors = array();
+
+$required_apps = array(
+ array('id' => 'tal', 'name' => 'TAL Page Templates'),
+ array('id' => 'journal', 'name' => 'Journal'),
+ array('id' => 'contacts', 'name' => 'Contacts'),
+);
+foreach($required_apps as $app) {
+ if(!OCP\App::isEnabled($app['id'])) {
+ $error = (string)$l->t('The %%s app isn\'t enabled! Please enable it here: <strong><a href="%%s?appid=%%s">Enable %%s app</a></strong>');
+ $errors[] = sprintf($error, $app['name'],OCP\Util::linkTo('settings', 'apps'), $app['id'], $app['name']);
+ }
+}
+
+$calendars = OC_Calendar_Calendar::allCalendars(OCP\User::getUser(), true);
+if( count($calendars) == 0 ) {
+ $error = (string)$l->t('You have no calendars. Please add one at the <strong><a href="%%s">Calendar app</a></strong>');
+ $errors[] = sprintf($error, OCP\Util::linkTo('calendar', 'index.php'));
+}
+
+if(count($errors) > 0) {
+ $tmpl = new OCP\Template('journal', 'rtfm');
+ $tmpl->assign('errors',$errors, false);
+} else {
+ $cid = OCP\Config::getUserValue(OCP\User::getUser(), 'journal', 'default_calendar', null);
+ OCP\Util::addScript('journal', 'settings');
+ $tmpl = new OC_TALTemplate('journal', 'settings', 'user');
+ $tmpl->assign('calendars', $calendars);
+ $tmpl->assign('cid', $cid);
+}
+
+return $tmpl->fetchPage();
+
+?>
diff --git a/apps/journal/templates/index.pt b/apps/journal/templates/index.pt
new file mode 100644
index 00000000000..40ce351a543
--- /dev/null
+++ b/apps/journal/templates/index.pt
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html metal:use-macro="${maintemplate}/page">
+<head>
+ <title>Journal</title>
+</head>
+<body>
+<div id="content" metal:fill-slot="content">
+ <form onsubmit="return false;">
+ <div id="controls">
+ <select class="tip" id="entrysort" title="Sort journal entries" i18n:attributes="title">
+ <option value="dtdesc">Date - descending</option>
+ <option value="dtasc">Date - ascending</option>
+ <option value="sumdesc">Summary - descending</option>
+ <option value="sumasc">Summary - ascending</option>
+ </select>
+ <button class="tip" id="add" title="Add new journal entry" i18n:attributes="title" i18n:translate="">Add</button>
+ </div>
+ <ul id="leftcontent"></ul>
+ <div id="rightcontent">
+ <div id="entry">
+ <input class="property propertycontainer content" id="summary" name="value" type="text" size="80" placeholder="First enter a short summary" value="" required="required" data-type="SUMMARY" i18n:attributes="placeholder" />
+ <ul id="editortoolbar" class="rte-toolbar svg">
+ <li class="richtext">
+ <button class="tip" data-cmd="bold" title="Bold" i18n:attributes="title">
+ <img tal:attributes="src image:string:journal/bold.svg" />
+ </button>
+ </li>
+ <li class="richtext">
+ <button class="tip" data-cmd="italic" title="Italic" i18n:attributes="title">
+ <img tal:attributes="src image:string:journal/italic.svg" />
+ </button>
+ </li>
+ <li class="richtext">
+ <button class="tip" data-cmd="strikethrough" title="Strike-through" i18n:attributes="title">
+ <img tal:attributes="src image:string:journal/strikethrough.svg" />
+ </button>
+ </li>
+ <li class="richtext">
+ <button class="tip" data-cmd="underline" title="Underline" i18n:attributes="title">
+ <img tal:attributes="src image:string:journal/underline.svg" />
+ </button>
+ </li>
+ <li class="richtext">
+ <button class="tip" data-cmd="ulist" title="Unordered list" i18n:attributes="title">
+ <img tal:attributes="src image:string:journal/list.svg" />
+ </button>
+ </li>
+ <li class="richtext">
+ <button class="tip" data-cmd="createlink" title="Link (URL)" i18n:attributes="title">
+ <img tal:attributes="src image:string:core/actions/public.svg" />
+ </button>
+ </li>
+ <li><a id="togglemode" class="hidden" i18n:translate="">Switch edit mode</a></li>
+ </ul>
+ <div class="propertycontainer" data-type="DESCRIPTION"><textarea class="property content" id="description" name="value"></textarea></div>
+ </div>
+ <dl id="metadata">
+ <div>
+ <input type="checkbox" id="editable" /><label class="tip" for="editable" title="Set this journal entry in edit mode" i18n:attributes="title" i18n:translate="">Edit</label>
+ <a class="tip" id="export" title="Export as iCal file" i18n:attributes="title">
+ <img class="svg action" tal:attributes="src image:string:core/actions/download.svg" />
+ </a>
+ <a class="tip" id="showlink" title="Direct link to this journal entry" i18n:attributes="title">
+ <img class="svg action" tal:attributes="src image:string:core/actions/public.svg" />
+ </a>
+ <a class="tip" id="delete" title="Delete this journal entry" i18n:attributes="title">
+ <img class="svg action" tal:attributes="src image:string:core/actions/delete.svg" />
+ </a>
+ <input class="hidden" id="link" type="text" />
+ </div>
+ <dt><label for="organizer" i18n:translate="">Author</label></dt>
+ <dd>
+ <input class="property propertycontainer" id="organizer" name="value" type="text" size="16" placeholder="Who has written this" value="" data-type="ORGANIZER" disabled="true" i18n:attributes="placeholder" />
+ </dd>
+ <dt id="categories_label"><label for="categories" i18n:translate="">Categories</label></dt>
+ <dd class="propertycontainer" id="categories_value" data-type="CATEGORIES">
+ <input class="property" id="categories" name="value" type="text" size="16" placeholder="Separate with commas" value="" disabled="true" i18n:attributes="placeholder" />
+ <a role="button" class="tip action" id="editcategories" title="Edit groups" i18n:attributes="title">
+ <img class="svg action" tal:attributes="src image:string:core/actions/rename.svg" />
+ </a>
+ </dd>
+ <dt>
+ <label for="dtstartdate" i18n:translate="">Date</label>
+ <input type="checkbox" id="also_time" />
+ <label class="tip" for="also_time" title="Select if this entry also has a time associated." i18n:attributes="title" i18n:translate="">Include time</label>
+ </dt>
+ <dd class="propertycontainer" data-type="DTSTART">
+ <input class="property" id="dtstartdate" name="value" type="date" size="10" placeholder="dd-mm-yyyy" value="" i18n:attributes="placeholder" disabled="true" />
+ <input class="property" id="dtstarttime" name="value" type="time" size="5" placeholder="hh-mm" value="" i18n:attributes="placeholder" disabled="true" />
+ </dd>
+ </dl>
+ </div>
+ <script type="text/javascript">
+ var categories = ${structure php:json_encode(categories)};
+ var id = '${id}';
+ var totalurl = '${url:string:journal/index.php}';
+ </script>
+</form>
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/apps/journal/templates/rtfm.php b/apps/journal/templates/rtfm.php
new file mode 100644
index 00000000000..8fd51fb8d02
--- /dev/null
+++ b/apps/journal/templates/rtfm.php
@@ -0,0 +1,7 @@
+<ul style="margin: 2em; font-size: 1.4em; line-spacing: 2em;">
+<?php
+foreach($_['errors'] as $error) { ?>
+ <li><?php echo $error; ?></li>
+<?php } ?>
+</ul>
+
diff --git a/apps/journal/templates/settings.pt b/apps/journal/templates/settings.pt
new file mode 100644
index 00000000000..3e788f58818
--- /dev/null
+++ b/apps/journal/templates/settings.pt
@@ -0,0 +1,15 @@
+<form id="journal">
+ <fieldset class="personalblock">
+ <legend i18n:translate="">Journal</legend>
+ <dl>
+ <dt i18n:translate="">Select default calendar<br />for storing journals</dt>
+ <dd>
+ <select id="journal_calendar" style="float:left;">
+ <tal:block repeat="calendar calendars">
+ <option tal:define="id calendar/id" tal:attributes="value id; selected php:(cid == id ? 'selected' : null )" tal:content="calendar/displayname"></option>
+ </tal:block>
+ </select>
+ </dd><span id="journal_status"></span>
+ </dl>
+ </fieldset>
+</form>
diff --git a/apps/media/appinfo/version b/apps/media/appinfo/version
index e6adf3fc7bb..44bb5d1f743 100644
--- a/apps/media/appinfo/version
+++ b/apps/media/appinfo/version
@@ -1 +1 @@
-0.4 \ No newline at end of file
+0.4.1 \ No newline at end of file
diff --git a/apps/media/lib/share/album.php b/apps/media/lib/share/album.php
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/apps/media/lib/share/album.php
diff --git a/apps/media/lib/share/artist.php b/apps/media/lib/share/artist.php
new file mode 100644
index 00000000000..7218fa1a279
--- /dev/null
+++ b/apps/media/lib/share/artist.php
@@ -0,0 +1,65 @@
+<?php
+/**
+* ownCloud
+*
+* @author Michael Gapczynski
+* @copyright 2012 Michael Gapczynski mtgap@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/>.
+*/
+
+class OC_Share_Backend_Artist extends OCP\Share_Backend {
+
+ public function getSource($item, $uid) {
+ $query = OCP\DB::prepare('SELECT artist_id FROM *PREFIX*media_artists WHERE artist_id = ? AND song_user = ?');
+ $result = $query->execute(array($item, $uid))->fetchRow();
+ if (is_array($result)) {
+ return array('item' => $item, 'file' => $result['song_path']);
+ }
+ return false;
+ }
+
+ public function generateTarget($item, $uid, $exclude = null) {
+ // TODO Make sure target path doesn't exist already
+ return '/Shared'.$item;
+ }
+
+ public function formatItems($items, $format) {
+ $ids = array();
+ foreach ($items as $id => $info) {
+ $ids[] = $id;
+ }
+ $ids = "'".implode("','", $ids)."'";
+ switch ($format) {
+ case self::FORMAT_SOURCE_PATH:
+ $query = OCP\DB::prepare('SELECT path FROM *PREFIX*fscache WHERE id IN ('.$ids.')');
+ return $query->execute()->fetchAll();
+ case self::FORMAT_FILE_APP:
+ $query = OCP\DB::prepare('SELECT id, path, name, ctime, mtime, mimetype, size, encrypted, versioned, writable FROM *PREFIX*fscache WHERE id IN ('.$ids.')');
+ $result = $query->execute();
+ $files = array();
+ while ($file = $result->fetchRow()) {
+ // Set target path
+ $file['path'] = $items[$file['id']]['item_target'];
+ $file['name'] = basename($file['path']);
+ // TODO Set permissions: $file['writable']
+ $files[] = $file;
+ }
+ return $files;
+ }
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/apps/media/lib/share/song.php b/apps/media/lib/share/song.php
new file mode 100644
index 00000000000..fc69975f353
--- /dev/null
+++ b/apps/media/lib/share/song.php
@@ -0,0 +1,65 @@
+<?php
+/**
+* ownCloud
+*
+* @author Michael Gapczynski
+* @copyright 2012 Michael Gapczynski mtgap@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/>.
+*/
+
+class OC_Share_Backend_Song extends OCP\Share_Backend {
+
+ public function getSource($item, $uid) {
+ $query = OCP\DB::prepare('SELECT song_path FROM *PREFIX*media_songs WHERE song_id = ? AND song_user = ?');
+ $result = $query->execute(array($item, $uid))->fetchRow();
+ if (is_array($result)) {
+ return array('item' => $item, 'file' => $result['song_path']);
+ }
+ return false;
+ }
+
+ public function generateTarget($item, $uid, $exclude = null) {
+ // TODO Make sure target path doesn't exist already
+ return '/Shared'.$item;
+ }
+
+ public function formatItems($items, $format) {
+ $ids = array();
+ foreach ($items as $id => $info) {
+ $ids[] = $id;
+ }
+ $ids = "'".implode("','", $ids)."'";
+ switch ($format) {
+ case self::FORMAT_SOURCE_PATH:
+ $query = OCP\DB::prepare('SELECT path FROM *PREFIX*fscache WHERE id IN ('.$ids.')');
+ return $query->execute()->fetchAll();
+ case self::FORMAT_FILE_APP:
+ $query = OCP\DB::prepare('SELECT id, path, name, ctime, mtime, mimetype, size, encrypted, versioned, writable FROM *PREFIX*fscache WHERE id IN ('.$ids.')');
+ $result = $query->execute();
+ $files = array();
+ while ($file = $result->fetchRow()) {
+ // Set target path
+ $file['path'] = $items[$file['id']]['item_target'];
+ $file['name'] = basename($file['path']);
+ // TODO Set permissions: $file['writable']
+ $files[] = $file;
+ }
+ return $files;
+ }
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/apps/media/lib_ampache.php b/apps/media/lib_ampache.php
index d35cca150b2..d5a093338cc 100644
--- a/apps/media/lib_ampache.php
+++ b/apps/media/lib_ampache.php
@@ -271,7 +271,6 @@ class OC_MEDIA_AMPACHE{
</root>");
return;
}
- global $SITEROOT;
$filter=$params['filter'];
$albums=OC_MEDIA_COLLECTION::getAlbums($filter);
$artist=OC_MEDIA_COLLECTION::getArtistName($filter);
diff --git a/apps/media/lib_collection.php b/apps/media/lib_collection.php
index e65930f551d..cacab8e959f 100644
--- a/apps/media/lib_collection.php
+++ b/apps/media/lib_collection.php
@@ -27,7 +27,6 @@ class OC_MEDIA_COLLECTION{
public static $uid;
private static $artistIdCache=array();
private static $albumIdCache=array();
- private static $songIdCache=array();
private static $queries=array();
/**
@@ -152,7 +151,7 @@ class OC_MEDIA_COLLECTION{
return $artistId;
}else{
$query=OCP\DB::prepare("INSERT INTO `*PREFIX*media_artists` (`artist_name`) VALUES (?)");
- $result=$query->execute(array($name));
+ $query->execute(array($name));
return self::getArtistId($name);;
}
}
diff --git a/apps/media/lib_media.php b/apps/media/lib_media.php
index 9e687a4af2c..54502f42575 100644
--- a/apps/media/lib_media.php
+++ b/apps/media/lib_media.php
@@ -27,12 +27,12 @@ class OC_MEDIA{
* @param array $params, parameters passed from OC_Hook
*/
public static function loginListener($params){
- if(isset($_POST['user']) and $_POST['password']){
- $name=$_POST['user'];
+ if(isset($params['uid']) and $params['password']){
+ $name=$params['uid'];
$query=OCP\DB::prepare("SELECT user_id from *PREFIX*media_users WHERE user_id LIKE ?");
$uid=$query->execute(array($name))->fetchAll();
if(count($uid)==0){
- $password=hash('sha256',$_POST['password']);
+ $password=hash('sha256',$params['password']);
$query=OCP\DB::prepare("INSERT INTO *PREFIX*media_users (user_id, user_password_sha256) VALUES (?, ?);");
$query->execute(array($name,$password));
}
diff --git a/apps/news/.gitignore b/apps/news/.gitignore
new file mode 100644
index 00000000000..16469399de3
--- /dev/null
+++ b/apps/news/.gitignore
@@ -0,0 +1,5 @@
+3rdparty/*
+news.kdev4
+*~
+.kdev4
+img/*
diff --git a/apps/news/ajax/createfeed.php b/apps/news/ajax/createfeed.php
new file mode 100644
index 00000000000..754d874957f
--- /dev/null
+++ b/apps/news/ajax/createfeed.php
@@ -0,0 +1,37 @@
+<?php
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('news');
+OCP\JSON::callCheck();
+
+$userid = OCP\USER::getUser();
+
+$feedurl = trim($_POST['feedurl']);
+$folderid = trim($_POST['folderid']);
+
+$feed = OC_News_Utils::fetch($feedurl);
+$feedmapper = new OC_News_FeedMapper();
+$feedid = $feedmapper->save($feed, $folderid);
+
+$l = OC_L10N::get('news');
+
+if(!$feedid) {
+ OCP\JSON::error(array('data' => array('message' => $l->t('Error adding folder.'))));
+ OCP\Util::writeLog('news','ajax/createfeed.php: Error adding feed: '.$_POST['feedurl'], OCP\Util::ERROR);
+ exit();
+}
+
+//TODO: replace the following with a real success case. see contact/ajax/createaddressbook.php for inspirations
+OCP\JSON::success(array('data' => array('message' => $l->t('Feed added!'))));
+
diff --git a/apps/news/ajax/createfolder.php b/apps/news/ajax/createfolder.php
new file mode 100644
index 00000000000..b5f624604dc
--- /dev/null
+++ b/apps/news/ajax/createfolder.php
@@ -0,0 +1,41 @@
+<?php
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('news');
+OCP\JSON::callCheck();
+
+$userid = OCP\USER::getUser();
+
+$name = trim($_POST['name']);
+$parentid = trim($_POST['parentid']);
+
+$foldermapper = new OC_News_FolderMapper($userid);
+
+if($parentid != 0)
+ $folder = new OC_News_Folder($name, NULL, $foldermapper->find($parentid));
+else
+ $folder = new OC_News_Folder($name);
+
+$folderid = $foldermapper->save($folder);
+
+$l = OC_L10N::get('news');
+
+if(!$folderid) {
+ OCP\JSON::error(array('data' => array('message' => $l->t('Error adding folder.'))));
+ OCP\Util::writeLog('news','ajax/createfolder.php: Error adding folder: '.$_POST['name'], OCP\Util::ERROR);
+}
+else {
+ //TODO: replace the following with a real success case. see contact/ajax/createaddressbook.php for inspirations
+ OCP\JSON::success(array('data' => array('message' => $l->t('Folder added!'))));
+} \ No newline at end of file
diff --git a/apps/news/ajax/deletefeed.php b/apps/news/ajax/deletefeed.php
new file mode 100644
index 00000000000..d31a9417d14
--- /dev/null
+++ b/apps/news/ajax/deletefeed.php
@@ -0,0 +1,33 @@
+<?php
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('news');
+OCP\JSON::callCheck();
+
+$userid = OCP\USER::getUser();
+
+$feedid = $_POST['feedid'];
+
+$feedmapper = new OC_News_FeedMapper();
+$success = $feedmapper->deleteById($feedid);
+
+$l = OC_L10N::get('news');
+
+if(!$success) {
+ OCP\JSON::error(array('data' => array('message' => $l->t('Error removing feed.'))));
+ OCP\Util::writeLog('news','ajax/deletefeed.php: Error removing feed: '.$_POST['feedid'], OCP\Util::ERROR);
+ exit();
+}
+
+OCP\JSON::success(array('data' => array( 'feedid' => $feedid )));
diff --git a/apps/news/ajax/deletefolder.php b/apps/news/ajax/deletefolder.php
new file mode 100644
index 00000000000..bf975c6c329
--- /dev/null
+++ b/apps/news/ajax/deletefolder.php
@@ -0,0 +1,33 @@
+<?php
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('news');
+OCP\JSON::callCheck();
+
+$userid = OCP\USER::getUser();
+
+$folderid = trim($_POST['folderid']);
+
+$foldermapper = new OC_News_FolderMapper();
+$success = $foldermapper->deleteById($folderid);
+
+$l = OC_L10N::get('news');
+
+if(!$success) {
+ OCP\JSON::error(array('data' => array('message' => $l->t('Error removing folder.'))));
+ OCP\Util::writeLog('news','ajax/deletefolder.php: Error removing folder: '.$_POST['folderid'], OCP\Util::ERROR);
+ exit();
+}
+
+OCP\JSON::success(array('data' => array( 'folderid' => $folderid )));
diff --git a/apps/news/ajax/feeddialog.php b/apps/news/ajax/feeddialog.php
new file mode 100644
index 00000000000..c42771c9884
--- /dev/null
+++ b/apps/news/ajax/feeddialog.php
@@ -0,0 +1,7 @@
+<?php
+
+include("populateroot.php");
+
+$output = new OCP\Template("news", "part.addfeed");
+$output -> assign('allfeeds', $allfeeds);
+$output -> printpage(); \ No newline at end of file
diff --git a/apps/news/ajax/folderdialog.php b/apps/news/ajax/folderdialog.php
new file mode 100644
index 00000000000..97b679e662e
--- /dev/null
+++ b/apps/news/ajax/folderdialog.php
@@ -0,0 +1,7 @@
+<?php
+
+include("populateroot.php");
+
+$output = new OCP\Template("news", "part.addfolder");
+$output -> assign('allfeeds', $allfeeds);
+$output -> printpage(); \ No newline at end of file
diff --git a/apps/news/ajax/markitem.php b/apps/news/ajax/markitem.php
new file mode 100644
index 00000000000..c42dc9350cf
--- /dev/null
+++ b/apps/news/ajax/markitem.php
@@ -0,0 +1,35 @@
+<?php
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('news');
+OCP\JSON::callCheck();
+
+$itemid = $_POST['itemid'];
+
+$itemmapper = new OC_News_ItemMapper();
+$item = $itemmapper->find($itemid);
+$item->setRead();
+$success = $itemmapper->update($item);
+
+$l = OC_L10N::get('news');
+
+if(!$success) {
+ OCP\JSON::error(array('data' => array('message' => $l->t('Error marking item as read.'))));
+ OCP\Util::writeLog('news','ajax/markitem.php: Error marking item as read: '.$_POST['itemid'], OCP\Util::ERROR);
+ exit();
+}
+
+//TODO: replace the following with a real success case. see contact/ajax/createaddressbook.php for inspirations
+OCP\JSON::success(array('data' => array('itemid' => $itemid )));
+
diff --git a/apps/news/ajax/populateroot.php b/apps/news/ajax/populateroot.php
new file mode 100644
index 00000000000..a408c25ec5d
--- /dev/null
+++ b/apps/news/ajax/populateroot.php
@@ -0,0 +1,18 @@
+<?php
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('news');
+
+$foldermapper = new OC_News_FolderMapper(OCP\USER::getUser());
+
+$allfeeds = $foldermapper->populate('Everything', 0);
+
+if ($allfeeds) {
+ $feedid = isset( $_GET['feedid'] ) ? $_GET['feedid'] : null;
+ if ($feedid == null) {
+
+ }
+}
+else {
+ $feedid = 0;
+}
diff --git a/apps/news/appinfo/app.php b/apps/news/appinfo/app.php
new file mode 100644
index 00000000000..eb1b54455dd
--- /dev/null
+++ b/apps/news/appinfo/app.php
@@ -0,0 +1,42 @@
+<?php
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+OC::$CLASSPATH['OC_News_Item'] = 'apps/news/lib/item.php';
+OC::$CLASSPATH['OC_News_Collection'] = 'apps/news/lib/collection.php';
+OC::$CLASSPATH['OC_News_Feed'] = 'apps/news/lib/feed.php';
+OC::$CLASSPATH['OC_News_Folder'] = 'apps/news/lib/folder.php';
+
+OC::$CLASSPATH['OC_News_FeedMapper'] = 'apps/news/lib/feedmapper.php';
+OC::$CLASSPATH['OC_News_ItemMapper'] = 'apps/news/lib/itemmapper.php';
+OC::$CLASSPATH['OC_News_FolderMapper'] = 'apps/news/lib/foldermapper.php';
+
+OC::$CLASSPATH['OC_News_Utils'] = 'apps/news/lib/utils.php';
+
+
+$l = new OC_l10n('news');
+
+OCP\App::registerPersonal('news', 'settings');
+
+OCP\App::register( array(
+ 'order' => 70,
+ 'id' => 'news',
+ 'name' => 'News'
+));
+
+OCP\App::addNavigationEntry( array(
+ 'id' => 'news',
+ 'order' => 74,
+ 'href' => OC_Helper::linkTo( 'news', 'index.php' ),
+ 'icon' => OC_Helper::imagePath( 'news', 'icon.svg' ),
+ 'name' => $l->t('News')
+));
+
diff --git a/apps/news/appinfo/database.xml b/apps/news/appinfo/database.xml
new file mode 100644
index 00000000000..3f5f6c44afd
--- /dev/null
+++ b/apps/news/appinfo/database.xml
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<database>
+ <name>*dbname*</name>
+ <create>true</create>
+ <overwrite>false</overwrite>
+ <charset>latin1</charset>
+ <table>
+ <name>*dbprefix*news_folders</name>
+ <declaration>
+ <field>
+ <name>id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <length>4</length>
+ </field>
+ <field>
+ <name>parent_id</name>
+ <type>integer</type>
+ <notnull>false</notnull>
+ <length>4</length>
+ </field>
+ <field>
+ <name>name</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>100</length>
+ </field>
+ <field>
+ <name>user_id</name>
+ <type>text</type>
+ <default></default>
+ <notnull>true</notnull>
+ <length>64</length>
+ </field>
+
+ <index>
+ <name>folder_id</name>
+ <unique>true</unique>
+ <field>
+ <name>id</name>
+ <sorting>descending</sorting>
+ </field>
+ </index>
+ <index>
+ <name>user_id</name>
+ <field>
+ <name>user_id</name>
+ </field>
+ </index>
+ </declaration>
+ </table>
+
+ <table>
+ <name>*dbprefix*news_feeds</name>
+ <declaration>
+ <field>
+ <name>id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <length>4</length>
+ </field>
+ <field>
+ <name>url</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>100</length>
+ </field>
+ <field>
+ <name>title</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>100</length>
+ </field>
+ <field>
+ <name>added</name>
+ <type>integer</type>
+ <default></default>
+ <notnull>false</notnull>
+ <unsigned>true</unsigned>
+ <length>4</length>
+ </field>
+ <field>
+ <name>lastmodified</name>
+ <type>integer</type>
+ <default></default>
+ <notnull>false</notnull>
+ <unsigned>true</unsigned>
+ <length>4</length>
+ </field>
+ <field>
+ <name>folder_id</name>
+ <type>integer</type>
+ <notnull>true</notnull>
+ <length>4</length>
+ </field>
+
+ <index>
+ <name>feed_id</name>
+ <unique>true</unique>
+ <primary>true</primary>
+ <field>
+ <name>id</name>
+ <sorting>descending</sorting>
+ </field>
+ </index>
+
+ <index>
+ <name>feed_url</name>
+ <unique>true</unique>
+ <primary>false</primary>
+ <field>
+ <name>url</name>
+ </field>
+ </index>
+ </declaration>
+ </table>
+
+ <table>
+ <name>*dbprefix*news_items</name>
+ <declaration>
+ <field>
+ <name>id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <length>4</length>
+ </field>
+ <field>
+ <name>guid</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>100</length>
+ </field>
+ <field>
+ <name>url</name>
+ <type>text</type>
+ <length>100</length>
+ </field>
+ <field>
+ <name>title</name>
+ <type>text</type>
+ <length>100</length>
+ </field>
+ <field>
+ <name>feed_id</name>
+ <type>integer</type>
+ <notnull>true</notnull>
+ <length>4</length>
+ </field>
+ <field>
+ <name>body</name>
+ <type>text</type>
+ <length>4000</length>
+ </field>
+ <field>
+ <name>status</name>
+ <type>integer</type>
+ <length>1</length>
+ </field>
+
+ <index>
+ <name>item_id</name>
+ <unique>true</unique>
+ <field>
+ <name>id</name>
+ <sorting>descending</sorting>
+ </field>
+ </index>
+ <index>
+ <name>item_guid</name>
+ <unique>true</unique>
+ <field>
+ <name>guid</name>
+ </field>
+ <field>
+ <name>feed_id</name>
+ </field>
+ </index>
+ </declaration>
+ </table>
+</database>
diff --git a/apps/news/appinfo/info.xml b/apps/news/appinfo/info.xml
new file mode 100644
index 00000000000..0ada6e75c03
--- /dev/null
+++ b/apps/news/appinfo/info.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<info>
+ <id>news</id>
+ <name>News</name>
+ <description>An RSS/Atom feed reader</description>
+ <version>5.5</version>
+ <licence>AGPL</licence>
+ <author>Alessandro Cosentino</author>
+ <require>4</require>
+</info>
diff --git a/apps/news/css/news.css b/apps/news/css/news.css
new file mode 100644
index 00000000000..04ce0d2e738
--- /dev/null
+++ b/apps/news/css/news.css
@@ -0,0 +1,52 @@
+li { padding-right: 0px !important; }
+
+.collapsable { background: url('%webroot%/core/img/places/folder.svg') no-repeat left center; padding-left: 20px;}
+.news_input { float:left; font-size:12px; padding:4px 2px; border:solid 1px #aacfe4; width:200px; }
+.svg { border: inherit; background: inherit; }
+
+#leftcontent { top: 3.5em !important; padding: 0; margin: 0; }
+#rightcontent { top: 3.5em !important; padding-top: 5px; }
+#feeds { background: #fff; width: 20em; left: 12.5em; top: 3.7em; bottom:3em; position: fixed; overflow: auto; padding: 0; margin: 0; }
+/* #feeds a { height: 23px; display: block; margin: 0 0 0 0; padding: 0 0 0 25px; } */
+#bottomcontrols { padding: 0; bottom:0px; overflow:visible; height:2.8em; width: 20em; margin:0; background:#eee; border-top:1px solid #ccc; position:fixed; -moz-box-shadow: 0 -3px 3px -3px #000; -webkit-box-shadow: 0 -3px 3px -3px #000; box-shadow: 0 -3px 3px -3px #000;}
+#feeds_delete { position: absolute; right: 0px; background: url('%webroot%/core/img/actions/delete.svg') no-repeat center; display: inline; }
+#feeds_edit { position: absolute; right: 1.6em; background: url('%webroot%/core/img/actions/rename.svg') no-repeat center; display: inline; }
+
+#dropdownBtn { width: 9em; padding-left: 0; padding-right:20px; background: url('%webroot%/core/img/actions/triangle-s.svg') no-repeat right center; }
+
+#addfolder { background: url('%webroot%/core/img/places/folder.svg') no-repeat left center; padding-left: 20px; }
+#addfeed { background: url('%appswebroot%/apps/news/img/rss.svg') no-repeat left center; padding-left: 20px; }
+
+ul.controls li { float: left; }
+
+.accordion .title_unread { background: #DCDCDC; font-size: 12px; border-bottom:1px solid #ccc; font-weight:bold;}
+.accordion .title_read { background: #DCDCDC; font-size: 12px; border-bottom:1px solid #ccc;}
+
+ul.menu { position: absolute; z-index:100; margin-left: 0.3em;
+ display: none;
+ background:#EEEEEE; /* default background for browsers without gradient support */
+ /* css3 */
+ background:-webkit-gradient(linear, 0 0, 0 100%, from(#DCDCDC), to(#EEEEEE));
+ background:-moz-linear-gradient(#DCDCDC, #EEEEEE);
+ background:-o-linear-gradient(#DCDCDC, #EEEEEE);
+ background:linear-gradient(#DCDCDC, #EEEEEE);
+ /*
+ -webkit-border-radius:10px;
+ -moz-border-radius:10px;
+ border-radius:10px;
+ */
+}
+
+ul#dropdownmenu { left: 0em; box-shadow: 0px 0px 10px rgb(0, 0, 0); }
+
+ul#feedfoldermenu { position:fixed; margin-left: 0; bottom: 2.8em; border-left:1px solid #ccc; border-top:1px solid #ccc; border-right:1px solid #ccc; -moz-box-shadow: 0 -3px 3px -3px #000; -webkit-box-shadow: 0 -3px 3px -3px #000; box-shadow: 0 -3px 3px -3px #000;}
+
+li.menuItem { margin-left:0.7em; margin-right:0.7em; float: none !important; text-align: left; }
+
+li.folder_list:hover { color: rgb(0, 0, 0) !important; background: none !important; }
+li.feeds_list:hover { background: none repeat scroll 0% 0% rgb(221, 221, 221) !important; }
+div.collapsable:hover { background-color: rgb(221, 221, 221); }
+div.add_parentfolder { position: relative; }
+
+div.dialog { overflow: visible; }
+
diff --git a/apps/news/index.php b/apps/news/index.php
new file mode 100644
index 00000000000..e75ec19d670
--- /dev/null
+++ b/apps/news/index.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+// Check if we are a user
+OCP\User::checkLoggedIn();
+
+OCP\App::checkAppEnabled('news');
+OCP\App::setActiveNavigationEntry('news');
+
+OCP\Util::addscript('news','news');
+OCP\Util::addStyle('news','news');
+
+$l = OC_L10N::get('news');
+
+$foldermapper = new OC_News_FolderMapper(OCP\USER::getUser());
+
+$allfeeds = $foldermapper->populate($l->t('Everything'), 0);
+
+if ($allfeeds) {
+ $feedid = isset( $_GET['feedid'] ) ? $_GET['feedid'] : null;
+ if ($feedid == null) {
+
+ }
+}
+else {
+ $feedid = 0;
+}
+
+$tmpl = new OCP\Template( 'news', 'main', 'user' );
+$tmpl->assign('allfeeds', $allfeeds);
+$tmpl->assign('feedid', $feedid);
+$tmpl->printPage();
+
+?>
diff --git a/apps/news/js/news.js b/apps/news/js/news.js
new file mode 100644
index 00000000000..96c3f60b7d2
--- /dev/null
+++ b/apps/news/js/news.js
@@ -0,0 +1,196 @@
+News={
+ DropDownMenu: {
+ fade:function(menu){
+ var list = $(menu).toggle();
+ return false;
+ },
+ dropdown:function(button){
+ var list = $(button).parent().find('ul#dropdownmenu');
+ if (list.css('display') == 'none')
+ list.slideDown('fast').show();
+ else
+ list.slideUp('fast');
+
+ return false;
+ },
+ selectItem:function(item, folderid){
+ var parent = $(item).parent().parent();
+ parent.find('#dropdownBtn').text($(item).text());
+ parent.find(':input[name="folderid"]').val(folderid);
+ parent.find('ul#dropdownmenu').slideUp('fast');
+ }
+ },
+ UI: {
+ overview:function(dialogtype, dialogfile){
+ if($(dialogtype).dialog('isOpen') == true){
+ $(dialogtype).dialog('moveToTop');
+ }else{
+ $('#dialog_holder').load(OC.filePath('news', 'ajax', dialogfile), function(jsondata){
+ if(jsondata.status != 'error'){
+ $(dialogtype).dialog({
+ dialogClass:'dialog',
+ minWidth: 600,
+ close: function(event, ui) {
+ $(this).dialog('destroy').remove();
+ }
+ }).css('overflow','visible');
+ } else {
+ alert(jsondata.data.message);
+ }
+ });
+ }
+ return false;
+ }
+ },
+ Folder: {
+ submit:function(button){
+ var displayname = $("#folder_add_name").val().trim();
+
+ if(displayname.length == 0) {
+ OC.dialogs.alert(t('news', 'Displayname cannot be empty.'), t('news', 'Error'));
+ return false;
+ }
+
+ $(button).attr("disabled", true);
+ //translation here!!!
+ $(button).prop('value', 'Adding...');
+
+ var folderid = $('#inputfolderid:input[name="folderid"]').val();
+
+ var url;
+ url = OC.filePath('news', 'ajax', 'createfolder.php');
+
+ $.post(url, { name: displayname, parentid: folderid },
+ function(jsondata){
+ if(jsondata.status == 'success'){
+ //$(button).closest('tr').prev().html(jsondata.page).show().next().remove();
+ OC.dialogs.alert(jsondata.data.message, t('news', 'Success!'));
+ } else {
+ OC.dialogs.alert(jsondata.data.message, t('news', 'Error'));
+ }
+ $("#folder_add_name").val('');
+ $(button).attr("disabled", false);
+ //translation here!!!
+ $(button).prop('value', 'Add folder');
+ });
+ },
+ 'delete':function(folderid) {
+ $('#feeds_delete').tipsy('hide');
+ OC.dialogs.confirm(t('news', 'Are you sure you want to delete this folder and all its feeds?'), t('news', 'Warning'), function(answer) {
+ if(answer == true) {
+ $.post(OC.filePath('news', 'ajax', 'deletefolder.php'),{'folderid':folderid},function(jsondata){
+ if(jsondata.status == 'success'){
+ alert('removed!');
+ }
+ else{
+ OC.dialogs.alert(jsondata.data.message, t('news', 'Error'));
+ }
+ });
+ }
+ });
+ return false;
+ }
+ },
+ Feed: {
+ id:'',
+ submit:function(button){
+ var feedurl = $("#feed_add_url").val().trim();
+
+ if(feedurl.length == 0) {
+ OC.dialogs.alert(t('news', 'URL cannot be empty.'), t('news', 'Error'));
+ return false;
+ }
+
+ $(button).attr("disabled", true);
+ //translation here!!!
+ $(button).prop('value', 'Adding...');
+
+ var folderid = $('#inputfolderid:input[name="folderid"]').val();
+
+ var url;
+ url = OC.filePath('news', 'ajax', 'createfeed.php');
+
+ $.post(url, { feedurl: feedurl, folderid: folderid },
+ function(jsondata){
+ if(jsondata.status == 'success'){
+ OC.dialogs.alert(jsondata.data.message, t('news', 'Success!'));
+ } else {
+ OC.dialogs.alert(jsondata.data.message, t('news', 'Error'));
+ }
+ $("#feed_add_url").val('');
+ $(button).attr("disabled", false);
+ //translation here!!!
+ $(button).prop('value', 'Add feed');
+ });
+ },
+ 'delete':function(feedid) {
+ $('#feeds_delete').tipsy('hide');
+ OC.dialogs.confirm(t('news', 'Are you sure you want to delete this feed?'), t('news', 'Warning'), function(answer) {
+ if(answer == true) {
+ $.post(OC.filePath('news', 'ajax', 'deletefeed.php'),{'feedid':feedid},function(jsondata){
+ if(jsondata.status == 'success'){
+ $('#leftcontent [data-id="'+jsondata.data.feedid+'"]').remove();
+ //change the right view too (maybe a message to subscribe, like in Google Reader?)
+ }
+ else{
+ OC.dialogs.alert(jsondata.data.message, t('news', 'Error'));
+ }
+ });
+ }
+ });
+ return false;
+ },
+ markItem:function(itemid) {
+ $.post(OC.filePath('news', 'ajax', 'markitem.php'),{'itemid':itemid},function(jsondata){
+ if(jsondata.status == 'success'){
+ var $currentitem = $('#rightcontent [data-id="'+jsondata.data.itemid+'"]');
+ $currentitem.removeClass('title_unread');
+ $currentitem.addClass('title_read');
+ //set a timeout for this
+ }
+ else{
+ OC.dialogs.alert(jsondata.data.message, t('news', 'Error'));
+ }
+ });
+ }
+ }
+}
+
+$(document).ready(function(){
+
+ $('#addfeed').click(function() {
+ News.UI.overview('#addfeed_dialog','feeddialog.php');
+ $(this).parent().toggle();
+ });
+ $('#addfolder').click(function() {
+ News.UI.overview('#addfolder_dialog','folderdialog.php');
+ $(this).parent().toggle();
+ });
+
+ $('.collapsable').click(function(){
+ $(this).parent().children().toggle();
+ $(this).toggle();
+ });
+
+ $('.accordion .title_unread').click(function() {
+ $(this).next().toggle();
+ return false;
+ }).next().hide();
+
+ $('.accordion .title_read').click(function() {
+ $(this).next().toggle();
+ return false;
+ }).next().hide();
+
+ var list = $('.collapsable,.feeds_list').hover(function() {
+ var elem = $(this).find('#feeds_delete,#feeds_edit');
+ if(elem.css('display') == 'none')
+ elem.css('display', 'inline');
+ else
+ elem.css('display', 'none');
+
+ return false;
+ });
+ list.find('#feeds_delete').hide();
+ list.find('#feeds_edit').hide();
+});
diff --git a/apps/news/js/settings.js b/apps/news/js/settings.js
new file mode 100644
index 00000000000..4c645d241e2
--- /dev/null
+++ b/apps/news/js/settings.js
@@ -0,0 +1,15 @@
+$(document).ready(function(){
+
+
+
+ $('#somesetting').blur(function(event){
+ event.preventDefault();
+ var post = $( "#somesetting" ).serialize();
+ $.post( OC.filePath('apptemplate','ajax','seturl.php') , post, function(data){ OC.msg.finishedSaving('#somesetting .msg', data); });
+ });
+
+
+
+});
+
+
diff --git a/apps/news/lib/collection.php b/apps/news/lib/collection.php
new file mode 100644
index 00000000000..2ebbb646f0a
--- /dev/null
+++ b/apps/news/lib/collection.php
@@ -0,0 +1,32 @@
+<?php
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+/**
+ * This class models a collection, which is either a feed or a folder.
+ */
+class OC_News_Collection {
+
+ private $id;
+
+ public function __construct($id){
+ $this->id = $id;
+ }
+
+ public function getId(){
+ return $this->id;
+ }
+
+ public function setId($id){
+ $this->id = $id;
+ }
+
+} \ No newline at end of file
diff --git a/apps/news/lib/feed.php b/apps/news/lib/feed.php
new file mode 100644
index 00000000000..bbed8d59824
--- /dev/null
+++ b/apps/news/lib/feed.php
@@ -0,0 +1,46 @@
+<?php
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+/**
+ * This class models a feed.
+ */
+class OC_News_Feed extends OC_News_Collection {
+
+ private $url;
+ private $spfeed; //encapsulate a SimplePie_Core object
+ private $items; //array that contains all the items of the feed
+
+ public function __construct($url, $title, $items, $id = null){
+ $this->url = $url;
+ $this->title = $title;
+ $this->items = $items;
+ if ($id !== null){
+ parent::__construct($id);
+ }
+ }
+
+ public function getUrl(){
+ return $this->url;
+ }
+
+ public function getTitle(){
+ return $this->title;
+ }
+
+ public function setItems($items){
+ $this->items = $items;
+ }
+
+ public function getItems(){
+ return $this->items;
+ }
+}
diff --git a/apps/news/lib/feedmapper.php b/apps/news/lib/feedmapper.php
new file mode 100644
index 00000000000..73afa0e8647
--- /dev/null
+++ b/apps/news/lib/feedmapper.php
@@ -0,0 +1,182 @@
+<?php
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+/**
+ * This class maps a feed to an entry in the feeds table of the database.
+ */
+class OC_News_FeedMapper {
+
+ const tableName = '*PREFIX*news_feeds';
+
+ /**
+ * @brief Retrieve a feed from the database
+ * @param id The id of the feed in the database table.
+ * @returns
+ */
+ public function findById($id){
+ $stmt = OCP\DB::prepare('SELECT * FROM ' . self::tableName . ' WHERE id = ?');
+ $result = $stmt->execute(array($id));
+ $row = $result->fetchRow();
+ $url = $row['url'];
+ $title = $row['title'];
+ $feed = new OC_News_Feed($url, $title, null, $id);
+ return $feed;
+ }
+
+ /**
+ * @brief Retrieve a feed from the database
+ * @param id The id of the feed in the database table.
+ * @returns
+ */
+ public function findByFolderId($folderid){
+ $stmt = OCP\DB::prepare('SELECT * FROM ' . self::tableName . ' WHERE folder_id = ?');
+ $result = $stmt->execute(array($folderid));
+ $feeds = array();
+ while ($row = $result->fetchRow()) {
+ $url = $row['url'];
+ $title = $row['title'];
+ $id = $row['id'];
+ $feed = new OC_News_Feed($url, $title, null, $id);
+ $feeds[] = $feed;
+ }
+ return $feeds;
+ }
+
+
+ /**
+ * @brief Retrieve a feed and all its items from the database
+ * @param id The id of the feed in the database table.
+ * @returns
+ */
+ public function findWithItems($id){
+ $stmt = OCP\DB::prepare('SELECT * FROM ' . self::tableName . ' WHERE id = ?');
+ $result = $stmt->execute(array($id));
+ $row = $result->fetchRow();
+ $url = $row['url'];
+ $title = $row['title'];
+ $feed = new OC_News_Feed($url, $title, null,$id);
+
+ $itemMapper = new OC_News_ItemMapper();
+ $items = $itemMapper->findAll($id);
+ $feed->setItems($items);
+
+ return $feed;
+ }
+
+ /**
+ * @brief Find the id of a feed and all its items from the database
+ * @param url url of the feed
+ * @return id of the feed corresponding to the url passed as parameters
+ * null - if there is no such feed
+ */
+ public function findIdFromUrl($url){
+ $stmt = OCP\DB::prepare('SELECT * FROM ' . self::tableName . ' WHERE url = ?');
+ $result = $stmt->execute(array($url));
+ $row = $result->fetchRow();
+ $id = null;
+ if ($row != null){
+ $id = $row['id'];
+ }
+ return $id;
+ }
+
+ /**
+ * @brief Save the feed and all its items into the database
+ * @param feed the feed to be saved
+ * @returns The id of the feed in the database table.
+ */
+ //TODO: handle error case
+ public function save(OC_News_Feed $feed, $folderid){
+ $CONFIG_DBTYPE = OCP\Config::getSystemValue( "dbtype", "sqlite" );
+ if( $CONFIG_DBTYPE == 'sqlite' or $CONFIG_DBTYPE == 'sqlite3' ){
+ $_ut = "strftime('%s','now')";
+ } elseif($CONFIG_DBTYPE == 'pgsql') {
+ $_ut = 'date_part(\'epoch\',now())::integer';
+ } else {
+ $_ut = "UNIX_TIMESTAMP()";
+ }
+
+ $title = $feed->getTitle();
+ $url = htmlspecialchars_decode($feed->getUrl());
+
+ if(empty($title)) {
+ $l = OC_L10N::get('news');
+ $title = $l->t('no title');
+ }
+
+ //FIXME: Detect when feed contains already a database id
+ $feedid = $this->findIdFromUrl($url);
+ if ($feedid == null){
+ $query = OCP\DB::prepare('
+ INSERT INTO ' . self::tableName .
+ '(url, title, folder_id, added, lastmodified)
+ VALUES (?, ?, ?, ?, ?)
+ ');
+
+ $params=array(
+ $url,
+ htmlspecialchars_decode($title),
+ $folderid,
+ $_ut,
+ $_ut
+ );
+ $query->execute($params);
+
+ $feedid = OCP\DB::insertid(self::tableName);
+ }
+ $feed->setId($feedid);
+
+ $itemMapper = new OC_News_ItemMapper();
+
+ $items = $feed->getItems();
+ foreach($items as $item){
+ $itemMapper->save($item, $feedid);
+ }
+
+ return $feedid;
+ }
+
+ public function deleteById($id){
+ if ($id == null) {
+ return false;
+ }
+ $stmt = OCP\DB::prepare('DELETE FROM ' . self::tableName .' WHERE id = ?');
+
+ $result = $stmt->execute(array($id));
+
+ $itemMapper = new OC_News_ItemMapper();
+ //TODO: handle the value that the execute returns
+ $itemMapper->deleteAll($id);
+
+ return true;
+ }
+ public function delete(OC_News_Feed $feed){
+ $id = $feed->getId();
+ return deleteById($id);
+ }
+
+ //it's more complicated tan this...recursive delete, or delete with a join
+ public function deleteAll($folderdid){
+ if ($folderid == null) {
+ return false;
+ }
+ $stmt = OCP\DB::prepare('DELETE FROM ' . self::tableName .' WHERE folder_id = ?');
+
+ $result = $stmt->execute(array($folderid));
+
+ $itemMapper = new OC_News_ItemMapper();
+ //TODO: handle the value that the execute returns
+ $itemMapper->deleteAll($id);
+
+ return true;
+ }
+} \ No newline at end of file
diff --git a/apps/news/lib/folder.php b/apps/news/lib/folder.php
new file mode 100644
index 00000000000..6654b2f5e92
--- /dev/null
+++ b/apps/news/lib/folder.php
@@ -0,0 +1,56 @@
+<?php
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+/**
+ * This class models a folder that contains feeds.
+ */
+class OC_News_Folder extends OC_News_Collection {
+
+ private $name;
+ private $children;
+ private $parent;
+
+ public function __construct($name, $id = null, OC_News_Collection $parent = null){
+ $this->name = $name;
+ if ($id !== null){
+ parent::__construct($id);
+ }
+ $this->children = array();
+ if ($parent !== null){
+ $this->parent = $parent;
+ }
+ }
+
+ public function getName(){
+ return $this->name;
+ }
+
+ public function setName($name){
+ $this->name = $name;
+ }
+
+ public function getParentId(){
+ if ($this->parent === null){
+ return 0;
+ }
+ return $this->parent->getId();
+ }
+
+ public function addChild(OC_News_Collection $child){
+ $this->children[] = $child;
+ }
+
+ public function getChildren(){
+ return $this->children;
+ }
+
+} \ No newline at end of file
diff --git a/apps/news/lib/foldermapper.php b/apps/news/lib/foldermapper.php
new file mode 100644
index 00000000000..45dc53c4243
--- /dev/null
+++ b/apps/news/lib/foldermapper.php
@@ -0,0 +1,148 @@
+<?php
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+/**
+ * This class maps a feed to an entry in the feeds table of the database.
+ */
+class OC_News_FolderMapper {
+
+ const tableName = '*PREFIX*news_folders';
+
+ private $userid;
+
+ public function __construct($userid = null){
+ if ($userid !== null) {
+ $this->userid = $userid;
+ }
+ $userid = OCP\USER::getUser();
+ }
+
+ /**
+ * @brief Create a folder and populate with children from the database
+ * @param id The id of the folder.
+ * @param name The name of the folder.
+ * @returns an instance of OC_News_Folder
+ */
+ public function populate($name, $id){
+ $root = new OC_News_Folder($name, $id);
+ $stmt = OCP\DB::prepare('SELECT *
+ FROM ' . self::tableName .
+ ' WHERE user_id = ? AND parent_id = ?');
+ $result = $stmt->execute(array($this->userid, $id));
+
+ while( $row = $result->fetchRow()){
+ $child = OC_News_FolderMapper::populate($row['name'], $row['id']);
+ $root->addChild($child);
+ }
+
+ $feedmapper = new OC_News_FeedMapper();
+ $feeds = $feedmapper->findByFolderId($id);
+ foreach ($feeds as $feed){
+ $root->addChild($feed);
+ }
+
+ return $root;
+ }
+
+ /**
+ * @brief Retrieve a folder from the database
+ * @param id The id of the folder in the database table.
+ * @returns an instance of OC_News_Folder
+ */
+ public function find($id){
+ $stmt = OCP\DB::prepare('SELECT *
+ FROM ' . self::tableName .
+ ' WHERE user_id = ? AND id = ?');
+ $result = $stmt->execute(array($this->userid, $id));
+
+ $row = $result->fetchRow();
+ $folder = new OC_News_Folder($row['name'], $row['id']);
+
+ return $folder;
+ }
+
+ /**
+ * @brief Retrieve a feed and all its items from the database
+ * @param id The id of the feed in the database table.
+ * @returns
+ */
+ public function findWithItems($id){
+ $stmt = OCP\DB::prepare('SELECT * FROM ' . self::tableName . ' WHERE id = ?');
+ $result = $stmt->execute(array($id));
+ $row = $result->fetchRow();
+ $url = $row['url'];
+ $title = $row['title'];
+ $feed = new OC_News_Feed($url, $title, null,$id);
+
+ $itemMapper = new OC_News_ItemMapper($feed);
+ $items = $itemMapper->findAll();
+ $feed->setItems($items);
+
+ return $feed;
+ }
+
+ /**
+ * @brief Store the folder and all its feeds into the database
+ * @param folder the folder to be saved
+ * @returns The id of the folder in the database table.
+ */
+ public function save(OC_News_Folder $folder){
+ $query = OCP\DB::prepare('
+ INSERT INTO ' . self::tableName .
+ '(name, parent_id, user_id)
+ VALUES (?, ?, ?)
+ ');
+
+ $name = $folder->getName();
+
+ if(empty($name)) {
+ $l = OC_L10N::get('news');
+ $name = $l->t('no name');
+ }
+
+ $parentid = $folder->getParentId();
+
+ $params=array(
+ htmlspecialchars_decode($name),
+ $parentid,
+ $this->userid
+ );
+ $query->execute($params);
+ $folderid = OCP\DB::insertid(self::tableName);
+
+ $folder->setId($folderid);
+ return $folderid;
+ }
+
+ public function delete(OC_News_Folder $folder){
+ $folderid = $folder->getId();
+ return deleteById(folderid);
+ }
+
+ //TODO: replace it with a DELETE INNER JOIN operation
+ public function deleteById($folderid){
+ if ($folderid == null){
+ return false;
+ }
+
+ $stmt = OCP\DB::prepare('DELETE FROM ' . self::tableName .' WHERE id = ?');
+
+ $result = $stmt->execute(array($folderid));
+
+ $feedMapper = new OC_News_FeedMapper();
+ //TODO: handle the value that the execute returns
+ $feedMapper->deleteAll($folderid);
+
+ return true;
+ }
+
+} \ No newline at end of file
diff --git a/apps/news/lib/item.php b/apps/news/lib/item.php
new file mode 100644
index 00000000000..64ae368c369
--- /dev/null
+++ b/apps/news/lib/item.php
@@ -0,0 +1,122 @@
+<?php
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+class StatusFlag{
+ const Unread = 0x02;
+ const Important = 0x04;
+ const Deleted = 0x08;
+ const Updated = 0x16;
+}
+
+/**
+ * This class models an item.
+ *
+ * It encapsulate a SimplePie_Item object and adds a status flag to it
+ */
+class OC_News_Item {
+
+ private $url;
+ private $title;
+ private $guid;
+ private $body;
+ private $status; //a bit-field set with status flags
+ private $id; //id of the item in the database table
+
+ public function __construct($url, $title, $guid, $body, $id = null){
+ $this->title = $title;
+ $this->url = $url;
+ $this->guid = $guid;
+ $this->body = $body;
+ if ($id == null) {
+ $this->status |= StatusFlag::Unread;
+ }
+ else {
+ $this->id = $id;
+ }
+ }
+
+ public function getGuid(){
+ return $this->guid;
+ }
+
+ public function setGuid($guid){
+ $this->guid = $guid;
+ }
+
+ public function getId(){
+ return $this->id;
+ }
+
+ public function setId($id){
+ $this->id = $id;
+ }
+
+ public function setRead(){
+ $this->status &= ~StatusFlag::Unread;
+ }
+
+ public function setUnread(){
+ $this->status |= StatusFlag::Unread;
+ }
+
+ public function isRead(){
+ return !($this->status & StatusFlag::Unread);
+ }
+
+ public function setImportant(){
+ $this->status |= StatusFlag::Important;
+ }
+
+ public function setUnimportant(){
+ $this->status &= ~StatusFlag::Important;
+ }
+
+ public function isImportant(){
+ return ($this->status & StatusFlag::Important);
+ }
+
+ /**
+ * NOTE: this is needed to store items in the database, otherwise
+ * the status of an item should be retrieved with methods: isRead(), isImportant(), ...
+ */
+ public function getStatus(){
+ return $this->status;
+ }
+
+ public function setStatus($status){
+ $this->status = $status;
+ }
+
+ public function getTitle(){
+ return $this->title;
+ }
+
+ public function setTitle($title){
+ $this->title = $title;
+ }
+
+ public function getUrl(){
+ return $this->url;
+ }
+
+ public function setUrl($url){
+ $this->url = $url;
+ }
+
+ public function getBody(){
+ return $this->body;
+ }
+
+ public function setBody($body){
+ $this->body = $body;
+ }
+}
diff --git a/apps/news/lib/itemmapper.php b/apps/news/lib/itemmapper.php
new file mode 100644
index 00000000000..8d6a755b161
--- /dev/null
+++ b/apps/news/lib/itemmapper.php
@@ -0,0 +1,171 @@
+<?php
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+/**
+ * This class maps an item to a row of the items table in the database.
+ * It follows the Data Mapper pattern (see http://martinfowler.com/eaaCatalog/dataMapper.html).
+ */
+class OC_News_ItemMapper {
+
+ const tableName = '*PREFIX*news_items';
+
+ public function fromRow($row){
+ $url = $row['url'];
+ $title = $row['title'];
+ $guid = $row['guid'];
+ $status = $row['status'];
+ $body = $row['body'];
+ $id = $row['id'];
+ $item = new OC_News_Item($url, $title, $guid, $body, $id);
+ $item->setStatus($status);
+
+ return $item;
+ }
+
+ /**
+ * @brief Retrieve all the item corresponding to a feed from the database
+ * @param feedid The id of the feed in the database table.
+ */
+ public function findAll($feedid){
+ $stmt = OCP\DB::prepare('SELECT * FROM ' . self::tableName . ' WHERE feed_id = ?');
+ $result = $stmt->execute(array($feedid));
+
+ $items = array();
+ while ($row = $result->fetchRow()) {
+ $item = $this->fromRow($row);
+ $items[] = $item;
+ }
+
+ return $items;
+ }
+
+ public function findIdFromGuid($guid, $feedid){
+ $stmt = OCP\DB::prepare('
+ SELECT * FROM ' . self::tableName . '
+ WHERE guid = ?
+ AND feed_id = ?
+ ');
+ $result = $stmt->execute(array($guid, $feedid));
+ $row = $result->fetchRow();
+ $id = null;
+ if ($row != null){
+ $id = $row['id'];
+ }
+ return $id;
+ }
+
+ /**
+ * @brief Update the item after its status has changed
+ * @returns The item whose status has changed.
+ */
+ public function update(OC_News_Item $item){
+
+ $itemid = $item->getId();
+ $status = $item->getStatus();
+
+ $stmt = OCP\DB::prepare('
+ UPDATE ' . self::tableName .
+ ' SET status = ?
+ WHERE id = ?
+ ');
+
+ $params=array(
+ $status,
+ $itemid
+ );
+ $stmt->execute($params);
+
+ return true;
+ }
+
+ /**
+ * @brief Save the feed and all its items into the database
+ * @returns The id of the feed in the database table.
+ */
+ public function save(OC_News_Item $item, $feedid){
+ $guid = $item->getGuid();
+ $status = $item->getStatus();
+
+ $itemid = $this->findIdFromGuid($guid, $feedid);
+
+ if ($itemid == null){
+ $title = $item->getTitle();
+ $body = $item->getBody();
+
+ $stmt = OCP\DB::prepare('
+ INSERT INTO ' . self::tableName .
+ '(url, title, body, guid, feed_id, status)
+ VALUES (?, ?, ?, ?, ?, ?)
+ ');
+
+ if(empty($title)) {
+ $l = OC_L10N::get('news');
+ $title = $l->t('no title');
+ }
+
+ if(empty($body)) {
+ $l = OC_L10N::get('news');
+ $body = $l->t('no body');
+ }
+
+ $params=array(
+ htmlspecialchars_decode($item->getUrl()),
+ htmlspecialchars_decode($title),
+ $body,
+ $guid,
+ $feedid,
+ $status
+ );
+
+ $stmt->execute($params);
+
+ $itemid = OCP\DB::insertid(self::tableName);
+ }
+ else {
+ $this->update($item);
+ }
+ $item->setId($itemid);
+ return $itemid;
+ }
+
+ /**
+ * @brief Retrieve an item from the database
+ * @param id The id of the feed in the database table.
+ */
+ public function find($id){
+ $stmt = OCP\DB::prepare('SELECT * FROM ' . self::tableName . ' WHERE id = ?');
+ $result = $stmt->execute(array($id));
+ $row = $result->fetchRow();
+
+ $item = $this->fromRow($row);
+
+ return $item;
+
+ }
+
+
+ /**
+ * @brief Permanently delete all items belonging to a feed from the database
+ * @param feedid The id of the feed that we wish to delete
+ * @return
+ */
+ public function deleteAll($feedid){
+ if ($feedid == null) {
+ return false;
+ }
+ $stmt = OCP\DB::prepare('DELETE FROM ' . self::tableName .' WHERE feed_id = ?');
+
+ $result = $stmt->execute(array($feedid));
+
+ return $result;
+ }
+} \ No newline at end of file
diff --git a/apps/news/lib/utils.php b/apps/news/lib/utils.php
new file mode 100644
index 00000000000..e18ce2969e0
--- /dev/null
+++ b/apps/news/lib/utils.php
@@ -0,0 +1,46 @@
+<?php
+/**
+* ownCloud - News app
+*
+* @author Alessandro Cosentino
+* Copyright (c) 2012 - Alessandro Cosentino <cosenal@gmail.com>
+*
+* This file is licensed under the Affero General Public License version 3 or later.
+* See the COPYING-README file
+*
+*/
+
+// load SimplePie library
+//TODO: is this file a suitable place for the following require?
+require_once('news/3rdparty/SimplePie/SimplePieAutoloader.php');
+
+class OC_News_Utils {
+
+ /**
+ * @brief Fetch a feed from remote
+ * @param url remote url of the feed
+ * @returns
+ */
+ public static function fetch($url){
+ //TODO: handle the case where fetching of the feed fails
+ $spfeed = new SimplePie_Core();
+ $spfeed->set_feed_url( $url );
+ $spfeed->enable_cache( false );
+ $spfeed->init();
+ $spfeed->handle_content_type();
+ $title = $spfeed->get_title();
+
+ $spitems = $spfeed->get_items();
+ $items = array();
+ foreach($spitems as $spitem) { //FIXME: maybe we can avoid this loop
+ $itemUrl = $spitem->get_permalink();
+ $itemTitle = $spitem->get_title();
+ $itemGUID = $spitem->get_id();
+ $itemBody = $spitem->get_content();
+ $items[] = new OC_News_Item($itemUrl, $itemTitle, $itemGUID, $itemBody);
+ }
+
+ $feed = new OC_News_Feed($url, $title, $items);
+ return $feed;
+ }
+} \ No newline at end of file
diff --git a/apps/news/settings.php b/apps/news/settings.php
new file mode 100644
index 00000000000..94d05735891
--- /dev/null
+++ b/apps/news/settings.php
@@ -0,0 +1,8 @@
+<?php
+
+//OCP\Util::addscript( "news", "admin" );
+
+$tmpl = new OCP\Template( 'news', 'settings');
+
+return $tmpl->fetchPage();
+
diff --git a/apps/news/templates/main.php b/apps/news/templates/main.php
new file mode 100644
index 00000000000..6ab1bc64ea9
--- /dev/null
+++ b/apps/news/templates/main.php
@@ -0,0 +1,36 @@
+<div id="leftcontent" class="leftcontent">
+ <ul id="feeds">
+ <?php echo $this->inc("part.feeds"); ?>
+ </ul>
+</div>
+
+<ul class="menu" id="feedfoldermenu">
+ <li class="menuItem" id="addfeed"><?php echo $l->t('Feed'); ?></li>
+ <li class="menuItem" id="addfolder"><?php echo $l->t('Folder'); ?></li>
+</ul>
+
+<div id="bottomcontrols">
+ <ul class="controls">
+ <li>
+ <button class="svg" id="addfeedfolder" title="<?php echo $l->t('Add Feed/Folder'); ?>" onclick="News.DropDownMenu.fade('ul#feedfoldermenu')"><img class="svg" src="<?php echo OCP\Util::linkTo('news', 'img/add.svg'); ?>" alt="<?php echo $l->t('Add Feed/Folder'); ?>" /></button>
+ </li>
+ <li><button class="svg" title="<?php echo $l->t('Change View'); ?>">Eye</button></li>
+ <li><button class="svg" title="<?php echo $l->t('Settings'); ?>">Settings</button></li>
+ <ul>
+</div>
+
+<div id="rightcontent" class="rightcontent" data-id="<?php echo $_['feedid']; ?>">
+ <?php
+ if ($_['feedid']){
+ echo $this->inc("part.items");
+ }
+ else {
+ echo $this->inc("part.nofeeds");
+ }
+ ?>
+</div>
+
+<!-- Dialogs -->
+<div id="dialog_holder"></div>
+<!-- End of Dialogs -->
+
diff --git a/apps/news/templates/part.addfeed.php b/apps/news/templates/part.addfeed.php
new file mode 100644
index 00000000000..6807f3c5b35
--- /dev/null
+++ b/apps/news/templates/part.addfeed.php
@@ -0,0 +1,22 @@
+
+<div id="addfeed_dialog" title="<?php echo $l->t("Add Feed"); ?>">
+<table width="100%" style="border: 0;">
+<tr>
+ <td>Add new feed</td>
+ <td>
+ <div class="add_parentfolder">
+ <button id="dropdownBtn" onclick="News.DropDownMenu.dropdown(this)">
+ <?php echo $l->t('EVERYTHING'); ?>
+ </button>
+ <input id="inputfolderid" type="hidden" name="folderid" value="0" />
+ <ul class="menu" id="dropdownmenu">
+ <?php echo $this->inc("part.folderlist"); ?>
+ </ul>
+ </div>
+ </td>
+</tr>
+<tr>
+ <td><input type="text" id="feed_add_url" placeholder="<?php echo $l->t('URL'); ?>" class="news_input" /></td>
+ <td><input type="submit" value="<?php echo $l->t('Add feed'); ?>" onclick="News.Feed.submit(this)" id="feed_add_submit" /></td>
+</tr>
+</table> \ No newline at end of file
diff --git a/apps/news/templates/part.addfolder.php b/apps/news/templates/part.addfolder.php
new file mode 100644
index 00000000000..e1217160cee
--- /dev/null
+++ b/apps/news/templates/part.addfolder.php
@@ -0,0 +1,22 @@
+
+<div id="addfolder_dialog" title="<?php echo $l->t("Add Folder"); ?>">
+<table width="100%" style="border: 0;">
+<tr>
+ <td>Add new folder</td>
+ <td>
+ <div class="add_parentfolder">
+ <button id="dropdownBtn" onclick="News.DropDownMenu.dropdown(this)">
+ <?php echo $l->t('EVERYTHING'); ?>
+ </button>
+ <input id="inputfolderid" type="hidden" name="folderid" value="0" />
+ <ul class="menu" id="dropdownmenu">
+ <?php echo $this->inc("part.folderlist"); ?>
+ </ul>
+ </div>
+ </td>
+</tr>
+<tr>
+ <td><input type="text" id="folder_add_name" placeholder="<?php echo $l->t('Folder name'); ?>" class="news_input" /></td>
+ <td><input type="submit" value="<?php echo $l->t('Add folder'); ?>" onclick="News.Folder.submit(this)" id="folder_add_submit" /></td>
+</tr>
+</table> \ No newline at end of file
diff --git a/apps/news/templates/part.feeds.php b/apps/news/templates/part.feeds.php
new file mode 100644
index 00000000000..587e73f366a
--- /dev/null
+++ b/apps/news/templates/part.feeds.php
@@ -0,0 +1,31 @@
+<?php
+ function print_folder(OC_News_Folder $folder, $depth){
+ $l = new OC_l10n('news');
+
+ echo '<ul style="margin-left:' . 10*$depth . 'px;"> <li class="folder_list" >' .
+ '<div class="collapsable" >' . strtoupper($folder->getName()) .
+ ( ($depth != 0) ? '<button class="svg action" id="feeds_delete" onClick="(News.Folder.delete(' . $folder->getId(). '))" title="' . $l->t('Delete folder') . '"></button>' .
+ '<button class="svg action" id="feeds_edit" title="' . $l->t('Rename folder') . '"></button>': '' ) .
+ '</div>';
+ echo '<ul>';
+ $children = $folder->getChildren();
+ foreach($children as $child) {
+ if ($child instanceOf OC_News_Folder){
+ print_folder($child, $depth+1);
+ }
+ elseif ($child instanceOf OC_News_Feed) { //onhover $(element).attr('id', 'newID');
+
+ echo '<li class="feeds_list" data-id="' . $child->getId() . '"><a href="' . OCP\Util::linkTo('news', 'index.php'). '?feedid=' . $child->getId() . '">' . $child->getTitle() .'</a>';
+ echo '<button class="svg action" id="feeds_delete" onClick="(News.Feed.delete(' . $child->getId(). '))" title="' . $l->t('Delete feed') . '"></button>';
+ echo '<button class="svg action" id="feeds_edit" title="' . $l->t('Edit feed') . '"></button>';
+ echo '</li>';
+ }
+ else {
+ //TODO:handle error in this case
+ }
+ }
+ echo '</ul></li></ul>';
+ }
+
+ print_folder($_['allfeeds'], 0);
+?> \ No newline at end of file
diff --git a/apps/news/templates/part.folderlist.php b/apps/news/templates/part.folderlist.php
new file mode 100644
index 00000000000..670684fd4c9
--- /dev/null
+++ b/apps/news/templates/part.folderlist.php
@@ -0,0 +1,12 @@
+<?php
+ function print_folder(OC_News_Folder $folder, $depth){
+ echo '<li class="menuItem" onclick="News.DropDownMenu.selectItem(this, ' . $folder->getId() . ')">' . strtoupper($folder->getName()) . '</li>';
+ $children = $folder->getChildren();
+ foreach($children as $child) {
+ if ($child instanceOf OC_News_Folder){
+ print_folder($child, $depth+1);
+ }
+ }
+ }
+ print_folder($_['allfeeds'], 0);
+?> \ No newline at end of file
diff --git a/apps/news/templates/part.items.php b/apps/news/templates/part.items.php
new file mode 100644
index 00000000000..24ca958e98b
--- /dev/null
+++ b/apps/news/templates/part.items.php
@@ -0,0 +1,23 @@
+<?php
+
+$feedid = isset($_['feedid']) ? $_['feedid'] : '';
+
+$itemmapper = new OC_News_ItemMapper();
+
+$items = $itemmapper->findAll($feedid);
+
+echo '<ul class="accordion">';
+foreach($items as $item) {
+ $title = $item->getTitle();
+ echo '<li>';
+ echo '<div data-id="' . $item->getId() . '"';
+ if ($item->isRead()) {
+ echo ' class="title_read">';
+ }
+ else {
+ echo ' class="title_unread" onClick="News.Feed.markItem(' . $item->getId() . ')">';
+ }
+ echo $title . '</div><div class="body">' . $item->getBody() . '</div>';
+ echo '</li>';
+}
+echo '</ul>';
diff --git a/apps/news/templates/part.nofeeds.php b/apps/news/templates/part.nofeeds.php
new file mode 100644
index 00000000000..bb78dedbf89
--- /dev/null
+++ b/apps/news/templates/part.nofeeds.php
@@ -0,0 +1,3 @@
+<div id="firstrun">
+ <?php echo $l->t('You have no feeds in your reader.') ?>
+</div> \ No newline at end of file
diff --git a/apps/news/templates/settings.php b/apps/news/templates/settings.php
new file mode 100644
index 00000000000..ccbb004f001
--- /dev/null
+++ b/apps/news/templates/settings.php
@@ -0,0 +1,8 @@
+<form id="news">
+ <fieldset class="personalblock">
+ <strong>News</strong><br />
+ <input type="text" name="opml_file" id="opml_file" placeholder="<?php echo $l->t('.opml file');?>" />
+ <br />
+
+ </fieldset>
+</form>
diff --git a/apps/news/templates/test.php b/apps/news/templates/test.php
new file mode 100644
index 00000000000..d8e31e67590
--- /dev/null
+++ b/apps/news/templates/test.php
@@ -0,0 +1,85 @@
+<?php
+
+$feedmapper = new OC_News_FeedMapper();
+$foldermapper = new OC_News_FolderMapper();
+$itemmapper = new OC_News_ItemMapper();
+
+$folder = new OC_News_Folder( 'Friends' );
+$folderid = $foldermapper->save($folder);
+
+$feed = OC_News_Utils::fetch( 'http://www.dabacon.org/newpontiff/?feed=rss2' );
+
+$feedmapper->save($feed, $folder->getId());
+
+$feed = $feedmapper->findWithItems($feed->getId());
+echo '<br>' . $feed->getTitle() . '<br>';
+$items = $feed->getItems();
+
+foreach($items as $item) {
+
+ echo $item->getTitle() . ' - ';
+ if ($item->isRead()) {
+ echo $l->t('Read');
+ }
+ else {
+ echo $l->t('Unread');
+ }
+ echo ' - ';
+ if ($item->isImportant()) {
+ echo $l->t('Important');
+ }
+ else {
+ echo $l->t('Not important');
+ }
+ echo '<br>';
+ $item->setImportant();
+}
+
+echo '<br>...after changing status';
+echo '<br>' . $feed->getTitle() . '<br>';
+
+foreach($items as $item) {
+ echo $item->getTitle() . ' - ';
+ if ($item->isRead()) {
+ echo $l->t('Read');
+ }
+ else {
+ echo $l->t('Unread');
+ }
+ echo ' - ';
+ if ($item->isImportant()) {
+ echo $l->t('Important');
+ }
+ else {
+ echo $l->t('Not important');
+ }
+ echo '<br>';
+ $item->setUnimportant();
+}
+
+$feedmapper->save($feed, $folder->getId());
+
+echo '<br>...after saving and reloading';
+
+$feed = $feedmapper->findWithItems($feed->getId());
+echo '<br>' . $feed->getTitle() . '<br>';
+$items = $feed->getItems();
+
+foreach($items as &$item) {
+
+ echo $item->getTitle() . ' - ';
+ if ($item->isRead()) {
+ echo $l->t('Read');
+ }
+ else {
+ echo $l->t('Unread');
+ }
+ echo ' - ';
+ if ($item->isImportant()) {
+ echo $l->t('Important');
+ }
+ else {
+ echo $l->t('Not important');
+ }
+ echo '<br>';
+} \ No newline at end of file
diff --git a/apps/remoteStorage/appinfo/info.xml b/apps/remoteStorage/appinfo/info.xml
index fa878762a05..1388ad9c316 100644
--- a/apps/remoteStorage/appinfo/info.xml
+++ b/apps/remoteStorage/appinfo/info.xml
@@ -7,4 +7,7 @@
<author>Michiel de Jong</author>
<require>4</require>
<shipped>true</shipped>
+ <remote>
+ <remoteStorage>webdav.php</remoteStorage>
+ </remote>
</info>
diff --git a/apps/remoteStorage/appinfo/version b/apps/remoteStorage/appinfo/version
index 490f510fc27..0e2c93950bb 100644
--- a/apps/remoteStorage/appinfo/version
+++ b/apps/remoteStorage/appinfo/version
@@ -1 +1 @@
-0.6 \ No newline at end of file
+0.7 \ No newline at end of file
diff --git a/apps/remoteStorage/appinfo/webfinger.php b/apps/remoteStorage/appinfo/webfinger.php
index 5d481f315f8..e8b237628c4 100644
--- a/apps/remoteStorage/appinfo/webfinger.php
+++ b/apps/remoteStorage/appinfo/webfinger.php
@@ -1,8 +1,8 @@
-<?php if(OC_User::userExists(WF_USER)) { ?>
+<?php if(OC_User::userExists(WF_USER)): ?>
{
"rel":"remoteStorage",
- "template":"<?php echo WF_BASEURL; ?>/apps/remoteStorage/WebDAV.php/<?php echo WF_USER; ?>/remoteStorage/{category}/",
+ "template":"<?php echo WF_BASEURL; ?>/remote.php/remoteStorage/<?php echo WF_USER; ?>/remoteStorage/{category}/",
"api":"WebDAV",
"auth":"<?php echo WF_BASEURL; ?>/?app=remoteStorage&getfile=auth.php&userid=<?php echo WF_USER; ?>"
}
-<?php } ?>
+<?php endif ?>
diff --git a/apps/remoteStorage/lib_remoteStorage.php b/apps/remoteStorage/lib_remoteStorage.php
index 42cd9c90f64..c1765640c5d 100644
--- a/apps/remoteStorage/lib_remoteStorage.php
+++ b/apps/remoteStorage/lib_remoteStorage.php
@@ -17,12 +17,11 @@ class OC_remoteStorage {
$user=OCP\USER::getUser();
$query=OCP\DB::prepare("SELECT token FROM *PREFIX*authtoken WHERE user=? AND appUrl=? AND category=? LIMIT 1");
$result=$query->execute(array($user, $appUrl, $categories));
- $ret = array();
if($row=$result->fetchRow()) {
- return base64_encode('remoteStorage:'.$row['token']);
- } else {
- return false;
- }
+ return base64_encode('remoteStorage:'.$row['token']);
+ } else {
+ return false;
+ }
}
public static function getAllTokens() {
@@ -42,13 +41,13 @@ class OC_remoteStorage {
public static function deleteToken($token) {
$user=OCP\USER::getUser();
$query=OCP\DB::prepare("DELETE FROM *PREFIX*authtoken WHERE token=? AND user=?");
- $result=$query->execute(array($token,$user));
+ $query->execute(array($token,$user));
return 'unknown';//how can we see if any rows were affected?
}
private static function addToken($token, $appUrl, $categories){
$user=OCP\USER::getUser();
$query=OCP\DB::prepare("INSERT INTO *PREFIX*authtoken (`token`,`appUrl`,`user`,`category`) VALUES(?,?,?,?)");
- $result=$query->execute(array($token,$appUrl,$user,$categories));
+ $query->execute(array($token,$appUrl,$user,$categories));
}
public static function createCategories($appUrl, $categories) {
$token=uniqid();
diff --git a/apps/remoteStorage/oauth_ro_auth.php b/apps/remoteStorage/oauth_ro_auth.php
index 12d02d1cf5d..bed3093c3b3 100644
--- a/apps/remoteStorage/oauth_ro_auth.php
+++ b/apps/remoteStorage/oauth_ro_auth.php
@@ -9,10 +9,10 @@
class OC_Connector_Sabre_Auth_ro_oauth extends Sabre_DAV_Auth_Backend_AbstractBasic {
private $validTokens;
- private $category;
+ private $category;
public function __construct($validTokensArg, $categoryArg) {
$this->validTokens = $validTokensArg;
- $this->category = $categoryArg;
+ $this->category = $categoryArg;
}
/**
@@ -25,16 +25,16 @@ class OC_Connector_Sabre_Auth_ro_oauth extends Sabre_DAV_Auth_Backend_AbstractBa
*/
protected function validateUserPass($username, $password){
//always give read-only:
- if(($_SERVER['REQUEST_METHOD'] == 'OPTIONS')
+ if(($_SERVER['REQUEST_METHOD'] == 'OPTIONS')
|| (isset($this->validTokens[$password]))
- || (($_SERVER['REQUEST_METHOD'] == 'GET') && ($this->category == 'public'))
- ) {
+ || (($_SERVER['REQUEST_METHOD'] == 'GET') && ($this->category == 'public'))
+ ) {
OC_Util::setUpFS();
return true;
} else {
- //var_export($_SERVER);
- //var_export($this->validTokens);
- //die('not getting in with "'.$username.'"/"'.$password.'"!');
+ //var_export($_SERVER);
+ //var_export($this->validTokens);
+ //die('not getting in with "'.$username.'"/"'.$password.'"!');
return false;
}
}
@@ -48,8 +48,8 @@ class OC_Connector_Sabre_Auth_ro_oauth extends Sabre_DAV_Auth_Backend_AbstractBa
$userpass = $auth->getUserPass();
if (!$userpass) {
if(($_SERVER['REQUEST_METHOD'] == 'OPTIONS')
- ||(($_SERVER['REQUEST_METHOD'] == 'GET') && ($this->category == 'public'))
- ) {
+ ||(($_SERVER['REQUEST_METHOD'] == 'GET') && ($this->category == 'public'))
+ ) {
$userpass = array('', '');
} else {
$auth->requireLogin();
diff --git a/apps/remoteStorage/WebDAV.php b/apps/remoteStorage/webdav.php
index 7a81c18e0af..8d8ec6a45a1 100644
--- a/apps/remoteStorage/WebDAV.php
+++ b/apps/remoteStorage/webdav.php
@@ -25,22 +25,7 @@
*
*/
-
-// Do not load FS ...
-$RUNTIME_NOSETUPFS = true;
-
-
-require_once('../../lib/base.php');
-
-require_once('../../lib/user.php');
-require_once('../../lib/public/user.php');
-
-require_once('../../lib/app.php');
-require_once('../../lib/public/app.php');
-
-require_once('../../3rdparty/Sabre/DAV/Auth/IBackend.php');
-require_once('../../3rdparty/Sabre/DAV/Auth/Backend/AbstractBasic.php');
-require_once('../../lib/connector/sabre/auth.php');
+OC_App::loadApps(array('filesystem','authentication'));
OCP\App::checkAppEnabled('remoteStorage');
require_once('lib_remoteStorage.php');
@@ -61,14 +46,15 @@ if(isset($_SERVER['HTTP_ORIGIN'])) {
header('Access-Control-Allow-Origin: *');
}
-$path = substr($_SERVER["REQUEST_URI"], strlen($_SERVER["SCRIPT_NAME"]));
+$path = substr($_SERVER["REQUEST_URI"], strlen($baseuri));
+
$pathParts = explode('/', $path);
// for webdav:
-// 0/ 1 / 2 / 3...
-// /$ownCloudUser/remoteStorage/$category/
+// 0 / 1 / 2...
+// $ownCloudUser/remoteStorage/$category/
-if(count($pathParts) >= 3 && $pathParts[0] == '') {
- list($dummy, $ownCloudUser, $dummy2, $category) = $pathParts;
+if(count($pathParts) >= 2) {
+ list($ownCloudUser, $dummy2, $category) = $pathParts;
OC_Util::setupFS($ownCloudUser);
@@ -77,13 +63,13 @@ if(count($pathParts) >= 3 && $pathParts[0] == '') {
$server = new Sabre_DAV_Server($publicDir);
// Path to our script
- $server->setBaseUri(OC::$WEBROOT."/apps/remoteStorage/WebDAV.php/$ownCloudUser");
+ $server->setBaseUri($baseuri.$ownCloudUser);
// Auth backend
$authBackend = new OC_Connector_Sabre_Auth_ro_oauth(
- OC_remoteStorage::getValidTokens($ownCloudUser, $category),
- $category
- );
+ OC_remoteStorage::getValidTokens($ownCloudUser, $category),
+ $category
+ );
$authPlugin = new Sabre_DAV_Auth_Plugin($authBackend,'ownCloud');//should use $validTokens here
$server->addPlugin($authPlugin);
diff --git a/apps/shorty/.htaccess b/apps/shorty/.htaccess
new file mode 100644
index 00000000000..b7c4189ae49
--- /dev/null
+++ b/apps/shorty/.htaccess
@@ -0,0 +1,45 @@
+#
+# @package shorty an ownCloud url shortener plugin
+# @category internet
+# @author Christian Reiner
+# @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+# @license GNU Affero General Public license (AGPL)
+# @link information
+# @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+#
+# 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/>.
+#
+
+# @file .htaccess
+# These rules try to detect any requests that carry a "shorty id" and map them to forward.php?<shorty id>
+# That way you can use shorter (or more simple) urls to publish or relay links
+# @author Christian Reiner
+
+RewriteEngine On
+
+# example: http://.../apps/shorty/index.php/0123456789
+RewriteRule index\.php/([a-z0-9]{4,12})$ ../../public.php?service=shorty_relay&id=$1 [NC,L]
+
+# example: http://.../apps/shorty/index.php?0123456789
+RewriteCond %{QUERY_STRING} ^([a-z0-9]{4,12})$ [NC]
+RewriteRule index\.php ../../public.php?service=shorty_relay&id=%{QUERY_STRING} [L]
+
+# example: http://.../apps/shorty/?0123456789
+RewriteCond %{QUERY_STRING} ^([a-z0-9]{4,12})$ [NC]
+RewriteCond %{REQUEST_URI} meta/.+\.php
+RewriteRule - ../../public.php?service=shorty_relay&id=%{QUERY_STRING} [L]
+
+# example: http://.../apps/shorty/0123456789
+RewriteRule ^([a-z0-9]{4,12})$ ../../public.php?service=shorty_relay&id=$1 [NC,L]
diff --git a/apps/shorty/3rdparty/js/jquery.tinysort.min.js b/apps/shorty/3rdparty/js/jquery.tinysort.min.js
new file mode 100644
index 00000000000..225f799ca9d
--- /dev/null
+++ b/apps/shorty/3rdparty/js/jquery.tinysort.min.js
@@ -0,0 +1 @@
+(function(b){var o=!1,d=null,u=parseFloat,j=String.fromCharCode,q=Math.min,l=/(\d+\.?\d*)$/g,g,a=[],h,m,t=9472,f={},c;for(var p=32,k=j(p),r=255;p<r;p++,k=j(p).toLowerCase()){if(!~a.indexOf(k)){a.push(k)}}a.sort();b.tinysort={id:"TinySort",version:"1.3.20",copyright:"Copyright (c) 2008-2012 Ron Valstar",uri:"http://tinysort.sjeiti.com/",licenced:{MIT:"http://www.opensource.org/licenses/mit-license.php",GPL:"http://www.gnu.org/licenses/gpl.html"},defaults:{order:"asc",attr:d,data:d,useVal:o,place:"start",returns:o,cases:o,forceStrings:o,sortFunction:d,charOrder:g}};b.fn.extend({tinysort:function(V,L){if(V&&typeof(V)!="string"){L=V;V=d}var T=b.extend({},b.tinysort.defaults,L),v,Q=this,z=b(this).length,ae={},W=!(!V||V==""),H=!(T.attr===d||T.attr==""),ah=T.data!==d,J=W&&V[0]==":",C=J?Q.filter(V):Q,F=T.sortFunction,s=T.order=="asc"?1:-1,P=[];if(T.charOrder!=g){g=T.charOrder;if(!T.charOrder){m=false;t=9472;f={};c=h=d}else{h=a.slice(0);m=false;for(var S=[],B=function(i,ai){S.push(ai);f[T.cases?i:i.toLowerCase()]=ai},N="",X="z",aa=g.length,ac,Z,ad=0;ad<aa;ad++){var x=g[ad],ab=x.charCodeAt(),I=ab>96&&ab<123;if(!I){if(x=="["){var D=S.length,M=D?S[D-1]:X,w=g.substr(ad+1).match(/[^\]]*/)[0],R=w.match(/{[^}]*}/g);if(R){for(ac=0,Z=R.length;ac<Z;ac++){var O=R[ac];ad+=O.length;w=w.replace(O,"");B(O.replace(/[{}]/g,""),M);m=true}}for(ac=0,Z=w.length;ac<Z;ac++){B(M,w[ac])}ad+=w.length+1}else{if(x=="{"){var G=g.substr(ad+1).match(/[^}]*/)[0];B(G,j(t++));ad+=G.length+1;m=true}else{S.push(x)}}}if(S.length&&(I||ad===aa-1)){var E=S.join("");N+=E;b.each(E,function(i,ai){h.splice(h.indexOf(ai),1)});var A=S.slice(0);A.splice(0,0,h.indexOf(X)+1,0);Array.prototype.splice.apply(h,A);S.length=0}if(ad+1===aa){c=new RegExp("["+N+"]","gi")}else{if(I){X=x}}}}}if(!F){F=T.order=="rand"?function(){return Math.random()<0.5?1:-1}:function(av,at){var au=o,am=!T.cases?n(av.s):av.s,ak=!T.cases?n(at.s):at.s;if(!T.forceStrings){var aj=am&&am.match(l),aw=ak&&ak.match(l);if(aj&&aw){var ar=am.substr(0,am.length-aj[0].length),aq=ak.substr(0,ak.length-aw[0].length);if(ar==aq){au=!o;am=u(aj[0]);ak=u(aw[0])}}}var ai=s*(am<ak?-1:(am>ak?1:0));if(!au&&T.charOrder){if(m){for(var ax in f){var al=f[ax];am=am.replace(ax,al);ak=ak.replace(ax,al)}}if(am.match(c)!==d||ak.match(c)!==d){for(var ap=0,ao=q(am.length,ak.length);ap<ao;ap++){var an=h.indexOf(am[ap]),i=h.indexOf(ak[ap]);if(ai=s*(an<i?-1:(an>i?1:0))){break}}}}return ai}}Q.each(function(ak,al){var am=b(al),ai=W?(J?C.filter(al):am.find(V)):am,an=ah?ai.data(T.data):(H?ai.attr(T.attr):(T.useVal?ai.val():ai.text())),aj=am.parent();if(!ae[aj]){ae[aj]={s:[],n:[]}}if(ai.length>0){ae[aj].s.push({s:an,e:am,n:ak})}else{ae[aj].n.push({e:am,n:ak})}});for(v in ae){ae[v].s.sort(F)}for(v in ae){var ag=ae[v],K=[],Y=z,af=[0,0],ad;switch(T.place){case"first":b.each(ag.s,function(ai,aj){Y=q(Y,aj.n)});break;case"org":b.each(ag.s,function(ai,aj){K.push(aj.n)});break;case"end":Y=ag.n.length;break;default:Y=0}for(ad=0;ad<z;ad++){var y=e(K,ad)?!o:ad>=Y&&ad<Y+ag.s.length,U=(y?ag.s:ag.n)[af[y?0:1]].e;U.parent().append(U);if(y||!T.returns){P.push(U.get(0))}af[y?0:1]++}}Q.length=0;Array.prototype.push.apply(Q,P);return Q}});function n(i){return i&&i.toLowerCase?i.toLowerCase():i}function e(v,x){for(var w=0,s=v.length;w<s;w++){if(v[w]==x){return !o}}return o}b.fn.TinySort=b.fn.Tinysort=b.fn.tsort=b.fn.tinysort})(jQuery); \ No newline at end of file
diff --git a/apps/shorty/3rdparty/php/phpqrcode.php b/apps/shorty/3rdparty/php/phpqrcode.php
new file mode 100644
index 00000000000..80adb9df239
--- /dev/null
+++ b/apps/shorty/3rdparty/php/phpqrcode.php
@@ -0,0 +1,3312 @@
+<?php
+
+/*
+ * PHP QR Code encoder
+ *
+ * This file contains MERGED version of PHP QR Code library.
+ * It was auto-generated from full version for your convenience.
+ *
+ * This merged version was configured to not requre any external files,
+ * with disabled cache, error loging and weker but faster mask matching.
+ * If you need tune it up please use non-merged version.
+ *
+ * For full version, documentation, examples of use please visit:
+ *
+ * http://phpqrcode.sourceforge.net/
+ * https://sourceforge.net/projects/phpqrcode/
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+
+
+/*
+ * Version: 1.1.4
+ * Build: 2010100721
+ */
+
+
+
+//---- qrconst.php -----------------------------
+
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Common constants
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ // Encoding modes
+
+ define('QR_MODE_NUL', -1);
+ define('QR_MODE_NUM', 0);
+ define('QR_MODE_AN', 1);
+ define('QR_MODE_8', 2);
+ define('QR_MODE_KANJI', 3);
+ define('QR_MODE_STRUCTURE', 4);
+
+ // Levels of error correction.
+
+ define('QR_ECLEVEL_L', 0);
+ define('QR_ECLEVEL_M', 1);
+ define('QR_ECLEVEL_Q', 2);
+ define('QR_ECLEVEL_H', 3);
+
+ // Supported output formats
+
+ define('QR_FORMAT_TEXT', 0);
+ define('QR_FORMAT_PNG', 1);
+
+ class qrstr {
+ public static function set(&$srctab, $x, $y, $repl, $replLen = false) {
+ $srctab[$y] = substr_replace($srctab[$y], ($replLen !== false)?substr($repl,0,$replLen):$repl, $x, ($replLen !== false)?$replLen:strlen($repl));
+ }
+ }
+
+
+
+//---- merged_config.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Config file, tuned-up for merged verion
+ */
+
+ define('QR_CACHEABLE', false); // use cache - more disk reads but less CPU power, masks and format templates are stored there
+ define('QR_CACHE_DIR', false); // used when QR_CACHEABLE === true
+ define('QR_LOG_DIR', false); // default error logs dir
+
+ define('QR_FIND_BEST_MASK', true); // if true, estimates best mask (spec. default, but extremally slow; set to false to significant performance boost but (propably) worst quality code
+ define('QR_FIND_FROM_RANDOM', 2); // if false, checks all masks available, otherwise value tells count of masks need to be checked, mask id are got randomly
+ define('QR_DEFAULT_MASK', 2); // when QR_FIND_BEST_MASK === false
+
+ define('QR_PNG_MAXIMUM_SIZE', 1024); // maximum allowed png image width (in pixels), tune to make sure GD and PHP can handle such big images
+
+
+
+
+//---- qrtools.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Toolset, handy and debug utilites.
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ class QRtools {
+
+ //----------------------------------------------------------------------
+ public static function binarize($frame)
+ {
+ $len = count($frame);
+ foreach ($frame as &$frameLine) {
+
+ for($i=0; $i<$len; $i++) {
+ $frameLine[$i] = (ord($frameLine[$i])&1)?'1':'0';
+ }
+ }
+
+ return $frame;
+ }
+
+ //----------------------------------------------------------------------
+ public static function tcpdfBarcodeArray($code, $mode = 'QR,L', $tcPdfVersion = '4.5.037')
+ {
+ $barcode_array = array();
+
+ if (!is_array($mode))
+ $mode = explode(',', $mode);
+
+ $eccLevel = 'L';
+
+ if (count($mode) > 1) {
+ $eccLevel = $mode[1];
+ }
+
+ $qrTab = QRcode::text($code, false, $eccLevel);
+ $size = count($qrTab);
+
+ $barcode_array['num_rows'] = $size;
+ $barcode_array['num_cols'] = $size;
+ $barcode_array['bcode'] = array();
+
+ foreach ($qrTab as $line) {
+ $arrAdd = array();
+ foreach(str_split($line) as $char)
+ $arrAdd[] = ($char=='1')?1:0;
+ $barcode_array['bcode'][] = $arrAdd;
+ }
+
+ return $barcode_array;
+ }
+
+ //----------------------------------------------------------------------
+ public static function clearCache()
+ {
+ self::$frames = array();
+ }
+
+ //----------------------------------------------------------------------
+ public static function buildCache()
+ {
+ QRtools::markTime('before_build_cache');
+
+ $mask = new QRmask();
+ for ($a=1; $a <= QRSPEC_VERSION_MAX; $a++) {
+ $frame = QRspec::newFrame($a);
+ if (QR_IMAGE) {
+ $fileName = QR_CACHE_DIR.'frame_'.$a.'.png';
+ QRimage::png(self::binarize($frame), $fileName, 1, 0);
+ }
+
+ $width = count($frame);
+ $bitMask = array_fill(0, $width, array_fill(0, $width, 0));
+ for ($maskNo=0; $maskNo<8; $maskNo++)
+ $mask->makeMaskNo($maskNo, $width, $frame, $bitMask, true);
+ }
+
+ QRtools::markTime('after_build_cache');
+ }
+
+ //----------------------------------------------------------------------
+ public static function log($outfile, $err)
+ {
+ if (QR_LOG_DIR !== false) {
+ if ($err != '') {
+ if ($outfile !== false) {
+ file_put_contents(QR_LOG_DIR.basename($outfile).'-errors.txt', date('Y-m-d H:i:s').': '.$err, FILE_APPEND);
+ } else {
+ file_put_contents(QR_LOG_DIR.'errors.txt', date('Y-m-d H:i:s').': '.$err, FILE_APPEND);
+ }
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------
+ public static function dumpMask($frame)
+ {
+ $width = count($frame);
+ for($y=0;$y<$width;$y++) {
+ for($x=0;$x<$width;$x++) {
+ echo ord($frame[$y][$x]).',';
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------
+ public static function markTime($markerId)
+ {
+ list($usec, $sec) = explode(" ", microtime());
+ $time = ((float)$usec + (float)$sec);
+
+ if (!isset($GLOBALS['qr_time_bench']))
+ $GLOBALS['qr_time_bench'] = array();
+
+ $GLOBALS['qr_time_bench'][$markerId] = $time;
+ }
+
+ //----------------------------------------------------------------------
+ public static function timeBenchmark()
+ {
+ self::markTime('finish');
+
+ $lastTime = 0;
+ $startTime = 0;
+ $p = 0;
+
+ echo '<table cellpadding="3" cellspacing="1">
+ <thead><tr style="border-bottom:1px solid silver"><td colspan="2" style="text-align:center">BENCHMARK</td></tr></thead>
+ <tbody>';
+
+ foreach($GLOBALS['qr_time_bench'] as $markerId=>$thisTime) {
+ if ($p > 0) {
+ echo '<tr><th style="text-align:right">till '.$markerId.': </th><td>'.number_format($thisTime-$lastTime, 6).'s</td></tr>';
+ } else {
+ $startTime = $thisTime;
+ }
+
+ $p++;
+ $lastTime = $thisTime;
+ }
+
+ echo '</tbody><tfoot>
+ <tr style="border-top:2px solid black"><th style="text-align:right">TOTAL: </th><td>'.number_format($lastTime-$startTime, 6).'s</td></tr>
+ </tfoot>
+ </table>';
+ }
+
+ }
+
+ //##########################################################################
+
+ QRtools::markTime('start');
+
+
+
+
+//---- qrspec.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * QR Code specifications
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * The following data / specifications are taken from
+ * "Two dimensional symbol -- QR-code -- Basic Specification" (JIS X0510:2004)
+ * or
+ * "Automatic identification and data capture techniques --
+ * QR Code 2005 bar code symbology specification" (ISO/IEC 18004:2006)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ define('QRSPEC_VERSION_MAX', 40);
+ define('QRSPEC_WIDTH_MAX', 177);
+
+ define('QRCAP_WIDTH', 0);
+ define('QRCAP_WORDS', 1);
+ define('QRCAP_REMINDER', 2);
+ define('QRCAP_EC', 3);
+
+ class QRspec {
+
+ public static $capacity = array(
+ array( 0, 0, 0, array( 0, 0, 0, 0)),
+ array( 21, 26, 0, array( 7, 10, 13, 17)), // 1
+ array( 25, 44, 7, array( 10, 16, 22, 28)),
+ array( 29, 70, 7, array( 15, 26, 36, 44)),
+ array( 33, 100, 7, array( 20, 36, 52, 64)),
+ array( 37, 134, 7, array( 26, 48, 72, 88)), // 5
+ array( 41, 172, 7, array( 36, 64, 96, 112)),
+ array( 45, 196, 0, array( 40, 72, 108, 130)),
+ array( 49, 242, 0, array( 48, 88, 132, 156)),
+ array( 53, 292, 0, array( 60, 110, 160, 192)),
+ array( 57, 346, 0, array( 72, 130, 192, 224)), //10
+ array( 61, 404, 0, array( 80, 150, 224, 264)),
+ array( 65, 466, 0, array( 96, 176, 260, 308)),
+ array( 69, 532, 0, array( 104, 198, 288, 352)),
+ array( 73, 581, 3, array( 120, 216, 320, 384)),
+ array( 77, 655, 3, array( 132, 240, 360, 432)), //15
+ array( 81, 733, 3, array( 144, 280, 408, 480)),
+ array( 85, 815, 3, array( 168, 308, 448, 532)),
+ array( 89, 901, 3, array( 180, 338, 504, 588)),
+ array( 93, 991, 3, array( 196, 364, 546, 650)),
+ array( 97, 1085, 3, array( 224, 416, 600, 700)), //20
+ array(101, 1156, 4, array( 224, 442, 644, 750)),
+ array(105, 1258, 4, array( 252, 476, 690, 816)),
+ array(109, 1364, 4, array( 270, 504, 750, 900)),
+ array(113, 1474, 4, array( 300, 560, 810, 960)),
+ array(117, 1588, 4, array( 312, 588, 870, 1050)), //25
+ array(121, 1706, 4, array( 336, 644, 952, 1110)),
+ array(125, 1828, 4, array( 360, 700, 1020, 1200)),
+ array(129, 1921, 3, array( 390, 728, 1050, 1260)),
+ array(133, 2051, 3, array( 420, 784, 1140, 1350)),
+ array(137, 2185, 3, array( 450, 812, 1200, 1440)), //30
+ array(141, 2323, 3, array( 480, 868, 1290, 1530)),
+ array(145, 2465, 3, array( 510, 924, 1350, 1620)),
+ array(149, 2611, 3, array( 540, 980, 1440, 1710)),
+ array(153, 2761, 3, array( 570, 1036, 1530, 1800)),
+ array(157, 2876, 0, array( 570, 1064, 1590, 1890)), //35
+ array(161, 3034, 0, array( 600, 1120, 1680, 1980)),
+ array(165, 3196, 0, array( 630, 1204, 1770, 2100)),
+ array(169, 3362, 0, array( 660, 1260, 1860, 2220)),
+ array(173, 3532, 0, array( 720, 1316, 1950, 2310)),
+ array(177, 3706, 0, array( 750, 1372, 2040, 2430)) //40
+ );
+
+ //----------------------------------------------------------------------
+ public static function getDataLength($version, $level)
+ {
+ return self::$capacity[$version][QRCAP_WORDS] - self::$capacity[$version][QRCAP_EC][$level];
+ }
+
+ //----------------------------------------------------------------------
+ public static function getECCLength($version, $level)
+ {
+ return self::$capacity[$version][QRCAP_EC][$level];
+ }
+
+ //----------------------------------------------------------------------
+ public static function getWidth($version)
+ {
+ return self::$capacity[$version][QRCAP_WIDTH];
+ }
+
+ //----------------------------------------------------------------------
+ public static function getRemainder($version)
+ {
+ return self::$capacity[$version][QRCAP_REMINDER];
+ }
+
+ //----------------------------------------------------------------------
+ public static function getMinimumVersion($size, $level)
+ {
+
+ for($i=1; $i<= QRSPEC_VERSION_MAX; $i++) {
+ $words = self::$capacity[$i][QRCAP_WORDS] - self::$capacity[$i][QRCAP_EC][$level];
+ if($words >= $size)
+ return $i;
+ }
+
+ return -1;
+ }
+
+ //######################################################################
+
+ public static $lengthTableBits = array(
+ array(10, 12, 14),
+ array( 9, 11, 13),
+ array( 8, 16, 16),
+ array( 8, 10, 12)
+ );
+
+ //----------------------------------------------------------------------
+ public static function lengthIndicator($mode, $version)
+ {
+ if ($mode == QR_MODE_STRUCTURE)
+ return 0;
+
+ if ($version <= 9) {
+ $l = 0;
+ } else if ($version <= 26) {
+ $l = 1;
+ } else {
+ $l = 2;
+ }
+
+ return self::$lengthTableBits[$mode][$l];
+ }
+
+ //----------------------------------------------------------------------
+ public static function maximumWords($mode, $version)
+ {
+ if($mode == QR_MODE_STRUCTURE)
+ return 3;
+
+ if($version <= 9) {
+ $l = 0;
+ } else if($version <= 26) {
+ $l = 1;
+ } else {
+ $l = 2;
+ }
+
+ $bits = self::$lengthTableBits[$mode][$l];
+ $words = (1 << $bits) - 1;
+
+ if($mode == QR_MODE_KANJI) {
+ $words *= 2; // the number of bytes is required
+ }
+
+ return $words;
+ }
+
+ // Error correction code -----------------------------------------------
+ // Table of the error correction code (Reed-Solomon block)
+ // See Table 12-16 (pp.30-36), JIS X0510:2004.
+
+ public static $eccTable = array(
+ array(array( 0, 0), array( 0, 0), array( 0, 0), array( 0, 0)),
+ array(array( 1, 0), array( 1, 0), array( 1, 0), array( 1, 0)), // 1
+ array(array( 1, 0), array( 1, 0), array( 1, 0), array( 1, 0)),
+ array(array( 1, 0), array( 1, 0), array( 2, 0), array( 2, 0)),
+ array(array( 1, 0), array( 2, 0), array( 2, 0), array( 4, 0)),
+ array(array( 1, 0), array( 2, 0), array( 2, 2), array( 2, 2)), // 5
+ array(array( 2, 0), array( 4, 0), array( 4, 0), array( 4, 0)),
+ array(array( 2, 0), array( 4, 0), array( 2, 4), array( 4, 1)),
+ array(array( 2, 0), array( 2, 2), array( 4, 2), array( 4, 2)),
+ array(array( 2, 0), array( 3, 2), array( 4, 4), array( 4, 4)),
+ array(array( 2, 2), array( 4, 1), array( 6, 2), array( 6, 2)), //10
+ array(array( 4, 0), array( 1, 4), array( 4, 4), array( 3, 8)),
+ array(array( 2, 2), array( 6, 2), array( 4, 6), array( 7, 4)),
+ array(array( 4, 0), array( 8, 1), array( 8, 4), array(12, 4)),
+ array(array( 3, 1), array( 4, 5), array(11, 5), array(11, 5)),
+ array(array( 5, 1), array( 5, 5), array( 5, 7), array(11, 7)), //15
+ array(array( 5, 1), array( 7, 3), array(15, 2), array( 3, 13)),
+ array(array( 1, 5), array(10, 1), array( 1, 15), array( 2, 17)),
+ array(array( 5, 1), array( 9, 4), array(17, 1), array( 2, 19)),
+ array(array( 3, 4), array( 3, 11), array(17, 4), array( 9, 16)),
+ array(array( 3, 5), array( 3, 13), array(15, 5), array(15, 10)), //20
+ array(array( 4, 4), array(17, 0), array(17, 6), array(19, 6)),
+ array(array( 2, 7), array(17, 0), array( 7, 16), array(34, 0)),
+ array(array( 4, 5), array( 4, 14), array(11, 14), array(16, 14)),
+ array(array( 6, 4), array( 6, 14), array(11, 16), array(30, 2)),
+ array(array( 8, 4), array( 8, 13), array( 7, 22), array(22, 13)), //25
+ array(array(10, 2), array(19, 4), array(28, 6), array(33, 4)),
+ array(array( 8, 4), array(22, 3), array( 8, 26), array(12, 28)),
+ array(array( 3, 10), array( 3, 23), array( 4, 31), array(11, 31)),
+ array(array( 7, 7), array(21, 7), array( 1, 37), array(19, 26)),
+ array(array( 5, 10), array(19, 10), array(15, 25), array(23, 25)), //30
+ array(array(13, 3), array( 2, 29), array(42, 1), array(23, 28)),
+ array(array(17, 0), array(10, 23), array(10, 35), array(19, 35)),
+ array(array(17, 1), array(14, 21), array(29, 19), array(11, 46)),
+ array(array(13, 6), array(14, 23), array(44, 7), array(59, 1)),
+ array(array(12, 7), array(12, 26), array(39, 14), array(22, 41)), //35
+ array(array( 6, 14), array( 6, 34), array(46, 10), array( 2, 64)),
+ array(array(17, 4), array(29, 14), array(49, 10), array(24, 46)),
+ array(array( 4, 18), array(13, 32), array(48, 14), array(42, 32)),
+ array(array(20, 4), array(40, 7), array(43, 22), array(10, 67)),
+ array(array(19, 6), array(18, 31), array(34, 34), array(20, 61)),//40
+ );
+
+ //----------------------------------------------------------------------
+ // CACHEABLE!!!
+
+ public static function getEccSpec($version, $level, array &$spec)
+ {
+ if (count($spec) < 5) {
+ $spec = array(0,0,0,0,0);
+ }
+
+ $b1 = self::$eccTable[$version][$level][0];
+ $b2 = self::$eccTable[$version][$level][1];
+ $data = self::getDataLength($version, $level);
+ $ecc = self::getECCLength($version, $level);
+
+ if($b2 == 0) {
+ $spec[0] = $b1;
+ $spec[1] = (int)($data / $b1);
+ $spec[2] = (int)($ecc / $b1);
+ $spec[3] = 0;
+ $spec[4] = 0;
+ } else {
+ $spec[0] = $b1;
+ $spec[1] = (int)($data / ($b1 + $b2));
+ $spec[2] = (int)($ecc / ($b1 + $b2));
+ $spec[3] = $b2;
+ $spec[4] = $spec[1] + 1;
+ }
+ }
+
+ // Alignment pattern ---------------------------------------------------
+
+ // Positions of alignment patterns.
+ // This array includes only the second and the third position of the
+ // alignment patterns. Rest of them can be calculated from the distance
+ // between them.
+
+ // See Table 1 in Appendix E (pp.71) of JIS X0510:2004.
+
+ public static $alignmentPattern = array(
+ array( 0, 0),
+ array( 0, 0), array(18, 0), array(22, 0), array(26, 0), array(30, 0), // 1- 5
+ array(34, 0), array(22, 38), array(24, 42), array(26, 46), array(28, 50), // 6-10
+ array(30, 54), array(32, 58), array(34, 62), array(26, 46), array(26, 48), //11-15
+ array(26, 50), array(30, 54), array(30, 56), array(30, 58), array(34, 62), //16-20
+ array(28, 50), array(26, 50), array(30, 54), array(28, 54), array(32, 58), //21-25
+ array(30, 58), array(34, 62), array(26, 50), array(30, 54), array(26, 52), //26-30
+ array(30, 56), array(34, 60), array(30, 58), array(34, 62), array(30, 54), //31-35
+ array(24, 50), array(28, 54), array(32, 58), array(26, 54), array(30, 58), //35-40
+ );
+
+
+ /** --------------------------------------------------------------------
+ * Put an alignment marker.
+ * @param frame
+ * @param width
+ * @param ox,oy center coordinate of the pattern
+ */
+ public static function putAlignmentMarker(array &$frame, $ox, $oy)
+ {
+ $finder = array(
+ "\xa1\xa1\xa1\xa1\xa1",
+ "\xa1\xa0\xa0\xa0\xa1",
+ "\xa1\xa0\xa1\xa0\xa1",
+ "\xa1\xa0\xa0\xa0\xa1",
+ "\xa1\xa1\xa1\xa1\xa1"
+ );
+
+ $yStart = $oy-2;
+ $xStart = $ox-2;
+
+ for($y=0; $y<5; $y++) {
+ QRstr::set($frame, $xStart, $yStart+$y, $finder[$y]);
+ }
+ }
+
+ //----------------------------------------------------------------------
+ public static function putAlignmentPattern($version, &$frame, $width)
+ {
+ if($version < 2)
+ return;
+
+ $d = self::$alignmentPattern[$version][1] - self::$alignmentPattern[$version][0];
+ if($d < 0) {
+ $w = 2;
+ } else {
+ $w = (int)(($width - self::$alignmentPattern[$version][0]) / $d + 2);
+ }
+
+ if($w * $w - 3 == 1) {
+ $x = self::$alignmentPattern[$version][0];
+ $y = self::$alignmentPattern[$version][0];
+ self::putAlignmentMarker($frame, $x, $y);
+ return;
+ }
+
+ $cx = self::$alignmentPattern[$version][0];
+ for($x=1; $x<$w - 1; $x++) {
+ self::putAlignmentMarker($frame, 6, $cx);
+ self::putAlignmentMarker($frame, $cx, 6);
+ $cx += $d;
+ }
+
+ $cy = self::$alignmentPattern[$version][0];
+ for($y=0; $y<$w-1; $y++) {
+ $cx = self::$alignmentPattern[$version][0];
+ for($x=0; $x<$w-1; $x++) {
+ self::putAlignmentMarker($frame, $cx, $cy);
+ $cx += $d;
+ }
+ $cy += $d;
+ }
+ }
+
+ // Version information pattern -----------------------------------------
+
+ // Version information pattern (BCH coded).
+ // See Table 1 in Appendix D (pp.68) of JIS X0510:2004.
+
+ // size: [QRSPEC_VERSION_MAX - 6]
+
+ public static $versionPattern = array(
+ 0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d,
+ 0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9,
+ 0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75,
+ 0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64,
+ 0x27541, 0x28c69
+ );
+
+ //----------------------------------------------------------------------
+ public static function getVersionPattern($version)
+ {
+ if($version < 7 || $version > QRSPEC_VERSION_MAX)
+ return 0;
+
+ return self::$versionPattern[$version -7];
+ }
+
+ // Format information --------------------------------------------------
+ // See calcFormatInfo in tests/test_qrspec.c (orginal qrencode c lib)
+
+ public static $formatInfo = array(
+ array(0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976),
+ array(0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0),
+ array(0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed),
+ array(0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b)
+ );
+
+ public static function getFormatInfo($mask, $level)
+ {
+ if($mask < 0 || $mask > 7)
+ return 0;
+
+ if($level < 0 || $level > 3)
+ return 0;
+
+ return self::$formatInfo[$level][$mask];
+ }
+
+ // Frame ---------------------------------------------------------------
+ // Cache of initial frames.
+
+ public static $frames = array();
+
+ /** --------------------------------------------------------------------
+ * Put a finder pattern.
+ * @param frame
+ * @param width
+ * @param ox,oy upper-left coordinate of the pattern
+ */
+ public static function putFinderPattern(&$frame, $ox, $oy)
+ {
+ $finder = array(
+ "\xc1\xc1\xc1\xc1\xc1\xc1\xc1",
+ "\xc1\xc0\xc0\xc0\xc0\xc0\xc1",
+ "\xc1\xc0\xc1\xc1\xc1\xc0\xc1",
+ "\xc1\xc0\xc1\xc1\xc1\xc0\xc1",
+ "\xc1\xc0\xc1\xc1\xc1\xc0\xc1",
+ "\xc1\xc0\xc0\xc0\xc0\xc0\xc1",
+ "\xc1\xc1\xc1\xc1\xc1\xc1\xc1"
+ );
+
+ for($y=0; $y<7; $y++) {
+ QRstr::set($frame, $ox, $oy+$y, $finder[$y]);
+ }
+ }
+
+ //----------------------------------------------------------------------
+ public static function createFrame($version)
+ {
+ $width = self::$capacity[$version][QRCAP_WIDTH];
+ $frameLine = str_repeat ("\0", $width);
+ $frame = array_fill(0, $width, $frameLine);
+
+ // Finder pattern
+ self::putFinderPattern($frame, 0, 0);
+ self::putFinderPattern($frame, $width - 7, 0);
+ self::putFinderPattern($frame, 0, $width - 7);
+
+ // Separator
+ $yOffset = $width - 7;
+
+ for($y=0; $y<7; $y++) {
+ $frame[$y][7] = "\xc0";
+ $frame[$y][$width - 8] = "\xc0";
+ $frame[$yOffset][7] = "\xc0";
+ $yOffset++;
+ }
+
+ $setPattern = str_repeat("\xc0", 8);
+
+ QRstr::set($frame, 0, 7, $setPattern);
+ QRstr::set($frame, $width-8, 7, $setPattern);
+ QRstr::set($frame, 0, $width - 8, $setPattern);
+
+ // Format info
+ $setPattern = str_repeat("\x84", 9);
+ QRstr::set($frame, 0, 8, $setPattern);
+ QRstr::set($frame, $width - 8, 8, $setPattern, 8);
+
+ $yOffset = $width - 8;
+
+ for($y=0; $y<8; $y++,$yOffset++) {
+ $frame[$y][8] = "\x84";
+ $frame[$yOffset][8] = "\x84";
+ }
+
+ // Timing pattern
+
+ for($i=1; $i<$width-15; $i++) {
+ $frame[6][7+$i] = chr(0x90 | ($i & 1));
+ $frame[7+$i][6] = chr(0x90 | ($i & 1));
+ }
+
+ // Alignment pattern
+ self::putAlignmentPattern($version, $frame, $width);
+
+ // Version information
+ if($version >= 7) {
+ $vinf = self::getVersionPattern($version);
+
+ $v = $vinf;
+
+ for($x=0; $x<6; $x++) {
+ for($y=0; $y<3; $y++) {
+ $frame[($width - 11)+$y][$x] = chr(0x88 | ($v & 1));
+ $v = $v >> 1;
+ }
+ }
+
+ $v = $vinf;
+ for($y=0; $y<6; $y++) {
+ for($x=0; $x<3; $x++) {
+ $frame[$y][$x+($width - 11)] = chr(0x88 | ($v & 1));
+ $v = $v >> 1;
+ }
+ }
+ }
+
+ // and a little bit...
+ $frame[$width - 8][8] = "\x81";
+
+ return $frame;
+ }
+
+ //----------------------------------------------------------------------
+ public static function debug($frame, $binary_mode = false)
+ {
+ if ($binary_mode) {
+
+ foreach ($frame as &$frameLine) {
+ $frameLine = join('<span class="m">&nbsp;&nbsp;</span>', explode('0', $frameLine));
+ $frameLine = join('&#9608;&#9608;', explode('1', $frameLine));
+ }
+
+ ?>
+ <style>
+ .m { background-color: white; }
+ </style>
+ <?php
+ echo '<pre><tt><br/ ><br/ ><br/ >&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
+ echo join("<br/ >&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;", $frame);
+ echo '</tt></pre><br/ ><br/ ><br/ ><br/ ><br/ ><br/ >';
+
+ } else {
+
+ foreach ($frame as &$frameLine) {
+ $frameLine = join('<span class="m">&nbsp;</span>', explode("\xc0", $frameLine));
+ $frameLine = join('<span class="m">&#9618;</span>', explode("\xc1", $frameLine));
+ $frameLine = join('<span class="p">&nbsp;</span>', explode("\xa0", $frameLine));
+ $frameLine = join('<span class="p">&#9618;</span>', explode("\xa1", $frameLine));
+ $frameLine = join('<span class="s">&#9671;</span>', explode("\x84", $frameLine)); //format 0
+ $frameLine = join('<span class="s">&#9670;</span>', explode("\x85", $frameLine)); //format 1
+ $frameLine = join('<span class="x">&#9762;</span>', explode("\x81", $frameLine)); //special bit
+ $frameLine = join('<span class="c">&nbsp;</span>', explode("\x90", $frameLine)); //clock 0
+ $frameLine = join('<span class="c">&#9719;</span>', explode("\x91", $frameLine)); //clock 1
+ $frameLine = join('<span class="f">&nbsp;</span>', explode("\x88", $frameLine)); //version
+ $frameLine = join('<span class="f">&#9618;</span>', explode("\x89", $frameLine)); //version
+ $frameLine = join('&#9830;', explode("\x01", $frameLine));
+ $frameLine = join('&#8901;', explode("\0", $frameLine));
+ }
+
+ ?>
+ <style>
+ .p { background-color: yellow; }
+ .m { background-color: #00FF00; }
+ .s { background-color: #FF0000; }
+ .c { background-color: aqua; }
+ .x { background-color: pink; }
+ .f { background-color: gold; }
+ </style>
+ <?php
+ echo "<pre><tt>";
+ echo join("<br/ >", $frame);
+ echo "</tt></pre>";
+
+ }
+ }
+
+ //----------------------------------------------------------------------
+ public static function serial($frame)
+ {
+ return gzcompress(join("\n", $frame), 9);
+ }
+
+ //----------------------------------------------------------------------
+ public static function unserial($code)
+ {
+ return explode("\n", gzuncompress($code));
+ }
+
+ //----------------------------------------------------------------------
+ public static function newFrame($version)
+ {
+ if($version < 1 || $version > QRSPEC_VERSION_MAX)
+ return null;
+
+ if(!isset(self::$frames[$version])) {
+
+ $fileName = QR_CACHE_DIR.'frame_'.$version.'.dat';
+
+ if (QR_CACHEABLE) {
+ if (file_exists($fileName)) {
+ self::$frames[$version] = self::unserial(file_get_contents($fileName));
+ } else {
+ self::$frames[$version] = self::createFrame($version);
+ file_put_contents($fileName, self::serial(self::$frames[$version]));
+ }
+ } else {
+ self::$frames[$version] = self::createFrame($version);
+ }
+ }
+
+ if(is_null(self::$frames[$version]))
+ return null;
+
+ return self::$frames[$version];
+ }
+
+ //----------------------------------------------------------------------
+ public static function rsBlockNum($spec) { return $spec[0] + $spec[3]; }
+ public static function rsBlockNum1($spec) { return $spec[0]; }
+ public static function rsDataCodes1($spec) { return $spec[1]; }
+ public static function rsEccCodes1($spec) { return $spec[2]; }
+ public static function rsBlockNum2($spec) { return $spec[3]; }
+ public static function rsDataCodes2($spec) { return $spec[4]; }
+ public static function rsEccCodes2($spec) { return $spec[2]; }
+ public static function rsDataLength($spec) { return ($spec[0] * $spec[1]) + ($spec[3] * $spec[4]); }
+ public static function rsEccLength($spec) { return ($spec[0] + $spec[3]) * $spec[2]; }
+
+ }
+
+
+
+//---- qrimage.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Image output of code using GD2
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ define('QR_IMAGE', true);
+
+ class QRimage {
+
+ //----------------------------------------------------------------------
+ public static function png($frame, $filename = false, $pixelPerPoint = 4, $outerFrame = 4,$saveandprint=FALSE)
+ {
+ $image = self::image($frame, $pixelPerPoint, $outerFrame);
+
+ if ($filename === false) {
+ Header("Content-type: image/png");
+ ImagePng($image);
+ } else {
+ if($saveandprint===TRUE){
+ ImagePng($image, $filename);
+ header("Content-type: image/png");
+ ImagePng($image);
+ }else{
+ ImagePng($image, $filename);
+ }
+ }
+
+ ImageDestroy($image);
+ }
+
+ //----------------------------------------------------------------------
+ public static function jpg($frame, $filename = false, $pixelPerPoint = 8, $outerFrame = 4, $q = 85)
+ {
+ $image = self::image($frame, $pixelPerPoint, $outerFrame);
+
+ if ($filename === false) {
+ Header("Content-type: image/jpeg");
+ ImageJpeg($image, null, $q);
+ } else {
+ ImageJpeg($image, $filename, $q);
+ }
+
+ ImageDestroy($image);
+ }
+
+ //----------------------------------------------------------------------
+ private static function image($frame, $pixelPerPoint = 4, $outerFrame = 4)
+ {
+ $h = count($frame);
+ $w = strlen($frame[0]);
+
+ $imgW = $w + 2*$outerFrame;
+ $imgH = $h + 2*$outerFrame;
+
+ $base_image =ImageCreate($imgW, $imgH);
+
+ $col[0] = ImageColorAllocate($base_image,255,255,255);
+ $col[1] = ImageColorAllocate($base_image,0,0,0);
+
+ imagefill($base_image, 0, 0, $col[0]);
+
+ for($y=0; $y<$h; $y++) {
+ for($x=0; $x<$w; $x++) {
+ if ($frame[$y][$x] == '1') {
+ ImageSetPixel($base_image,$x+$outerFrame,$y+$outerFrame,$col[1]);
+ }
+ }
+ }
+
+ $target_image =ImageCreate($imgW * $pixelPerPoint, $imgH * $pixelPerPoint);
+ ImageCopyResized($target_image, $base_image, 0, 0, 0, 0, $imgW * $pixelPerPoint, $imgH * $pixelPerPoint, $imgW, $imgH);
+ ImageDestroy($base_image);
+
+ return $target_image;
+ }
+ }
+
+
+
+//---- qrinput.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Input encoding class
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ define('STRUCTURE_HEADER_BITS', 20);
+ define('MAX_STRUCTURED_SYMBOLS', 16);
+
+ class QRinputItem {
+
+ public $mode;
+ public $size;
+ public $data;
+ public $bstream;
+
+ public function __construct($mode, $size, $data, $bstream = null)
+ {
+ $setData = array_slice($data, 0, $size);
+
+ if (count($setData) < $size) {
+ $setData = array_merge($setData, array_fill(0,$size-count($setData),0));
+ }
+
+ if(!QRinput::check($mode, $size, $setData)) {
+ throw new Exception('Error m:'.$mode.',s:'.$size.',d:'.join(',',$setData));
+ return null;
+ }
+
+ $this->mode = $mode;
+ $this->size = $size;
+ $this->data = $setData;
+ $this->bstream = $bstream;
+ }
+
+ //----------------------------------------------------------------------
+ public function encodeModeNum($version)
+ {
+ try {
+
+ $words = (int)($this->size / 3);
+ $bs = new QRbitstream();
+
+ $val = 0x1;
+ $bs->appendNum(4, $val);
+ $bs->appendNum(QRspec::lengthIndicator(QR_MODE_NUM, $version), $this->size);
+
+ for($i=0; $i<$words; $i++) {
+ $val = (ord($this->data[$i*3 ]) - ord('0')) * 100;
+ $val += (ord($this->data[$i*3+1]) - ord('0')) * 10;
+ $val += (ord($this->data[$i*3+2]) - ord('0'));
+ $bs->appendNum(10, $val);
+ }
+
+ if($this->size - $words * 3 == 1) {
+ $val = ord($this->data[$words*3]) - ord('0');
+ $bs->appendNum(4, $val);
+ } else if($this->size - $words * 3 == 2) {
+ $val = (ord($this->data[$words*3 ]) - ord('0')) * 10;
+ $val += (ord($this->data[$words*3+1]) - ord('0'));
+ $bs->appendNum(7, $val);
+ }
+
+ $this->bstream = $bs;
+ return 0;
+
+ } catch (Exception $e) {
+ return -1;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ public function encodeModeAn($version)
+ {
+ try {
+ $words = (int)($this->size / 2);
+ $bs = new QRbitstream();
+
+ $bs->appendNum(4, 0x02);
+ $bs->appendNum(QRspec::lengthIndicator(QR_MODE_AN, $version), $this->size);
+
+ for($i=0; $i<$words; $i++) {
+ $val = (int)QRinput::lookAnTable(ord($this->data[$i*2 ])) * 45;
+ $val += (int)QRinput::lookAnTable(ord($this->data[$i*2+1]));
+
+ $bs->appendNum(11, $val);
+ }
+
+ if($this->size & 1) {
+ $val = QRinput::lookAnTable(ord($this->data[$words * 2]));
+ $bs->appendNum(6, $val);
+ }
+
+ $this->bstream = $bs;
+ return 0;
+
+ } catch (Exception $e) {
+ return -1;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ public function encodeMode8($version)
+ {
+ try {
+ $bs = new QRbitstream();
+
+ $bs->appendNum(4, 0x4);
+ $bs->appendNum(QRspec::lengthIndicator(QR_MODE_8, $version), $this->size);
+
+ for($i=0; $i<$this->size; $i++) {
+ $bs->appendNum(8, ord($this->data[$i]));
+ }
+
+ $this->bstream = $bs;
+ return 0;
+
+ } catch (Exception $e) {
+ return -1;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ public function encodeModeKanji($version)
+ {
+ try {
+
+ $bs = new QRbitrtream();
+
+ $bs->appendNum(4, 0x8);
+ $bs->appendNum(QRspec::lengthIndicator(QR_MODE_KANJI, $version), (int)($this->size / 2));
+
+ for($i=0; $i<$this->size; $i+=2) {
+ $val = (ord($this->data[$i]) << 8) | ord($this->data[$i+1]);
+ if($val <= 0x9ffc) {
+ $val -= 0x8140;
+ } else {
+ $val -= 0xc140;
+ }
+
+ $h = ($val >> 8) * 0xc0;
+ $val = ($val & 0xff) + $h;
+
+ $bs->appendNum(13, $val);
+ }
+
+ $this->bstream = $bs;
+ return 0;
+
+ } catch (Exception $e) {
+ return -1;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ public function encodeModeStructure()
+ {
+ try {
+ $bs = new QRbitstream();
+
+ $bs->appendNum(4, 0x03);
+ $bs->appendNum(4, ord($this->data[1]) - 1);
+ $bs->appendNum(4, ord($this->data[0]) - 1);
+ $bs->appendNum(8, ord($this->data[2]));
+
+ $this->bstream = $bs;
+ return 0;
+
+ } catch (Exception $e) {
+ return -1;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ public function estimateBitStreamSizeOfEntry($version)
+ {
+ $bits = 0;
+
+ if($version == 0)
+ $version = 1;
+
+ switch($this->mode) {
+ case QR_MODE_NUM: $bits = QRinput::estimateBitsModeNum($this->size); break;
+ case QR_MODE_AN: $bits = QRinput::estimateBitsModeAn($this->size); break;
+ case QR_MODE_8: $bits = QRinput::estimateBitsMode8($this->size); break;
+ case QR_MODE_KANJI: $bits = QRinput::estimateBitsModeKanji($this->size);break;
+ case QR_MODE_STRUCTURE: return STRUCTURE_HEADER_BITS;
+ default:
+ return 0;
+ }
+
+ $l = QRspec::lengthIndicator($this->mode, $version);
+ $m = 1 << $l;
+ $num = (int)(($this->size + $m - 1) / $m);
+
+ $bits += $num * (4 + $l);
+
+ return $bits;
+ }
+
+ //----------------------------------------------------------------------
+ public function encodeBitStream($version)
+ {
+ try {
+
+ unset($this->bstream);
+ $words = QRspec::maximumWords($this->mode, $version);
+
+ if($this->size > $words) {
+
+ $st1 = new QRinputItem($this->mode, $words, $this->data);
+ $st2 = new QRinputItem($this->mode, $this->size - $words, array_slice($this->data, $words));
+
+ $st1->encodeBitStream($version);
+ $st2->encodeBitStream($version);
+
+ $this->bstream = new QRbitstream();
+ $this->bstream->append($st1->bstream);
+ $this->bstream->append($st2->bstream);
+
+ unset($st1);
+ unset($st2);
+
+ } else {
+
+ $ret = 0;
+
+ switch($this->mode) {
+ case QR_MODE_NUM: $ret = $this->encodeModeNum($version); break;
+ case QR_MODE_AN: $ret = $this->encodeModeAn($version); break;
+ case QR_MODE_8: $ret = $this->encodeMode8($version); break;
+ case QR_MODE_KANJI: $ret = $this->encodeModeKanji($version);break;
+ case QR_MODE_STRUCTURE: $ret = $this->encodeModeStructure(); break;
+
+ default:
+ break;
+ }
+
+ if($ret < 0)
+ return -1;
+ }
+
+ return $this->bstream->size();
+
+ } catch (Exception $e) {
+ return -1;
+ }
+ }
+ };
+
+ //##########################################################################
+
+ class QRinput {
+
+ public $items;
+
+ private $version;
+ private $level;
+
+ //----------------------------------------------------------------------
+ public function __construct($version = 0, $level = QR_ECLEVEL_L)
+ {
+ if ($version < 0 || $version > QRSPEC_VERSION_MAX || $level > QR_ECLEVEL_H) {
+ throw new Exception('Invalid version no');
+ return NULL;
+ }
+
+ $this->version = $version;
+ $this->level = $level;
+ }
+
+ //----------------------------------------------------------------------
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ //----------------------------------------------------------------------
+ public function setVersion($version)
+ {
+ if($version < 0 || $version > QRSPEC_VERSION_MAX) {
+ throw new Exception('Invalid version no');
+ return -1;
+ }
+
+ $this->version = $version;
+
+ return 0;
+ }
+
+ //----------------------------------------------------------------------
+ public function getErrorCorrectionLevel()
+ {
+ return $this->level;
+ }
+
+ //----------------------------------------------------------------------
+ public function setErrorCorrectionLevel($level)
+ {
+ if($level > QR_ECLEVEL_H) {
+ throw new Exception('Invalid ECLEVEL');
+ return -1;
+ }
+
+ $this->level = $level;
+
+ return 0;
+ }
+
+ //----------------------------------------------------------------------
+ public function appendEntry(QRinputItem $entry)
+ {
+ $this->items[] = $entry;
+ }
+
+ //----------------------------------------------------------------------
+ public function append($mode, $size, $data)
+ {
+ try {
+ $entry = new QRinputItem($mode, $size, $data);
+ $this->items[] = $entry;
+ return 0;
+ } catch (Exception $e) {
+ return -1;
+ }
+ }
+
+ //----------------------------------------------------------------------
+
+ public function insertStructuredAppendHeader($size, $index, $parity)
+ {
+ if( $size > MAX_STRUCTURED_SYMBOLS ) {
+ throw new Exception('insertStructuredAppendHeader wrong size');
+ }
+
+ if( $index <= 0 || $index > MAX_STRUCTURED_SYMBOLS ) {
+ throw new Exception('insertStructuredAppendHeader wrong index');
+ }
+
+ $buf = array($size, $index, $parity);
+
+ try {
+ $entry = new QRinputItem(QR_MODE_STRUCTURE, 3, buf);
+ array_unshift($this->items, $entry);
+ return 0;
+ } catch (Exception $e) {
+ return -1;
+ }
+ }
+
+ //----------------------------------------------------------------------
+ public function calcParity()
+ {
+ $parity = 0;
+
+ foreach($this->items as $item) {
+ if($item->mode != QR_MODE_STRUCTURE) {
+ for($i=$item->size-1; $i>=0; $i--) {
+ $parity ^= $item->data[$i];
+ }
+ }
+ }
+
+ return $parity;
+ }
+
+ //----------------------------------------------------------------------
+ public static function checkModeNum($size, $data)
+ {
+ for($i=0; $i<$size; $i++) {
+ if((ord($data[$i]) < ord('0')) || (ord($data[$i]) > ord('9'))){
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ //----------------------------------------------------------------------
+ public static function estimateBitsModeNum($size)
+ {
+ $w = (int)$size / 3;
+ $bits = $w * 10;
+
+ switch($size - $w * 3) {
+ case 1:
+ $bits += 4;
+ break;
+ case 2:
+ $bits += 7;
+ break;
+ default:
+ break;
+ }
+
+ return $bits;
+ }
+
+ //----------------------------------------------------------------------
+ public static $anTable = array(
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+ );
+
+ //----------------------------------------------------------------------
+ public static function lookAnTable($c)
+ {
+ return (($c > 127)?-1:self::$anTable[$c]);
+ }
+
+ //----------------------------------------------------------------------
+ public static function checkModeAn($size, $data)
+ {
+ for($i=0; $i<$size; $i++) {
+ if (self::lookAnTable(ord($data[$i])) == -1) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ //----------------------------------------------------------------------
+ public static function estimateBitsModeAn($size)
+ {
+ $w = (int)($size / 2);
+ $bits = $w * 11;
+
+ if($size & 1) {
+ $bits += 6;
+ }
+
+ return $bits;
+ }
+
+ //----------------------------------------------------------------------
+ public static function estimateBitsMode8($size)
+ {
+ return $size * 8;
+ }
+
+ //----------------------------------------------------------------------
+ public function estimateBitsModeKanji($size)
+ {
+ return (int)(($size / 2) * 13);
+ }
+
+ //----------------------------------------------------------------------
+ public static function checkModeKanji($size, $data)
+ {
+ if($size & 1)
+ return false;
+
+ for($i=0; $i<$size; $i+=2) {
+ $val = (ord($data[$i]) << 8) | ord($data[$i+1]);
+ if( $val < 0x8140
+ || ($val > 0x9ffc && $val < 0xe040)
+ || $val > 0xebbf) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /***********************************************************************
+ * Validation
+ **********************************************************************/
+
+ public static function check($mode, $size, $data)
+ {
+ if($size <= 0)
+ return false;
+
+ switch($mode) {
+ case QR_MODE_NUM: return self::checkModeNum($size, $data); break;
+ case QR_MODE_AN: return self::checkModeAn($size, $data); break;
+ case QR_MODE_KANJI: return self::checkModeKanji($size, $data); break;
+ case QR_MODE_8: return true; break;
+ case QR_MODE_STRUCTURE: return true; break;
+
+ default:
+ break;
+ }
+
+ return false;
+ }
+
+
+ //----------------------------------------------------------------------
+ public function estimateBitStreamSize($version)
+ {
+ $bits = 0;
+
+ foreach($this->items as $item) {
+ $bits += $item->estimateBitStreamSizeOfEntry($version);
+ }
+
+ return $bits;
+ }
+
+ //----------------------------------------------------------------------
+ public function estimateVersion()
+ {
+ $version = 0;
+ $prev = 0;
+ do {
+ $prev = $version;
+ $bits = $this->estimateBitStreamSize($prev);
+ $version = QRspec::getMinimumVersion((int)(($bits + 7) / 8), $this->level);
+ if ($version < 0) {
+ return -1;
+ }
+ } while ($version > $prev);
+
+ return $version;
+ }
+
+ //----------------------------------------------------------------------
+ public static function lengthOfCode($mode, $version, $bits)
+ {
+ $payload = $bits - 4 - QRspec::lengthIndicator($mode, $version);
+ switch($mode) {
+ case QR_MODE_NUM:
+ $chunks = (int)($payload / 10);
+ $remain = $payload - $chunks * 10;
+ $size = $chunks * 3;
+ if($remain >= 7) {
+ $size += 2;
+ } else if($remain >= 4) {
+ $size += 1;
+ }
+ break;
+ case QR_MODE_AN:
+ $chunks = (int)($payload / 11);
+ $remain = $payload - $chunks * 11;
+ $size = $chunks * 2;
+ if($remain >= 6)
+ $size++;
+ break;
+ case QR_MODE_8:
+ $size = (int)($payload / 8);
+ break;
+ case QR_MODE_KANJI:
+ $size = (int)(($payload / 13) * 2);
+ break;
+ case QR_MODE_STRUCTURE:
+ $size = (int)($payload / 8);
+ break;
+ default:
+ $size = 0;
+ break;
+ }
+
+ $maxsize = QRspec::maximumWords($mode, $version);
+ if($size < 0) $size = 0;
+ if($size > $maxsize) $size = $maxsize;
+
+ return $size;
+ }
+
+ //----------------------------------------------------------------------
+ public function createBitStream()
+ {
+ $total = 0;
+
+ foreach($this->items as $item) {
+ $bits = $item->encodeBitStream($this->version);
+
+ if($bits < 0)
+ return -1;
+
+ $total += $bits;
+ }
+
+ return $total;
+ }
+
+ //----------------------------------------------------------------------
+ public function convertData()
+ {
+ $ver = $this->estimateVersion();
+ if($ver > $this->getVersion()) {
+ $this->setVersion($ver);
+ }
+
+ for(;;) {
+ $bits = $this->createBitStream();
+
+ if($bits < 0)
+ return -1;
+
+ $ver = QRspec::getMinimumVersion((int)(($bits + 7) / 8), $this->level);
+ if($ver < 0) {
+ throw new Exception('WRONG VERSION');
+ return -1;
+ } else if($ver > $this->getVersion()) {
+ $this->setVersion($ver);
+ } else {
+ break;
+ }
+ }
+
+ return 0;
+ }
+
+ //----------------------------------------------------------------------
+ public function appendPaddingBit(&$bstream)
+ {
+ $bits = $bstream->size();
+ $maxwords = QRspec::getDataLength($this->version, $this->level);
+ $maxbits = $maxwords * 8;
+
+ if ($maxbits == $bits) {
+ return 0;
+ }
+
+ if ($maxbits - $bits < 5) {
+ return $bstream->appendNum($maxbits - $bits, 0);
+ }
+
+ $bits += 4;
+ $words = (int)(($bits + 7) / 8);
+
+ $padding = new QRbitstream();
+ $ret = $padding->appendNum($words * 8 - $bits + 4, 0);
+
+ if($ret < 0)
+ return $ret;
+
+ $padlen = $maxwords - $words;
+
+ if($padlen > 0) {
+
+ $padbuf = array();
+ for($i=0; $i<$padlen; $i++) {
+ $padbuf[$i] = ($i&1)?0x11:0xec;
+ }
+
+ $ret = $padding->appendBytes($padlen, $padbuf);
+
+ if($ret < 0)
+ return $ret;
+
+ }
+
+ $ret = $bstream->append($padding);
+
+ return $ret;
+ }
+
+ //----------------------------------------------------------------------
+ public function mergeBitStream()
+ {
+ if($this->convertData() < 0) {
+ return null;
+ }
+
+ $bstream = new QRbitstream();
+
+ foreach($this->items as $item) {
+ $ret = $bstream->append($item->bstream);
+ if($ret < 0) {
+ return null;
+ }
+ }
+
+ return $bstream;
+ }
+
+ //----------------------------------------------------------------------
+ public function getBitStream()
+ {
+
+ $bstream = $this->mergeBitStream();
+
+ if($bstream == null) {
+ return null;
+ }
+
+ $ret = $this->appendPaddingBit($bstream);
+ if($ret < 0) {
+ return null;
+ }
+
+ return $bstream;
+ }
+
+ //----------------------------------------------------------------------
+ public function getByteStream()
+ {
+ $bstream = $this->getBitStream();
+ if($bstream == null) {
+ return null;
+ }
+
+ return $bstream->toByte();
+ }
+ }
+
+
+
+
+
+
+//---- qrbitstream.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Bitstream class
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ class QRbitstream {
+
+ public $data = array();
+
+ //----------------------------------------------------------------------
+ public function size()
+ {
+ return count($this->data);
+ }
+
+ //----------------------------------------------------------------------
+ public function allocate($setLength)
+ {
+ $this->data = array_fill(0, $setLength, 0);
+ return 0;
+ }
+
+ //----------------------------------------------------------------------
+ public static function newFromNum($bits, $num)
+ {
+ $bstream = new QRbitstream();
+ $bstream->allocate($bits);
+
+ $mask = 1 << ($bits - 1);
+ for($i=0; $i<$bits; $i++) {
+ if($num & $mask) {
+ $bstream->data[$i] = 1;
+ } else {
+ $bstream->data[$i] = 0;
+ }
+ $mask = $mask >> 1;
+ }
+
+ return $bstream;
+ }
+
+ //----------------------------------------------------------------------
+ public static function newFromBytes($size, $data)
+ {
+ $bstream = new QRbitstream();
+ $bstream->allocate($size * 8);
+ $p=0;
+
+ for($i=0; $i<$size; $i++) {
+ $mask = 0x80;
+ for($j=0; $j<8; $j++) {
+ if($data[$i] & $mask) {
+ $bstream->data[$p] = 1;
+ } else {
+ $bstream->data[$p] = 0;
+ }
+ $p++;
+ $mask = $mask >> 1;
+ }
+ }
+
+ return $bstream;
+ }
+
+ //----------------------------------------------------------------------
+ public function append(QRbitstream $arg)
+ {
+ if (is_null($arg)) {
+ return -1;
+ }
+
+ if($arg->size() == 0) {
+ return 0;
+ }
+
+ if($this->size() == 0) {
+ $this->data = $arg->data;
+ return 0;
+ }
+
+ $this->data = array_values(array_merge($this->data, $arg->data));
+
+ return 0;
+ }
+
+ //----------------------------------------------------------------------
+ public function appendNum($bits, $num)
+ {
+ if ($bits == 0)
+ return 0;
+
+ $b = QRbitstream::newFromNum($bits, $num);
+
+ if(is_null($b))
+ return -1;
+
+ $ret = $this->append($b);
+ unset($b);
+
+ return $ret;
+ }
+
+ //----------------------------------------------------------------------
+ public function appendBytes($size, $data)
+ {
+ if ($size == 0)
+ return 0;
+
+ $b = QRbitstream::newFromBytes($size, $data);
+
+ if(is_null($b))
+ return -1;
+
+ $ret = $this->append($b);
+ unset($b);
+
+ return $ret;
+ }
+
+ //----------------------------------------------------------------------
+ public function toByte()
+ {
+
+ $size = $this->size();
+
+ if($size == 0) {
+ return array();
+ }
+
+ $data = array_fill(0, (int)(($size + 7) / 8), 0);
+ $bytes = (int)($size / 8);
+
+ $p = 0;
+
+ for($i=0; $i<$bytes; $i++) {
+ $v = 0;
+ for($j=0; $j<8; $j++) {
+ $v = $v << 1;
+ $v |= $this->data[$p];
+ $p++;
+ }
+ $data[$i] = $v;
+ }
+
+ if($size & 7) {
+ $v = 0;
+ for($j=0; $j<($size & 7); $j++) {
+ $v = $v << 1;
+ $v |= $this->data[$p];
+ $p++;
+ }
+ $data[$bytes] = $v;
+ }
+
+ return $data;
+ }
+
+ }
+
+
+
+
+//---- qrsplit.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Input splitting classes
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * The following data / specifications are taken from
+ * "Two dimensional symbol -- QR-code -- Basic Specification" (JIS X0510:2004)
+ * or
+ * "Automatic identification and data capture techniques --
+ * QR Code 2005 bar code symbology specification" (ISO/IEC 18004:2006)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+ class QRsplit {
+
+ public $dataStr = '';
+ public $input;
+ public $modeHint;
+
+ //----------------------------------------------------------------------
+ public function __construct($dataStr, $input, $modeHint)
+ {
+ $this->dataStr = $dataStr;
+ $this->input = $input;
+ $this->modeHint = $modeHint;
+ }
+
+ //----------------------------------------------------------------------
+ public static function isdigitat($str, $pos)
+ {
+ if ($pos >= strlen($str))
+ return false;
+
+ return ((ord($str[$pos]) >= ord('0'))&&(ord($str[$pos]) <= ord('9')));
+ }
+
+ //----------------------------------------------------------------------
+ public static function isalnumat($str, $pos)
+ {
+ if ($pos >= strlen($str))
+ return false;
+
+ return (QRinput::lookAnTable(ord($str[$pos])) >= 0);
+ }
+
+ //----------------------------------------------------------------------
+ public function identifyMode($pos)
+ {
+ if ($pos >= strlen($this->dataStr))
+ return QR_MODE_NUL;
+
+ $c = $this->dataStr[$pos];
+
+ if(self::isdigitat($this->dataStr, $pos)) {
+ return QR_MODE_NUM;
+ } else if(self::isalnumat($this->dataStr, $pos)) {
+ return QR_MODE_AN;
+ } else if($this->modeHint == QR_MODE_KANJI) {
+
+ if ($pos+1 < strlen($this->dataStr))
+ {
+ $d = $this->dataStr[$pos+1];
+ $word = (ord($c) << 8) | ord($d);
+ if(($word >= 0x8140 && $word <= 0x9ffc) || ($word >= 0xe040 && $word <= 0xebbf)) {
+ return QR_MODE_KANJI;
+ }
+ }
+ }
+
+ return QR_MODE_8;
+ }
+
+ //----------------------------------------------------------------------
+ public function eatNum()
+ {
+ $ln = QRspec::lengthIndicator(QR_MODE_NUM, $this->input->getVersion());
+
+ $p = 0;
+ while(self::isdigitat($this->dataStr, $p)) {
+ $p++;
+ }
+
+ $run = $p;
+ $mode = $this->identifyMode($p);
+
+ if($mode == QR_MODE_8) {
+ $dif = QRinput::estimateBitsModeNum($run) + 4 + $ln
+ + QRinput::estimateBitsMode8(1) // + 4 + l8
+ - QRinput::estimateBitsMode8($run + 1); // - 4 - l8
+ if($dif > 0) {
+ return $this->eat8();
+ }
+ }
+ if($mode == QR_MODE_AN) {
+ $dif = QRinput::estimateBitsModeNum($run) + 4 + $ln
+ + QRinput::estimateBitsModeAn(1) // + 4 + la
+ - QRinput::estimateBitsModeAn($run + 1);// - 4 - la
+ if($dif > 0) {
+ return $this->eatAn();
+ }
+ }
+
+ $ret = $this->input->append(QR_MODE_NUM, $run, str_split($this->dataStr));
+ if($ret < 0)
+ return -1;
+
+ return $run;
+ }
+
+ //----------------------------------------------------------------------
+ public function eatAn()
+ {
+ $la = QRspec::lengthIndicator(QR_MODE_AN, $this->input->getVersion());
+ $ln = QRspec::lengthIndicator(QR_MODE_NUM, $this->input->getVersion());
+
+ $p = 0;
+
+ while(self::isalnumat($this->dataStr, $p)) {
+ if(self::isdigitat($this->dataStr, $p)) {
+ $q = $p;
+ while(self::isdigitat($this->dataStr, $q)) {
+ $q++;
+ }
+
+ $dif = QRinput::estimateBitsModeAn($p) // + 4 + la
+ + QRinput::estimateBitsModeNum($q - $p) + 4 + $ln
+ - QRinput::estimateBitsModeAn($q); // - 4 - la
+
+ if($dif < 0) {
+ break;
+ } else {
+ $p = $q;
+ }
+ } else {
+ $p++;
+ }
+ }
+
+ $run = $p;
+
+ if(!self::isalnumat($this->dataStr, $p)) {
+ $dif = QRinput::estimateBitsModeAn($run) + 4 + $la
+ + QRinput::estimateBitsMode8(1) // + 4 + l8
+ - QRinput::estimateBitsMode8($run + 1); // - 4 - l8
+ if($dif > 0) {
+ return $this->eat8();
+ }
+ }
+
+ $ret = $this->input->append(QR_MODE_AN, $run, str_split($this->dataStr));
+ if($ret < 0)
+ return -1;
+
+ return $run;
+ }
+
+ //----------------------------------------------------------------------
+ public function eatKanji()
+ {
+ $p = 0;
+
+ while($this->identifyMode($p) == QR_MODE_KANJI) {
+ $p += 2;
+ }
+
+ $ret = $this->input->append(QR_MODE_KANJI, $p, str_split($this->dataStr));
+ if($ret < 0)
+ return -1;
+
+ return $run;
+ }
+
+ //----------------------------------------------------------------------
+ public function eat8()
+ {
+ $la = QRspec::lengthIndicator(QR_MODE_AN, $this->input->getVersion());
+ $ln = QRspec::lengthIndicator(QR_MODE_NUM, $this->input->getVersion());
+
+ $p = 1;
+ $dataStrLen = strlen($this->dataStr);
+
+ while($p < $dataStrLen) {
+
+ $mode = $this->identifyMode($p);
+ if($mode == QR_MODE_KANJI) {
+ break;
+ }
+ if($mode == QR_MODE_NUM) {
+ $q = $p;
+ while(self::isdigitat($this->dataStr, $q)) {
+ $q++;
+ }
+ $dif = QRinput::estimateBitsMode8($p) // + 4 + l8
+ + QRinput::estimateBitsModeNum($q - $p) + 4 + $ln
+ - QRinput::estimateBitsMode8($q); // - 4 - l8
+ if($dif < 0) {
+ break;
+ } else {
+ $p = $q;
+ }
+ } else if($mode == QR_MODE_AN) {
+ $q = $p;
+ while(self::isalnumat($this->dataStr, $q)) {
+ $q++;
+ }
+ $dif = QRinput::estimateBitsMode8($p) // + 4 + l8
+ + QRinput::estimateBitsModeAn($q - $p) + 4 + $la
+ - QRinput::estimateBitsMode8($q); // - 4 - l8
+ if($dif < 0) {
+ break;
+ } else {
+ $p = $q;
+ }
+ } else {
+ $p++;
+ }
+ }
+
+ $run = $p;
+ $ret = $this->input->append(QR_MODE_8, $run, str_split($this->dataStr));
+
+ if($ret < 0)
+ return -1;
+
+ return $run;
+ }
+
+ //----------------------------------------------------------------------
+ public function splitString()
+ {
+ while (strlen($this->dataStr) > 0)
+ {
+ if($this->dataStr == '')
+ return 0;
+
+ $mode = $this->identifyMode(0);
+
+ switch ($mode) {
+ case QR_MODE_NUM: $length = $this->eatNum(); break;
+ case QR_MODE_AN: $length = $this->eatAn(); break;
+ case QR_MODE_KANJI:
+ if ($hint == QR_MODE_KANJI)
+ $length = $this->eatKanji();
+ else $length = $this->eat8();
+ break;
+ default: $length = $this->eat8(); break;
+
+ }
+
+ if($length == 0) return 0;
+ if($length < 0) return -1;
+
+ $this->dataStr = substr($this->dataStr, $length);
+ }
+ }
+
+ //----------------------------------------------------------------------
+ public function toUpper()
+ {
+ $stringLen = strlen($this->dataStr);
+ $p = 0;
+
+ while ($p<$stringLen) {
+ $mode = self::identifyMode(substr($this->dataStr, $p), $this->modeHint);
+ if($mode == QR_MODE_KANJI) {
+ $p += 2;
+ } else {
+ if (ord($this->dataStr[$p]) >= ord('a') && ord($this->dataStr[$p]) <= ord('z')) {
+ $this->dataStr[$p] = chr(ord($this->dataStr[$p]) - 32);
+ }
+ $p++;
+ }
+ }
+
+ return $this->dataStr;
+ }
+
+ //----------------------------------------------------------------------
+ public static function splitStringToQRinput($string, QRinput $input, $modeHint, $casesensitive = true)
+ {
+ if(is_null($string) || $string == '\0' || $string == '') {
+ throw new Exception('empty string!!!');
+ }
+
+ $split = new QRsplit($string, $input, $modeHint);
+
+ if(!$casesensitive)
+ $split->toUpper();
+
+ return $split->splitString();
+ }
+ }
+
+
+
+//---- qrrscode.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Reed-Solomon error correction support
+ *
+ * Copyright (C) 2002, 2003, 2004, 2006 Phil Karn, KA9Q
+ * (libfec is released under the GNU Lesser General Public License.)
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ class QRrsItem {
+
+ public $mm; // Bits per symbol
+ public $nn; // Symbols per block (= (1<<mm)-1)
+ public $alpha_to = array(); // log lookup table
+ public $index_of = array(); // Antilog lookup table
+ public $genpoly = array(); // Generator polynomial
+ public $nroots; // Number of generator roots = number of parity symbols
+ public $fcr; // First consecutive root, index form
+ public $prim; // Primitive element, index form
+ public $iprim; // prim-th root of 1, index form
+ public $pad; // Padding bytes in shortened block
+ public $gfpoly;
+
+ //----------------------------------------------------------------------
+ public function modnn($x)
+ {
+ while ($x >= $this->nn) {
+ $x -= $this->nn;
+ $x = ($x >> $this->mm) + ($x & $this->nn);
+ }
+
+ return $x;
+ }
+
+ //----------------------------------------------------------------------
+ public static function init_rs_char($symsize, $gfpoly, $fcr, $prim, $nroots, $pad)
+ {
+ // Common code for intializing a Reed-Solomon control block (char or int symbols)
+ // Copyright 2004 Phil Karn, KA9Q
+ // May be used under the terms of the GNU Lesser General Public License (LGPL)
+
+ $rs = null;
+
+ // Check parameter ranges
+ if($symsize < 0 || $symsize > 8) return $rs;
+ if($fcr < 0 || $fcr >= (1<<$symsize)) return $rs;
+ if($prim <= 0 || $prim >= (1<<$symsize)) return $rs;
+ if($nroots < 0 || $nroots >= (1<<$symsize)) return $rs; // Can't have more roots than symbol values!
+ if($pad < 0 || $pad >= ((1<<$symsize) -1 - $nroots)) return $rs; // Too much padding
+
+ $rs = new QRrsItem();
+ $rs->mm = $symsize;
+ $rs->nn = (1<<$symsize)-1;
+ $rs->pad = $pad;
+
+ $rs->alpha_to = array_fill(0, $rs->nn+1, 0);
+ $rs->index_of = array_fill(0, $rs->nn+1, 0);
+
+ // PHP style macro replacement ;)
+ $NN =& $rs->nn;
+ $A0 =& $NN;
+
+ // Generate Galois field lookup tables
+ $rs->index_of[0] = $A0; // log(zero) = -inf
+ $rs->alpha_to[$A0] = 0; // alpha**-inf = 0
+ $sr = 1;
+
+ for($i=0; $i<$rs->nn; $i++) {
+ $rs->index_of[$sr] = $i;
+ $rs->alpha_to[$i] = $sr;
+ $sr <<= 1;
+ if($sr & (1<<$symsize)) {
+ $sr ^= $gfpoly;
+ }
+ $sr &= $rs->nn;
+ }
+
+ if($sr != 1){
+ // field generator polynomial is not primitive!
+ $rs = NULL;
+ return $rs;
+ }
+
+ /* Form RS code generator polynomial from its roots */
+ $rs->genpoly = array_fill(0, $nroots+1, 0);
+
+ $rs->fcr = $fcr;
+ $rs->prim = $prim;
+ $rs->nroots = $nroots;
+ $rs->gfpoly = $gfpoly;
+
+ /* Find prim-th root of 1, used in decoding */
+ for($iprim=1;($iprim % $prim) != 0;$iprim += $rs->nn)
+ ; // intentional empty-body loop!
+
+ $rs->iprim = (int)($iprim / $prim);
+ $rs->genpoly[0] = 1;
+
+ for ($i = 0,$root=$fcr*$prim; $i < $nroots; $i++, $root += $prim) {
+ $rs->genpoly[$i+1] = 1;
+
+ // Multiply rs->genpoly[] by @**(root + x)
+ for ($j = $i; $j > 0; $j--) {
+ if ($rs->genpoly[$j] != 0) {
+ $rs->genpoly[$j] = $rs->genpoly[$j-1] ^ $rs->alpha_to[$rs->modnn($rs->index_of[$rs->genpoly[$j]] + $root)];
+ } else {
+ $rs->genpoly[$j] = $rs->genpoly[$j-1];
+ }
+ }
+ // rs->genpoly[0] can never be zero
+ $rs->genpoly[0] = $rs->alpha_to[$rs->modnn($rs->index_of[$rs->genpoly[0]] + $root)];
+ }
+
+ // convert rs->genpoly[] to index form for quicker encoding
+ for ($i = 0; $i <= $nroots; $i++)
+ $rs->genpoly[$i] = $rs->index_of[$rs->genpoly[$i]];
+
+ return $rs;
+ }
+
+ //----------------------------------------------------------------------
+ public function encode_rs_char($data, &$parity)
+ {
+ $MM =& $this->mm;
+ $NN =& $this->nn;
+ $ALPHA_TO =& $this->alpha_to;
+ $INDEX_OF =& $this->index_of;
+ $GENPOLY =& $this->genpoly;
+ $NROOTS =& $this->nroots;
+ $FCR =& $this->fcr;
+ $PRIM =& $this->prim;
+ $IPRIM =& $this->iprim;
+ $PAD =& $this->pad;
+ $A0 =& $NN;
+
+ $parity = array_fill(0, $NROOTS, 0);
+
+ for($i=0; $i< ($NN-$NROOTS-$PAD); $i++) {
+
+ $feedback = $INDEX_OF[$data[$i] ^ $parity[0]];
+ if($feedback != $A0) {
+ // feedback term is non-zero
+
+ // This line is unnecessary when GENPOLY[NROOTS] is unity, as it must
+ // always be for the polynomials constructed by init_rs()
+ $feedback = $this->modnn($NN - $GENPOLY[$NROOTS] + $feedback);
+
+ for($j=1;$j<$NROOTS;$j++) {
+ $parity[$j] ^= $ALPHA_TO[$this->modnn($feedback + $GENPOLY[$NROOTS-$j])];
+ }
+ }
+
+ // Shift
+ array_shift($parity);
+ if($feedback != $A0) {
+ array_push($parity, $ALPHA_TO[$this->modnn($feedback + $GENPOLY[0])]);
+ } else {
+ array_push($parity, 0);
+ }
+ }
+ }
+ }
+
+ //##########################################################################
+
+ class QRrs {
+
+ public static $items = array();
+
+ //----------------------------------------------------------------------
+ public static function init_rs($symsize, $gfpoly, $fcr, $prim, $nroots, $pad)
+ {
+ foreach(self::$items as $rs) {
+ if($rs->pad != $pad) continue;
+ if($rs->nroots != $nroots) continue;
+ if($rs->mm != $symsize) continue;
+ if($rs->gfpoly != $gfpoly) continue;
+ if($rs->fcr != $fcr) continue;
+ if($rs->prim != $prim) continue;
+
+ return $rs;
+ }
+
+ $rs = QRrsItem::init_rs_char($symsize, $gfpoly, $fcr, $prim, $nroots, $pad);
+ array_unshift(self::$items, $rs);
+
+ return $rs;
+ }
+ }
+
+
+
+//---- qrmask.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Masking
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ define('N1', 3);
+ define('N2', 3);
+ define('N3', 40);
+ define('N4', 10);
+
+ class QRmask {
+
+ public $runLength = array();
+
+ //----------------------------------------------------------------------
+ public function __construct()
+ {
+ $this->runLength = array_fill(0, QRSPEC_WIDTH_MAX + 1, 0);
+ }
+
+ //----------------------------------------------------------------------
+ public function writeFormatInformation($width, &$frame, $mask, $level)
+ {
+ $blacks = 0;
+ $format = QRspec::getFormatInfo($mask, $level);
+
+ for($i=0; $i<8; $i++) {
+ if($format & 1) {
+ $blacks += 2;
+ $v = 0x85;
+ } else {
+ $v = 0x84;
+ }
+
+ $frame[8][$width - 1 - $i] = chr($v);
+ if($i < 6) {
+ $frame[$i][8] = chr($v);
+ } else {
+ $frame[$i + 1][8] = chr($v);
+ }
+ $format = $format >> 1;
+ }
+
+ for($i=0; $i<7; $i++) {
+ if($format & 1) {
+ $blacks += 2;
+ $v = 0x85;
+ } else {
+ $v = 0x84;
+ }
+
+ $frame[$width - 7 + $i][8] = chr($v);
+ if($i == 0) {
+ $frame[8][7] = chr($v);
+ } else {
+ $frame[8][6 - $i] = chr($v);
+ }
+
+ $format = $format >> 1;
+ }
+
+ return $blacks;
+ }
+
+ //----------------------------------------------------------------------
+ public function mask0($x, $y) { return ($x+$y)&1; }
+ public function mask1($x, $y) { return ($y&1); }
+ public function mask2($x, $y) { return ($x%3); }
+ public function mask3($x, $y) { return ($x+$y)%3; }
+ public function mask4($x, $y) { return (((int)($y/2))+((int)($x/3)))&1; }
+ public function mask5($x, $y) { return (($x*$y)&1)+($x*$y)%3; }
+ public function mask6($x, $y) { return ((($x*$y)&1)+($x*$y)%3)&1; }
+ public function mask7($x, $y) { return ((($x*$y)%3)+(($x+$y)&1))&1; }
+
+ //----------------------------------------------------------------------
+ private function generateMaskNo($maskNo, $width, $frame)
+ {
+ $bitMask = array_fill(0, $width, array_fill(0, $width, 0));
+
+ for($y=0; $y<$width; $y++) {
+ for($x=0; $x<$width; $x++) {
+ if(ord($frame[$y][$x]) & 0x80) {
+ $bitMask[$y][$x] = 0;
+ } else {
+ $maskFunc = call_user_func(array($this, 'mask'.$maskNo), $x, $y);
+ $bitMask[$y][$x] = ($maskFunc == 0)?1:0;
+ }
+
+ }
+ }
+
+ return $bitMask;
+ }
+
+ //----------------------------------------------------------------------
+ public static function serial($bitFrame)
+ {
+ $codeArr = array();
+
+ foreach ($bitFrame as $line)
+ $codeArr[] = join('', $line);
+
+ return gzcompress(join("\n", $codeArr), 9);
+ }
+
+ //----------------------------------------------------------------------
+ public static function unserial($code)
+ {
+ $codeArr = array();
+
+ $codeLines = explode("\n", gzuncompress($code));
+ foreach ($codeLines as $line)
+ $codeArr[] = str_split($line);
+
+ return $codeArr;
+ }
+
+ //----------------------------------------------------------------------
+ public function makeMaskNo($maskNo, $width, $s, &$d, $maskGenOnly = false)
+ {
+ $b = 0;
+ $bitMask = array();
+
+ $fileName = QR_CACHE_DIR.'mask_'.$maskNo.DIRECTORY_SEPARATOR.'mask_'.$width.'_'.$maskNo.'.dat';
+
+ if (QR_CACHEABLE) {
+ if (file_exists($fileName)) {
+ $bitMask = self::unserial(file_get_contents($fileName));
+ } else {
+ $bitMask = $this->generateMaskNo($maskNo, $width, $s, $d);
+ if (!file_exists(QR_CACHE_DIR.'mask_'.$maskNo))
+ mkdir(QR_CACHE_DIR.'mask_'.$maskNo);
+ file_put_contents($fileName, self::serial($bitMask));
+ }
+ } else {
+ $bitMask = $this->generateMaskNo($maskNo, $width, $s, $d);
+ }
+
+ if ($maskGenOnly)
+ return;
+
+ $d = $s;
+
+ for($y=0; $y<$width; $y++) {
+ for($x=0; $x<$width; $x++) {
+ if($bitMask[$y][$x] == 1) {
+ $d[$y][$x] = chr(ord($s[$y][$x]) ^ (int)$bitMask[$y][$x]);
+ }
+ $b += (int)(ord($d[$y][$x]) & 1);
+ }
+ }
+
+ return $b;
+ }
+
+ //----------------------------------------------------------------------
+ public function makeMask($width, $frame, $maskNo, $level)
+ {
+ $masked = array_fill(0, $width, str_repeat("\0", $width));
+ $this->makeMaskNo($maskNo, $width, $frame, $masked);
+ $this->writeFormatInformation($width, $masked, $maskNo, $level);
+
+ return $masked;
+ }
+
+ //----------------------------------------------------------------------
+ public function calcN1N3($length)
+ {
+ $demerit = 0;
+
+ for($i=0; $i<$length; $i++) {
+
+ if($this->runLength[$i] >= 5) {
+ $demerit += (N1 + ($this->runLength[$i] - 5));
+ }
+ if($i & 1) {
+ if(($i >= 3) && ($i < ($length-2)) && ($this->runLength[$i] % 3 == 0)) {
+ $fact = (int)($this->runLength[$i] / 3);
+ if(($this->runLength[$i-2] == $fact) &&
+ ($this->runLength[$i-1] == $fact) &&
+ ($this->runLength[$i+1] == $fact) &&
+ ($this->runLength[$i+2] == $fact)) {
+ if(($this->runLength[$i-3] < 0) || ($this->runLength[$i-3] >= (4 * $fact))) {
+ $demerit += N3;
+ } else if((($i+3) >= $length) || ($this->runLength[$i+3] >= (4 * $fact))) {
+ $demerit += N3;
+ }
+ }
+ }
+ }
+ }
+ return $demerit;
+ }
+
+ //----------------------------------------------------------------------
+ public function evaluateSymbol($width, $frame)
+ {
+ $head = 0;
+ $demerit = 0;
+
+ for($y=0; $y<$width; $y++) {
+ $head = 0;
+ $this->runLength[0] = 1;
+
+ $frameY = $frame[$y];
+
+ if ($y>0)
+ $frameYM = $frame[$y-1];
+
+ for($x=0; $x<$width; $x++) {
+ if(($x > 0) && ($y > 0)) {
+ $b22 = ord($frameY[$x]) & ord($frameY[$x-1]) & ord($frameYM[$x]) & ord($frameYM[$x-1]);
+ $w22 = ord($frameY[$x]) | ord($frameY[$x-1]) | ord($frameYM[$x]) | ord($frameYM[$x-1]);
+
+ if(($b22 | ($w22 ^ 1))&1) {
+ $demerit += N2;
+ }
+ }
+ if(($x == 0) && (ord($frameY[$x]) & 1)) {
+ $this->runLength[0] = -1;
+ $head = 1;
+ $this->runLength[$head] = 1;
+ } else if($x > 0) {
+ if((ord($frameY[$x]) ^ ord($frameY[$x-1])) & 1) {
+ $head++;
+ $this->runLength[$head] = 1;
+ } else {
+ $this->runLength[$head]++;
+ }
+ }
+ }
+
+ $demerit += $this->calcN1N3($head+1);
+ }
+
+ for($x=0; $x<$width; $x++) {
+ $head = 0;
+ $this->runLength[0] = 1;
+
+ for($y=0; $y<$width; $y++) {
+ if($y == 0 && (ord($frame[$y][$x]) & 1)) {
+ $this->runLength[0] = -1;
+ $head = 1;
+ $this->runLength[$head] = 1;
+ } else if($y > 0) {
+ if((ord($frame[$y][$x]) ^ ord($frame[$y-1][$x])) & 1) {
+ $head++;
+ $this->runLength[$head] = 1;
+ } else {
+ $this->runLength[$head]++;
+ }
+ }
+ }
+
+ $demerit += $this->calcN1N3($head+1);
+ }
+
+ return $demerit;
+ }
+
+
+ //----------------------------------------------------------------------
+ public function mask($width, $frame, $level)
+ {
+ $minDemerit = PHP_INT_MAX;
+ $bestMaskNum = 0;
+ $bestMask = array();
+
+ $checked_masks = array(0,1,2,3,4,5,6,7);
+
+ if (QR_FIND_FROM_RANDOM !== false) {
+
+ $howManuOut = 8-(QR_FIND_FROM_RANDOM % 9);
+ for ($i = 0; $i < $howManuOut; $i++) {
+ $remPos = rand (0, count($checked_masks)-1);
+ unset($checked_masks[$remPos]);
+ $checked_masks = array_values($checked_masks);
+ }
+
+ }
+
+ $bestMask = $frame;
+
+ foreach($checked_masks as $i) {
+ $mask = array_fill(0, $width, str_repeat("\0", $width));
+
+ $demerit = 0;
+ $blacks = 0;
+ $blacks = $this->makeMaskNo($i, $width, $frame, $mask);
+ $blacks += $this->writeFormatInformation($width, $mask, $i, $level);
+ $blacks = (int)(100 * $blacks / ($width * $width));
+ $demerit = (int)((int)(abs($blacks - 50) / 5) * N4);
+ $demerit += $this->evaluateSymbol($width, $mask);
+
+ if($demerit < $minDemerit) {
+ $minDemerit = $demerit;
+ $bestMask = $mask;
+ $bestMaskNum = $i;
+ }
+ }
+
+ return $bestMask;
+ }
+
+ //----------------------------------------------------------------------
+ }
+
+
+
+
+//---- qrencode.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Main encoder classes.
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ class QRrsblock {
+ public $dataLength;
+ public $data = array();
+ public $eccLength;
+ public $ecc = array();
+
+ public function __construct($dl, $data, $el, &$ecc, QRrsItem $rs)
+ {
+ $rs->encode_rs_char($data, $ecc);
+
+ $this->dataLength = $dl;
+ $this->data = $data;
+ $this->eccLength = $el;
+ $this->ecc = $ecc;
+ }
+ };
+
+ //##########################################################################
+
+ class QRrawcode {
+ public $version;
+ public $datacode = array();
+ public $ecccode = array();
+ public $blocks;
+ public $rsblocks = array(); //of RSblock
+ public $count;
+ public $dataLength;
+ public $eccLength;
+ public $b1;
+
+ //----------------------------------------------------------------------
+ public function __construct(QRinput $input)
+ {
+ $spec = array(0,0,0,0,0);
+
+ $this->datacode = $input->getByteStream();
+ if(is_null($this->datacode)) {
+ throw new Exception('null imput string');
+ }
+
+ QRspec::getEccSpec($input->getVersion(), $input->getErrorCorrectionLevel(), $spec);
+
+ $this->version = $input->getVersion();
+ $this->b1 = QRspec::rsBlockNum1($spec);
+ $this->dataLength = QRspec::rsDataLength($spec);
+ $this->eccLength = QRspec::rsEccLength($spec);
+ $this->ecccode = array_fill(0, $this->eccLength, 0);
+ $this->blocks = QRspec::rsBlockNum($spec);
+
+ $ret = $this->init($spec);
+ if($ret < 0) {
+ throw new Exception('block alloc error');
+ return null;
+ }
+
+ $this->count = 0;
+ }
+
+ //----------------------------------------------------------------------
+ public function init(array $spec)
+ {
+ $dl = QRspec::rsDataCodes1($spec);
+ $el = QRspec::rsEccCodes1($spec);
+ $rs = QRrs::init_rs(8, 0x11d, 0, 1, $el, 255 - $dl - $el);
+
+
+ $blockNo = 0;
+ $dataPos = 0;
+ $eccPos = 0;
+ for($i=0; $i<QRspec::rsBlockNum1($spec); $i++) {
+ $ecc = array_slice($this->ecccode,$eccPos);
+ $this->rsblocks[$blockNo] = new QRrsblock($dl, array_slice($this->datacode, $dataPos), $el, $ecc, $rs);
+ $this->ecccode = array_merge(array_slice($this->ecccode,0, $eccPos), $ecc);
+
+ $dataPos += $dl;
+ $eccPos += $el;
+ $blockNo++;
+ }
+
+ if(QRspec::rsBlockNum2($spec) == 0)
+ return 0;
+
+ $dl = QRspec::rsDataCodes2($spec);
+ $el = QRspec::rsEccCodes2($spec);
+ $rs = QRrs::init_rs(8, 0x11d, 0, 1, $el, 255 - $dl - $el);
+
+ if($rs == NULL) return -1;
+
+ for($i=0; $i<QRspec::rsBlockNum2($spec); $i++) {
+ $ecc = array_slice($this->ecccode,$eccPos);
+ $this->rsblocks[$blockNo] = new QRrsblock($dl, array_slice($this->datacode, $dataPos), $el, $ecc, $rs);
+ $this->ecccode = array_merge(array_slice($this->ecccode,0, $eccPos), $ecc);
+
+ $dataPos += $dl;
+ $eccPos += $el;
+ $blockNo++;
+ }
+
+ return 0;
+ }
+
+ //----------------------------------------------------------------------
+ public function getCode()
+ {
+ $ret;
+
+ if($this->count < $this->dataLength) {
+ $row = $this->count % $this->blocks;
+ $col = $this->count / $this->blocks;
+ if($col >= $this->rsblocks[0]->dataLength) {
+ $row += $this->b1;
+ }
+ $ret = $this->rsblocks[$row]->data[$col];
+ } else if($this->count < $this->dataLength + $this->eccLength) {
+ $row = ($this->count - $this->dataLength) % $this->blocks;
+ $col = ($this->count - $this->dataLength) / $this->blocks;
+ $ret = $this->rsblocks[$row]->ecc[$col];
+ } else {
+ return 0;
+ }
+ $this->count++;
+
+ return $ret;
+ }
+ }
+
+ //##########################################################################
+
+ class QRcode {
+
+ public $version;
+ public $width;
+ public $data;
+
+ //----------------------------------------------------------------------
+ public function encodeMask(QRinput $input, $mask)
+ {
+ if($input->getVersion() < 0 || $input->getVersion() > QRSPEC_VERSION_MAX) {
+ throw new Exception('wrong version');
+ }
+ if($input->getErrorCorrectionLevel() > QR_ECLEVEL_H) {
+ throw new Exception('wrong level');
+ }
+
+ $raw = new QRrawcode($input);
+
+ QRtools::markTime('after_raw');
+
+ $version = $raw->version;
+ $width = QRspec::getWidth($version);
+ $frame = QRspec::newFrame($version);
+
+ $filler = new FrameFiller($width, $frame);
+ if(is_null($filler)) {
+ return NULL;
+ }
+
+ // inteleaved data and ecc codes
+ for($i=0; $i<$raw->dataLength + $raw->eccLength; $i++) {
+ $code = $raw->getCode();
+ $bit = 0x80;
+ for($j=0; $j<8; $j++) {
+ $addr = $filler->next();
+ $filler->setFrameAt($addr, 0x02 | (($bit & $code) != 0));
+ $bit = $bit >> 1;
+ }
+ }
+
+ QRtools::markTime('after_filler');
+
+ unset($raw);
+
+ // remainder bits
+ $j = QRspec::getRemainder($version);
+ for($i=0; $i<$j; $i++) {
+ $addr = $filler->next();
+ $filler->setFrameAt($addr, 0x02);
+ }
+
+ $frame = $filler->frame;
+ unset($filler);
+
+
+ // masking
+ $maskObj = new QRmask();
+ if($mask < 0) {
+
+ if (QR_FIND_BEST_MASK) {
+ $masked = $maskObj->mask($width, $frame, $input->getErrorCorrectionLevel());
+ } else {
+ $masked = $maskObj->makeMask($width, $frame, (intval(QR_DEFAULT_MASK) % 8), $input->getErrorCorrectionLevel());
+ }
+ } else {
+ $masked = $maskObj->makeMask($width, $frame, $mask, $input->getErrorCorrectionLevel());
+ }
+
+ if($masked == NULL) {
+ return NULL;
+ }
+
+ QRtools::markTime('after_mask');
+
+ $this->version = $version;
+ $this->width = $width;
+ $this->data = $masked;
+
+ return $this;
+ }
+
+ //----------------------------------------------------------------------
+ public function encodeInput(QRinput $input)
+ {
+ return $this->encodeMask($input, -1);
+ }
+
+ //----------------------------------------------------------------------
+ public function encodeString8bit($string, $version, $level)
+ {
+ if(string == NULL) {
+ throw new Exception('empty string!');
+ return NULL;
+ }
+
+ $input = new QRinput($version, $level);
+ if($input == NULL) return NULL;
+
+ $ret = $input->append($input, QR_MODE_8, strlen($string), str_split($string));
+ if($ret < 0) {
+ unset($input);
+ return NULL;
+ }
+ return $this->encodeInput($input);
+ }
+
+ //----------------------------------------------------------------------
+ public function encodeString($string, $version, $level, $hint, $casesensitive)
+ {
+
+ if($hint != QR_MODE_8 && $hint != QR_MODE_KANJI) {
+ throw new Exception('bad hint');
+ return NULL;
+ }
+
+ $input = new QRinput($version, $level);
+ if($input == NULL) return NULL;
+
+ $ret = QRsplit::splitStringToQRinput($string, $input, $hint, $casesensitive);
+ if($ret < 0) {
+ return NULL;
+ }
+
+ return $this->encodeInput($input);
+ }
+
+ //----------------------------------------------------------------------
+ public static function png($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4, $saveandprint=false)
+ {
+ $enc = QRencode::factory($level, $size, $margin);
+ return $enc->encodePNG($text, $outfile, $saveandprint=false);
+ }
+
+ //----------------------------------------------------------------------
+ public static function text($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4)
+ {
+ $enc = QRencode::factory($level, $size, $margin);
+ return $enc->encode($text, $outfile);
+ }
+
+ //----------------------------------------------------------------------
+ public static function raw($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4)
+ {
+ $enc = QRencode::factory($level, $size, $margin);
+ return $enc->encodeRAW($text, $outfile);
+ }
+ }
+
+ //##########################################################################
+
+ class FrameFiller {
+
+ public $width;
+ public $frame;
+ public $x;
+ public $y;
+ public $dir;
+ public $bit;
+
+ //----------------------------------------------------------------------
+ public function __construct($width, &$frame)
+ {
+ $this->width = $width;
+ $this->frame = $frame;
+ $this->x = $width - 1;
+ $this->y = $width - 1;
+ $this->dir = -1;
+ $this->bit = -1;
+ }
+
+ //----------------------------------------------------------------------
+ public function setFrameAt($at, $val)
+ {
+ $this->frame[$at['y']][$at['x']] = chr($val);
+ }
+
+ //----------------------------------------------------------------------
+ public function getFrameAt($at)
+ {
+ return ord($this->frame[$at['y']][$at['x']]);
+ }
+
+ //----------------------------------------------------------------------
+ public function next()
+ {
+ do {
+
+ if($this->bit == -1) {
+ $this->bit = 0;
+ return array('x'=>$this->x, 'y'=>$this->y);
+ }
+
+ $x = $this->x;
+ $y = $this->y;
+ $w = $this->width;
+
+ if($this->bit == 0) {
+ $x--;
+ $this->bit++;
+ } else {
+ $x++;
+ $y += $this->dir;
+ $this->bit--;
+ }
+
+ if($this->dir < 0) {
+ if($y < 0) {
+ $y = 0;
+ $x -= 2;
+ $this->dir = 1;
+ if($x == 6) {
+ $x--;
+ $y = 9;
+ }
+ }
+ } else {
+ if($y == $w) {
+ $y = $w - 1;
+ $x -= 2;
+ $this->dir = -1;
+ if($x == 6) {
+ $x--;
+ $y -= 8;
+ }
+ }
+ }
+ if($x < 0 || $y < 0) return null;
+
+ $this->x = $x;
+ $this->y = $y;
+
+ } while(ord($this->frame[$y][$x]) & 0x80);
+
+ return array('x'=>$x, 'y'=>$y);
+ }
+
+ } ;
+
+ //##########################################################################
+
+ class QRencode {
+
+ public $casesensitive = true;
+ public $eightbit = false;
+
+ public $version = 0;
+ public $size = 3;
+ public $margin = 4;
+
+ public $structured = 0; // not supported yet
+
+ public $level = QR_ECLEVEL_L;
+ public $hint = QR_MODE_8;
+
+ //----------------------------------------------------------------------
+ public static function factory($level = QR_ECLEVEL_L, $size = 3, $margin = 4)
+ {
+ $enc = new QRencode();
+ $enc->size = $size;
+ $enc->margin = $margin;
+
+ switch ($level.'') {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ $enc->level = $level;
+ break;
+ case 'l':
+ case 'L':
+ $enc->level = QR_ECLEVEL_L;
+ break;
+ case 'm':
+ case 'M':
+ $enc->level = QR_ECLEVEL_M;
+ break;
+ case 'q':
+ case 'Q':
+ $enc->level = QR_ECLEVEL_Q;
+ break;
+ case 'h':
+ case 'H':
+ $enc->level = QR_ECLEVEL_H;
+ break;
+ }
+
+ return $enc;
+ }
+
+ //----------------------------------------------------------------------
+ public function encodeRAW($intext, $outfile = false)
+ {
+ $code = new QRcode();
+
+ if($this->eightbit) {
+ $code->encodeString8bit($intext, $this->version, $this->level);
+ } else {
+ $code->encodeString($intext, $this->version, $this->level, $this->hint, $this->casesensitive);
+ }
+
+ return $code->data;
+ }
+
+ //----------------------------------------------------------------------
+ public function encode($intext, $outfile = false)
+ {
+ $code = new QRcode();
+
+ if($this->eightbit) {
+ $code->encodeString8bit($intext, $this->version, $this->level);
+ } else {
+ $code->encodeString($intext, $this->version, $this->level, $this->hint, $this->casesensitive);
+ }
+
+ QRtools::markTime('after_encode');
+
+ if ($outfile!== false) {
+ file_put_contents($outfile, join("\n", QRtools::binarize($code->data)));
+ } else {
+ return QRtools::binarize($code->data);
+ }
+ }
+
+ //----------------------------------------------------------------------
+ public function encodePNG($intext, $outfile = false,$saveandprint=false)
+ {
+ try {
+
+ ob_start();
+ $tab = $this->encode($intext);
+ $err = ob_get_contents();
+ ob_end_clean();
+
+ if ($err != '')
+ QRtools::log($outfile, $err);
+
+ $maxSize = (int)(QR_PNG_MAXIMUM_SIZE / (count($tab)+2*$this->margin));
+
+ QRimage::png($tab, $outfile, min(max(1, $this->size), $maxSize), $this->margin,$saveandprint);
+
+ } catch (Exception $e) {
+
+ QRtools::log($outfile, $e->getMessage());
+
+ }
+ }
+ }
+
+
diff --git a/apps/shorty/ajax/add.php b/apps/shorty/ajax/add.php
new file mode 100644
index 00000000000..b1130e47db6
--- /dev/null
+++ b/apps/shorty/ajax/add.php
@@ -0,0 +1,101 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU AFFERO GENERAL PUBLIC LICENSE (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file ajax/add.php
+ * @brief Ajax method to add a new shorty defined by request arguments
+ * @param title (string) Human readable title of the shorty
+ * @param target (url) Remote target url meant to be shortened
+ * @param until (date) Date until when the created shorty is valid and usable
+ * @param notes (string) Any additional information in text form
+ * @param favicon (url) Reference to the shortcut icon used in target url
+ * @returns (json) success/error state indicator
+ * @returns (json) Associative array of attributes of the generated shorty
+ * @returns (json) Human readable message
+ * @author Christian Reiner
+ */
+
+// swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+OC_Shorty_Tools::ob_control ( TRUE );
+
+ //no apps or filesystem
+$RUNTIME_NOSETUPFS = true;
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn ( );
+OCP\JSON::checkAppEnabled ( 'shorty' );
+
+try
+{
+ $p_id = OC_Shorty_Tools::shorty_id ( );
+ $p_status = OC_Shorty_Type::req_argument ( 'status', OC_Shorty_Type::STATUS, FALSE );
+ $p_title = OC_Shorty_Type::req_argument ( 'title', OC_Shorty_Type::STRING, FALSE );
+ $p_target = OC_Shorty_Type::req_argument ( 'target', OC_Shorty_Type::URL, TRUE );
+ $p_until = OC_Shorty_Type::req_argument ( 'until', OC_Shorty_Type::DATE, FALSE );
+ $p_notes = OC_Shorty_Type::req_argument ( 'notes', OC_Shorty_Type::STRING, FALSE );
+ $p_favicon = OC_Shorty_Type::req_argument ( 'favicon', OC_Shorty_Type::URL, FALSE );
+ // register shorty at backend
+ $p_source = OC_Shorty_Backend::registerUrl ( $p_id );
+ // fallback title: choose hostname if no title is specified
+ $p_title = $p_title ? trim($p_title) : parse_url($p_target,PHP_URL_HOST);
+ // insert new shorty into our database
+ $param = array
+ (
+ ':user' => OCP\User::getUser(),
+ ':id' => $p_id,
+ ':status' => $p_status ? $p_status : '',
+ ':title' => $p_title ? $p_title : '',
+ ':favicon' => $p_favicon ? $p_favicon : '',
+ ':source' => $p_source ? $p_source : '',
+ ':target' => $p_target ? $p_target : '',
+ ':notes' => $p_notes ? $p_notes : '',
+ ':until' => $p_until,
+ );
+ $query = OCP\DB::prepare ( OC_Shorty_Query::URL_INSERT );
+ $query->execute ( $param );
+
+ // read new entry for feedback
+ $param = array
+ (
+ ':user' => OCP\User::getUser(),
+ ':id' => $p_id,
+ );
+ $query = OCP\DB::prepare ( OC_Shorty_Query::URL_VERIFY );
+ $entries = $query->execute($param)->FetchAll();
+ if ( (1==count($entries))
+ &&(isset($entries[0]['id']))
+ &&($p_id==$entries[0]['id']) )
+ $entries[0]['relay']=OC_Shorty_Tools::relayUrl ( $entries[0]['id'] );
+ else
+ throw new OC_Shorty_Exception ( "failed to verify stored shorty with id '%1s'", array($p_id) );
+
+ // swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+ OC_Shorty_Tools::ob_control ( FALSE );
+ OCP\Util::writeLog( 'shorty', sprintf("Url shortened to: %s",$p_source), OC_Log::INFO );
+ OCP\JSON::success ( array ( 'data' => $entries[0],
+ 'message' => OC_Shorty_L10n::t("Url shortened to: %s",$p_source) ) );
+} catch ( Exception $e ) { OC_Shorty_Exception::JSONerror($e); }
+?>
diff --git a/apps/shorty/ajax/click.php b/apps/shorty/ajax/click.php
new file mode 100644
index 00000000000..c88f6eec4f5
--- /dev/null
+++ b/apps/shorty/ajax/click.php
@@ -0,0 +1,67 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file ajax/click.php
+ * @brief Ajax method to register a 'click', a single hit on an existing and valid shorty
+ * @param id (string) Internal id of a referenced shorty
+ * @returns (json) success/error state indicator
+ * @returns (json) Associative array holding the id of the shorty whose click was registered
+ * @author Christian Reiner
+ */
+
+// swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+OC_Shorty_Tools::ob_control ( TRUE );
+
+//no apps or filesystem
+$RUNTIME_NOSETUPFS = true;
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn ( );
+OCP\JSON::checkAppEnabled ( 'shorty' );
+
+try
+{
+ if (isset($_GET['id']))
+ {
+ $p_id = OC_Shorty_Type::req_argument ( $_GET['id'], OC_Shorty_Type::ID, TRUE );
+ $param = array
+ (
+ 'user' => OCP\User::getUser(),
+ 'id' => $p_id,
+ );
+ $query = OCP\DB::prepare ( OC_Shorty_Query::URL_CLICK );
+ $query->execute ( $param );
+ OCP\JSON::success ( array ( 'data' => array('id'=>$p_id),
+ 'message' => OC_Shorty_L10n::t('Click registered') ) );
+ }
+ else
+ throw new OC_Shorty_Exception ( "request failed: missing mandatory argument 'id'" );
+
+ // swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+ OC_Shorty_Tools::ob_control ( FALSE );
+} catch ( Exception $e ) { OC_Shorty_Exception::JSONerror($e); }
+?>
diff --git a/apps/shorty/ajax/count.php b/apps/shorty/ajax/count.php
new file mode 100644
index 00000000000..9a00083bb9f
--- /dev/null
+++ b/apps/shorty/ajax/count.php
@@ -0,0 +1,54 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file ajax/count.php
+ * @brief Ajax method to retrieve a list of important sums, counts of the existing set of shortys
+ * @returns (json) success/error state indicator
+ * @returns (json) Associative array of counts
+ * @author Christian Reiner
+ */
+
+// swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+OC_Shorty_Tools::ob_control ( TRUE );
+
+//no apps or filesystem
+$RUNTIME_NOSETUPFS = TRUE;
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn ( );
+OCP\JSON::checkAppEnabled ( 'shorty' );
+
+try
+{
+ $countResult = OC_Shorty_Tools::countShorties ( );
+
+ // swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+ OC_Shorty_Tools::ob_control ( FALSE );
+ OCP\JSON::success ( array ( 'data' => $countResult,
+ 'message' => OC_Shorty_L10n::t('Counted entries and clicks') ) );
+} catch ( Exception $e ) { OC_Shorty_Exception::JSONerror($e); }
+?>
diff --git a/apps/shorty/ajax/del.php b/apps/shorty/ajax/del.php
new file mode 100644
index 00000000000..b24f83915ed
--- /dev/null
+++ b/apps/shorty/ajax/del.php
@@ -0,0 +1,63 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file ajax/del.php
+ * @brief Ajax method to delete an existing shorty
+ * @param id (string) Internal id of a referenced shorty
+ * @returns (json) success/error state indicator
+ * @returns (json) Key of shorty that was deleted
+ * @author Christian Reiner
+ */
+
+// swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+OC_Shorty_Tools::ob_control ( TRUE );
+
+//no apps or filesystem
+$RUNTIME_NOSETUPFS = true;
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn ( );
+OCP\JSON::checkAppEnabled ( 'shorty' );
+
+try
+{
+ $p_id = OC_Shorty_Type::req_argument ( 'id', OC_Shorty_Type::ID, TRUE );
+ $param = array
+ (
+ 'user' => OCP\User::getUser(),
+ 'id' => $p_id,
+ );
+ $query = OCP\DB::prepare ( OC_Shorty_Query::URL_DELETE );
+ $query->execute($param);
+
+ // swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+ OC_Shorty_Tools::ob_control ( FALSE );
+ OCP\Util::writeLog( 'shorty', sprintf("Shorty with id '%s' deleted",$p_id), OC_Log::INFO );
+ OCP\JSON::success ( array ( 'data' => array('id'=>$p_id),
+ 'message' => OC_Shorty_L10n::t("Shorty with id '%s' deleted",$p_id) ) );
+} catch ( Exception $e ) { OC_Shorty_Exception::JSONerror($e); }
+?>
diff --git a/apps/shorty/ajax/edit.php b/apps/shorty/ajax/edit.php
new file mode 100644
index 00000000000..252e4fe5c29
--- /dev/null
+++ b/apps/shorty/ajax/edit.php
@@ -0,0 +1,88 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file ajax/edit.php
+ * @brief Ajax method to modify aspects of an existing shorty
+ * @param id (string) Internal id of the referenced shorty
+ * @param title (string) Human readable title
+ * @param notes (string) Any additional information in free text form
+ * @returns (json) success/error state indicator
+ * @returns (json) Associative array holding the id of the shorty whose click was registered
+ * @author Christian Reiner
+ */
+
+// swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+OC_Shorty_Tools::ob_control ( TRUE );
+
+//no apps or filesystem
+$RUNTIME_NOSETUPFS = true;
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn ( );
+OCP\JSON::checkAppEnabled ( 'shorty' );
+
+try
+{
+ $p_id = OC_Shorty_Type::req_argument ( 'id', OC_Shorty_Type::ID, TRUE );
+ $p_status = OC_Shorty_Type::req_argument ( 'status', OC_Shorty_Type::STATUS, FALSE );
+ $p_title = OC_Shorty_Type::req_argument ( 'title', OC_Shorty_Type::STRING, FALSE );
+ $p_until = OC_Shorty_Type::req_argument ( 'until', OC_Shorty_Type::DATE, FALSE );
+ $p_notes = OC_Shorty_Type::req_argument ( 'notes', OC_Shorty_Type::STRING, FALSE );
+ $param = array
+ (
+ ':user' => OCP\User::getUser ( ),
+ ':id' => $p_id,
+ ':status'=> $p_status ? $p_status : '',
+ ':title' => $p_title ? $p_title : '',
+ ':notes' => $p_notes ? $p_notes : '',
+ ':until' => $p_until,
+ );
+ $query = OCP\DB::prepare ( OC_Shorty_Query::URL_UPDATE );
+ $query->execute ( $param );
+
+ // read new entry for feedback
+ $param = array
+ (
+ 'user' => OCP\User::getUser(),
+ 'id' => $p_id,
+ );
+ $query = OCP\DB::prepare ( OC_Shorty_Query::URL_VERIFY );
+ $entries = $query->execute($param)->FetchAll();
+ if ( (1==count($entries))
+ &&(isset($entries[0]['id']))
+ &&($p_id==$entries[0]['id']) )
+ $entries[0]['relay']=OC_Shorty_Tools::relayUrl ( $entries[0]['id'] );
+ else
+ throw new OC_Shorty_Exception ( "failed to verify stored shorty with id '%1s'", array($p_id) );
+
+ // swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+ OC_Shorty_Tools::ob_control ( FALSE );
+ OCP\Util::writeLog( 'shorty', sprintf("Modifications for shorty with id '%s' saved",$p_id), OC_Log::INFO );
+ OCP\JSON::success ( array ( 'data' => $entries[0],
+ 'message' => OC_Shorty_L10n::t("Modifications for shorty with id '%s' saved",$p_id) ) );
+} catch ( Exception $e ) { OC_Shorty_Exception::JSONerror($e); }
+?>
diff --git a/apps/shorty/ajax/list.php b/apps/shorty/ajax/list.php
new file mode 100644
index 00000000000..8b4e9b3e81e
--- /dev/null
+++ b/apps/shorty/ajax/list.php
@@ -0,0 +1,77 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file ajax/list.php
+ * @brief Ajax method to retrieve a list of existing shortys
+ * @returns (json) success/error state indicator
+ * @returns (number) Total number of shortys in the list
+ * @returns (json) Numeric array of all shortys, associative array of attributes as values for every single shorty contained
+ * @author Christian Reiner
+ */
+
+// swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+OC_Shorty_Tools::ob_control ( TRUE );
+
+//no apps or filesystem
+$RUNTIME_NOSETUPFS = TRUE;
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn ( );
+OCP\JSON::checkAppEnabled ( 'shorty' );
+
+try
+{
+ // first remove any entries already marked as 'deleted'
+ $query = OCP\DB::prepare ( OC_Shorty_Query::URL_REMOVE );
+ $result = $query->execute(array(':user'=>OCP\User::getUser()));
+ // now comes the real list selection
+// define ('PAGE_SIZE', 100);
+// $p_offset = OC_Shorty_Type::req_argument ( 'page', OC_Shorty_Type::INTEGER, FALSE) * PAGE_SIZE;
+ // pre-sort list according to user preferences
+ $p_sort = OC_Shorty_Type::$SORTING[OCP\Config::getUserValue(OCP\User::getUser(),'shorty','list-sort-code','cd')];
+ $param = array
+ (
+ ':user' => OCP\User::getUser ( ),
+ ':sort' => $p_sort,
+// ':offset' => $p_offset,
+// ':limit' => PAGE_SIZE,
+ );
+ $query = OCP\DB::prepare ( OC_Shorty_Query::URL_LIST );
+ $result = $query->execute($param);
+ $reply = $result->fetchAll();
+ // enhance all entries with the relay url
+ foreach (array_keys($reply) as $key)
+ if (isset($reply[$key]['id']))
+ $reply[$key]['relay']=OC_Shorty_Tools::relayUrl ( $reply[$key]['id'] );
+
+ // swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+ OC_Shorty_Tools::ob_control ( FALSE );
+ OCP\JSON::success ( array ( 'data' => $reply,
+ 'count' => sizeof($reply),
+ 'message' => OC_Shorty_L10n::t('Number of entries: %s', count($reply)) ) );
+} catch ( Exception $e ) { OC_Shorty_Exception::JSONerror($e); }
+?>
diff --git a/apps/shorty/ajax/meta.php b/apps/shorty/ajax/meta.php
new file mode 100644
index 00000000000..5f9d5c3c315
--- /dev/null
+++ b/apps/shorty/ajax/meta.php
@@ -0,0 +1,56 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file ajax/meta.php
+ * @brief Ajax method to query meta information about a given remote url
+ * @param target (string) Url of a remote web resource
+ * @returns (json) success/error state indicator
+ * @returns (array) Associative array of meta data aspects
+ * @author Christian Reiner
+ */
+
+// swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+OC_Shorty_Tools::ob_control ( TRUE );
+
+//no apps or filesystem
+$RUNTIME_NOSETUPFS = true;
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn ( );
+OCP\JSON::checkAppEnabled ( 'shorty' );
+
+try
+{
+ $target = OC_Shorty_Type::req_argument ( 'target', OC_Shorty_Type::URL, TRUE );
+ $meta = OC_Shorty_Meta::fetchMetaData(htmlspecialchars_decode($target));
+
+ // swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+ OC_Shorty_Tools::ob_control ( FALSE );
+ OCP\JSON::success ( array ( 'data' => $meta,
+ 'message' => OC_Shorty_L10n::t("Target url '%s' is valid", $meta['target']) ) );
+} catch ( Exception $e ) { OC_Shorty_Exception::JSONerror($e); }
+?>
diff --git a/apps/shorty/ajax/preferences.php b/apps/shorty/ajax/preferences.php
new file mode 100644
index 00000000000..e35a2f7aff3
--- /dev/null
+++ b/apps/shorty/ajax/preferences.php
@@ -0,0 +1,119 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file ajax/preferences.php
+ * @brief Ajax method to store one or more personal preferences
+ * @param backend-type (string) Identifier of chosen backend type
+ * @param backend-static-base (string) Url to use as a base when the static backend is active
+ * @param backend-google-key (string) Personal authentication key to use when the google backend is active
+ * @param backend-bitly-key (string) Personal authentication key to use when the bit.li backend is active
+ * @param backend-bitly-user (string) Personal authentication user to use when the bit.li backend is active
+ * @param sms-control (string) Controls wether a 'send as sms' action should be offered is the sharing dialog
+ * @param list-sort-code (string) Two character sorting key controlling the active sorting of shorty lists
+ * @returns (json) success/error state indicator
+ * @returns (json) Associative array holding the stored values by their key
+ * @returns (json) Human readable message describing the result
+ * @author Christian Reiner
+ */
+
+// swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+OC_Shorty_Tools::ob_control ( TRUE );
+
+//no apps or filesystem
+$RUNTIME_NOSETUPFS = true;
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn ( );
+OCP\JSON::checkAppEnabled ( 'shorty' );
+
+try
+{
+ $data = array();
+ switch ( $_SERVER['REQUEST_METHOD'] )
+ {
+ case 'POST':
+ // detect provided preferences
+ $data = array();
+ foreach (array_keys($_POST) as $key)
+ if ( isset(OC_Shorty_Type::$PREFERENCE[$key]) ) // ignore unknown preference keys
+ {
+ $type = OC_Shorty_Type::$PREFERENCE[$key];
+ $data[$key] = OC_Shorty_Type::req_argument ( $key, $type, FALSE );
+ }
+ // eliminate settings not explicitly set
+ $data = array_diff ( $data, array(FALSE) );
+ // store settings
+ foreach ( $data as $key=>$val )
+ OCP\Config::setUserValue( OCP\User::getUser(), 'shorty', $key, $val );
+ // swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+ OC_Shorty_Tools::ob_control ( FALSE );
+ OCP\Util::writeLog( 'shorty', sprintf("Preference(s) '%s' saved.",implode(',',array_keys($data))), OC_Log::INFO );
+ OCP\JSON::success ( array ( 'data' => $data,
+ 'message' => OC_Shorty_L10n::t("Preference(s) '%s' saved.",implode(',',array_keys($data))) ) );
+ break;
+ case 'GET':
+ // detect requested preferences
+ foreach (array_keys($_GET) as $key)
+ {
+ if ( isset(OC_Shorty_Type::$PREFERENCE[$key]) ) // ignore unknown preference keys
+ {
+ $type = OC_Shorty_Type::$PREFERENCE[$key];
+ $data[$key] = OCP\Config::getUserValue( OCP\User::getUser(), 'shorty', $key);
+ // morph value into an explicit type
+ switch ($type)
+ {
+ case OC_Shorty_Type::ID:
+ case OC_Shorty_Type::STATUS:
+ case OC_Shorty_Type::SORTKEY:
+ case OC_Shorty_Type::SORTVAL:
+ case OC_Shorty_Type::STRING:
+ case OC_Shorty_Type::URL:
+ case OC_Shorty_Type::DATE:
+ settype ( $data[$key], 'string' );
+ break;
+ case OC_Shorty_Type::INTEGER:
+ case OC_Shorty_Type::TIMESTAMP:
+ settype ( $data[$key], 'integer' );
+ break;
+ case OC_Shorty_Type::FLOAT:
+ settype ( $data[$key], 'float' );
+ break;
+ default:
+ } // switch
+ }
+ } // foreach
+ // swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+ OC_Shorty_Tools::ob_control ( FALSE );
+ OCP\Util::writeLog( 'shorty', sprintf("Preference(s) '%s' saved.",implode(',',array_keys($data))), OC_Log::INFO );
+ OCP\JSON::success ( array ( 'data' => $data,
+ 'message' => OC_Shorty_L10n::t('Preference(s) retrieved.') ) );
+ break;
+ default:
+ throw new OC_Shorty_Exception ( "unexpected request method '%s'", $_SERVER['REQUEST_METHOD'] );
+ } // switch
+} catch ( Exception $e ) { OC_Shorty_Exception::JSONerror($e); }
+?>
diff --git a/apps/shorty/ajax/settings.php b/apps/shorty/ajax/settings.php
new file mode 100644
index 00000000000..af76b69e076
--- /dev/null
+++ b/apps/shorty/ajax/settings.php
@@ -0,0 +1,109 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file ajax/settings.php
+ * @brief Ajax method to store one or more system settings (plugin settings)
+ * @param backend-static-base (string) Url to use as a base when the static backend is active (plugins default, may be overridden by user preference)
+ * @returns (json) success/error state indicator
+ * @returns (json) Associative array holding the stored values by their key
+ * @returns (json) Human readable message describing the result
+ * @author Christian Reiner
+ */
+
+// swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+OC_Shorty_Tools::ob_control ( TRUE );
+
+//no apps or filesystem
+$RUNTIME_NOSETUPFS = true;
+
+// Check if we are a user
+OCP\JSON::checkAdminUser ( );
+OCP\JSON::checkAppEnabled ( 'shorty' );
+
+try
+{
+ $data = array();
+ switch ( $_SERVER['REQUEST_METHOD'] )
+ {
+ case 'POST':
+ // detect provided settings
+ $data = array();
+ foreach (array_keys($_POST) as $key)
+ if ( isset(OC_Shorty_Type::$SETTING[$key]) ) // ignore unknown preference keys
+ {
+ $type = OC_Shorty_Type::$SETTING[$key];
+ $data[$key] = OC_Shorty_Type::req_argument ( $key, $type, FALSE );
+ }
+ // eliminate settings not explicitly set
+ $data = array_diff ( $data, array(FALSE) );
+ // store settings one by one
+ foreach ( $data as $key=>$val )
+ OCP\Config::setAppValue( 'shorty', $key, $val );
+ break;
+ case 'GET':
+ // detect requested settings
+ foreach (array_keys($_GET) as $key)
+ {
+ if ( isset(OC_Shorty_Type::$SETTING[$key]) ) // ignore unknown preference keys
+ {
+ $type = OC_Shorty_Type::$SETTING[$key];
+ $data[$key] = OCP\Config::getUserValue( OCP\User::getUser(), 'shorty', $key);
+ // morph value into an explicit type
+ switch ($type)
+ {
+ case OC_Shorty_Type::ID:
+ case OC_Shorty_Type::STATUS:
+ case OC_Shorty_Type::SORTKEY:
+ case OC_Shorty_Type::SORTVAL:
+ case OC_Shorty_Type::STRING:
+ case OC_Shorty_Type::URL:
+ case OC_Shorty_Type::DATE:
+ settype ( $data[$key], 'string' );
+ break;
+ case OC_Shorty_Type::INTEGER:
+ case OC_Shorty_Type::TIMESTAMP:
+ settype ( $data[$key], 'integer' );
+ break;
+ case OC_Shorty_Type::FLOAT:
+ settype ( $data[$key], 'float' );
+ break;
+ default:
+ } // switch
+ }
+ } // foreach
+ break;
+ default:
+ throw new OC_Shorty_Exception ( "unexpected request method '%s'", $_SERVER['REQUEST_METHOD'] );
+ } // switch
+
+ // swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+ OC_Shorty_Tools::ob_control ( FALSE );
+ OCP\Util::writeLog( 'shorty', sprintf("Setting(s) '%s' saved.",implode(',',array_keys($data))), OC_Log::INFO );
+ OCP\JSON::success ( array ( 'data' => $data,
+ 'message' => OC_Shorty_L10n::t('Setting saved.') ) );
+} catch ( Exception $e ) { OC_Shorty_Exception::JSONerror($e); }
+?>
diff --git a/apps/shorty/ajax/status.php b/apps/shorty/ajax/status.php
new file mode 100644
index 00000000000..6d00bf58854
--- /dev/null
+++ b/apps/shorty/ajax/status.php
@@ -0,0 +1,67 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file ajax/status.php
+ * @brief Ajax method to modify the status of an existing shorty
+ * @param id (string) Internal id of the referenced shorty
+ * @param title (string) Human readable title
+ * @param notes (string) Any additional information in free text form
+ * @returns (json) success/error state indicator
+ * @returns (json) Associative array holding the id of the shorty whose click was registered
+ * @author Christian Reiner
+ */
+
+// swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+OC_Shorty_Tools::ob_control ( TRUE );
+
+//no apps or filesystem
+$RUNTIME_NOSETUPFS = true;
+
+// Check if we are a user
+OCP\JSON::checkLoggedIn ( );
+OCP\JSON::checkAppEnabled ( 'shorty' );
+
+try
+{
+ $p_id = OC_Shorty_Type::req_argument ( 'id', OC_Shorty_Type::ID, TRUE );
+ $p_status = OC_Shorty_Type::req_argument ( 'status', OC_Shorty_Type::STATUS, FALSE );
+ $param = array
+ (
+ 'user' => OCP\User::getUser ( ),
+ 'id' => $p_id,
+ 'status' => $p_status,
+ );
+ $query = OCP\DB::prepare ( OC_Shorty_Query::URL_STATUS );
+ $query->execute ( $param );
+
+ // swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+ OC_Shorty_Tools::ob_control ( FALSE );
+ OCP\Util::writeLog( 'shorty', sprintf("Status change for shorty with id '%s' saved",$p_id), OC_Log::INFO );
+ OCP\JSON::success ( array ( 'data' => array('id'=>$p_id),
+ 'message' => sprintf(OC_Shorty_L10n::t("Status change for shorty with id '%s' saved"),$p_id) ) );
+} catch ( Exception $e ) { OC_Shorty_Exception::JSONerror($e); }
+?>
diff --git a/apps/shorty/appinfo/app.php b/apps/shorty/appinfo/app.php
new file mode 100644
index 00000000000..635fda4ee27
--- /dev/null
+++ b/apps/shorty/appinfo/app.php
@@ -0,0 +1,53 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file appinfo/app.php
+ * @brief Basic registration of plugin at ownCloud
+ * @author Christian Reiner
+ */
+
+OC::$CLASSPATH['OC_Shorty_Backend'] = 'apps/shorty/lib/backend.php';
+OC::$CLASSPATH['OC_Shorty_Exception'] = 'apps/shorty/lib/exception.php';
+OC::$CLASSPATH['OC_Shorty_HttpException'] = 'apps/shorty/lib/exception.php';
+OC::$CLASSPATH['OC_Shorty_L10n'] = 'apps/shorty/lib/l10n.php';
+OC::$CLASSPATH['OC_Shorty_Meta'] = 'apps/shorty/lib/meta.php';
+OC::$CLASSPATH['OC_Shorty_Query'] = 'apps/shorty/lib/query.php';
+OC::$CLASSPATH['OC_Shorty_Tools'] = 'apps/shorty/lib/tools.php';
+OC::$CLASSPATH['OC_Shorty_Type'] = 'apps/shorty/lib/type.php';
+
+OCP\App::addNavigationEntry ( array ( 'id' => 'shorty_index',
+ 'order' => 71,
+ 'href' => OCP\Util::linkTo ( 'shorty', 'index.php' ),
+ 'icon' => OCP\Util::imagePath( 'shorty', 'shorty.svg' ),
+ 'name' => 'Shorty' ) );
+
+OCP\App::register ( array ( 'order' => 71, 'id' => 'shorty', 'name' => 'Shorty' ) );
+OCP\App::registerAdmin ( 'shorty', 'settings' );
+OCP\App::registerPersonal ( 'shorty', 'preferences' );
+OCP\Util::connectHook ( 'OC_User', 'post_deleteUser', 'OC_Shorty_Hooks', 'deleteUser');
+
+?>
diff --git a/apps/shorty/appinfo/database.xml b/apps/shorty/appinfo/database.xml
new file mode 100644
index 00000000000..0cac24dbf25
--- /dev/null
+++ b/apps/shorty/appinfo/database.xml
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!--
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+-->
+
+<!--
+ /**
+ * @file appinfo/database.xml
+ * @brief Database scheme definition for plugins tables
+ * @author Christian Reiner
+ */
+ -->
+
+<database>
+ <name>*dbname*</name>
+ <create>true</create>
+ <overwrite>false</overwrite>
+ <charset>latin1</charset>
+ <table>
+ <name>*dbprefix*shorty</name>
+ <declaration>
+ <field>
+ <name>id</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>12</length>
+ </field>
+ <field>
+ <name>status</name>
+ <type>text</type>
+ <default>'shared'</default>
+ <notnull>true</notnull>
+ <length>10</length>
+ </field>
+ <field>
+ <name>title</name>
+ <type>text</type>
+ <default>''</default>
+ <notnull>true</notnull>
+ <length>80</length>
+ </field>
+ <field>
+ <name>favicon</name>
+ <type>text</type>
+ <default>''</default>
+ <notnull>false</notnull>
+ <length>1024</length>
+ </field>
+ <field>
+ <name>source</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>4096</length>
+ </field>
+ <field>
+ <name>target</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>4096</length>
+ </field>
+ <field>
+ <name>user</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>64</length>
+ </field>
+ <field>
+ <name>until</name>
+ <type>date</type>
+ <notnull>false</notnull>
+ <default></default>
+ </field>
+ <field>
+ <name>created</name>
+ <type>date</type>
+ <notnull>false</notnull>
+ <default></default>
+ </field>
+ <field>
+ <name>accessed</name>
+ <type>timestamp</type>
+ <notnull>false</notnull>
+ <default></default>
+ </field>
+ <field>
+ <name>clicks</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <unsigned>true</unsigned>
+ <length>10</length>
+ </field>
+ <field>
+ <name>notes</name>
+ <type>text</type>
+ <default>''</default>
+ <notnull>true</notnull>
+ <length>4096</length>
+ </field>
+
+ <index>
+ <name>shorty_id</name>
+ <unique>true</unique>
+ <field>
+ <name>id</name>
+ <sorting>descending</sorting>
+ </field>
+ </index>
+ <index>
+ <name>shorty_user</name>
+ <unique>false</unique>
+ <field>
+ <name>user</name>
+ <sorting>ascending</sorting>
+ </field>
+ </index>
+ <index>
+ <name>shorty_source</name>
+ <unique>false</unique>
+ <field>
+ <name>source</name>
+ <sorting>ascending</sorting>
+ </field>
+ </index>
+ </declaration>
+ </table>
+</database>
diff --git a/apps/shorty/appinfo/info.xml b/apps/shorty/appinfo/info.xml
new file mode 100644
index 00000000000..bbee9dd673b
--- /dev/null
+++ b/apps/shorty/appinfo/info.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<!--
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+-->
+
+<!--
+ /**
+ * @file appinfo/info.xml
+ * @brief Definition of central information about this plugin
+ * @author Christian Reiner
+ /*
+ -->
+
+<info>
+ <id>shorty</id>
+ <name>Shorty</name>
+ <description>Shorty offers a service to store, manage and use a collection of short links
+pointing to ressources in the web. The features are a combination of a
+centralized bookmarks collection, an url shortener and an access control.
+The collection is presented as a link of "shorties", a shorty can be created
+either manually in the list ("New") or by using the "Shortlet". The Shortlet
+is something like a Booklet, a JS based bookmark meant to be stored inside the
+bookmark toolbar or area of a web browser. When clicked, the page currently
+open in the browser will be offered to be added to the list of existing shorties.
+For more information see: http://apps.owncloud.com/content/show.php/Shorty?content=150401
+Current version: 0.2.2 (06.06.2012)</description>
+ <version>0.2.2</version>
+ <licence>AGPL</licence>
+ <author>Christian Reiner</author>
+ <require>4</require>
+ <public>
+ <shorty_relay>relay.php</shorty_relay>
+ <shorty_qrcode>qrcode.php</shorty_qrcode>
+ </public>
+</info>
diff --git a/apps/shorty/appinfo/migrate.php b/apps/shorty/appinfo/migrate.php
new file mode 100644
index 00000000000..5efce251a65
--- /dev/null
+++ b/apps/shorty/appinfo/migrate.php
@@ -0,0 +1,101 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file appinfo/migrate.php
+ * @brief OC migration support
+ * @author Christian Reiner
+ */
+
+class OC_Migration_Provider_Shorty extends OC_Migration_Provider
+{
+
+ function export ( )
+ {
+ OCP\Util::writeLog ( 'migration','starting export for Shorty', OCP\Util::INFO );
+ $options = array(
+ 'table'=>'shorty',
+ 'matchcol'=>'user',
+ 'matchval'=>$this->uid,
+ 'idcol'=>'id'
+ );
+ $ids = $this->content->copyRows( $options );
+ $count = OC_Shorty_Tools::countShorties();
+ // check for success
+ if( (is_array($ids) && is_array($count))
+ && (count($ids)==$count['sum_shortys']) )
+ return true;
+ else return false;
+ } // function export
+
+ function import ( )
+ {
+ switch( $this->appinfo->version )
+ {
+ default:
+ $query = $this->content->prepare( "SELECT * FROM shorty WHERE user_id LIKE ?" );
+ $result = $query->execute( array( $this->olduid ) );
+ if (is_array(is_array($result)))
+ {
+ while( $row = $result->fetchRow() )
+ {
+ $param = array (
+ 'id' => $row['id'],
+ 'status' => $row['status'],
+ 'title' => $row['title'],
+ 'favicon' => $row['favicon'],
+ 'source' => $row['source'],
+ 'target' => $row['target'],
+ 'user' => $row['user'],
+ 'until' => $row['until'],
+ 'created' => $row['created'],
+ 'accessed' => $row['accessed'],
+ 'clicks' => $row['clicks'],
+ 'notes' => $row['notes'],
+ );
+ // import each shorty one by one, no special treatment required, since no autoincrement id is used
+ $query = OCP\DB::prepare( sprintf ( "INSERT INTO *PREFIX*shorty(%s) VALUES (%s)",
+ implode(',',array_keys($param)),
+ implode(',',array_fill(0,count($param),'?')) ) );
+ $query->execute( $param );
+ } // while
+ } // if
+ break;
+ } // switch
+ // check for success by counting the generated entries
+ $count = OC_Shorty_Tools::countShorties();
+ if( (is_array($result) && is_array($count))
+ && (count($result)==$count['sum_shortys']) )
+ return true;
+ else return false;
+ } // function import
+
+} // class OC_Migration_Provider_Shorty
+
+// Load the provider
+new OC_Migration_Provider_Shorty ( 'shortys' );
+
+?>
diff --git a/apps/shorty/css/preferences.css b/apps/shorty/css/preferences.css
new file mode 100644
index 00000000000..37a86a534a6
--- /dev/null
+++ b/apps/shorty/css/preferences.css
@@ -0,0 +1,109 @@
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file css/preferences.css
+ * @brief Style definitions for the user preference dialog
+ * @author Christian Reiner
+ */
+
+#shorty img {
+ vertical-align: middle;
+ margin:0;
+ padding:0;
+}
+#shorty #shortlet {
+ outline: medium none;
+ padding: 0.6em 0.8em;
+ background-image: -moz-linear-gradient(center bottom, Lavender 0%, white 50%);
+ border: 1px solid #DDDDDD;
+ border-radius: 0.5em 0.5em 0.5em 0.5em;
+ box-shadow: 0 1px 0 #BBBBBB inset;
+}
+#shorty #shortlet a {
+ font-style: italic;
+ color: DarkSlateGray;
+}
+#shorty #shortlet:after {
+ content: url("%appswebroot%/apps/shorty/img/drag_me.png");
+ position: relative;
+ top: -12px;
+ margin-left: -160px;
+}
+#shorty .chzn-container {
+ vertical-align: middle;
+}
+
+#shorty .title {
+ margin-bottom: 0.6em;
+}
+
+#shorty .aspect {
+ display: inline-block;
+ font-weight: bold;
+ width: 6em;
+ margin: 0 0.2em 0.4em 0;
+}
+
+#shorty .explain {
+ display: inline-block;
+ font-style: italic;
+ margin: 0.3em;
+}
+
+#shorty .example {
+ font-family: Monospace;
+ margin: 0.3em;
+}
+
+#shorty a.external {
+ text-decoration:underline;
+}
+#shorty a.external:after {
+ content:url("%appswebroot%/apps/shorty/img/actions/external.png");
+ margin-left:0.3em;
+}
+
+#verification #hourglass {
+ display: block;
+ text-align: center;
+ width: 100%; height: 100%;
+ margin-top: 20px;
+}
+
+#verification #failure {
+ display: none;
+ position: absolute;
+}
+
+#verification #success {
+ display: none;
+ position: absolute;
+}
+
+#verification legend {
+ width: 100%;
+ text-align: center;
+}
diff --git a/apps/shorty/css/settings.css b/apps/shorty/css/settings.css
new file mode 100644
index 00000000000..e92031d8913
--- /dev/null
+++ b/apps/shorty/css/settings.css
@@ -0,0 +1,59 @@
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file css/settings.css
+ * @brief Style definitions for the system settings dialog
+ * @author Christian Reiner
+ */
+
+#shorty img {
+ vertical-align: middle;
+ margin:0;
+ padding:0;
+}
+#shorty .aspect {
+ display: inline-block;
+ font-weight: bold;
+ width: 6em;
+ margin: 0 0.2em 0.4em;
+}
+
+#shorty .explain {
+ display: inline-block;
+ font-style: italic;
+ margin: 0.3em;
+}
+
+#shorty .example {
+ font-family: Monospace;
+ margin: 0.3em;
+ text-decoration: underline;
+ color: blue;
+}
+
+#shorty .title {
+ margin-bottom: 0.6em;
+}
diff --git a/apps/shorty/css/shorty.css b/apps/shorty/css/shorty.css
new file mode 100644
index 00000000000..b071f06f77f
--- /dev/null
+++ b/apps/shorty/css/shorty.css
@@ -0,0 +1,488 @@
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file css/shorty.css
+ * @brief Style definitions of the plugins own view
+ * @author Christian Reiner
+ */
+
+/* everything :-) */
+#content { overflow: auto; }
+
+/* a case sensitive help button */
+#content #controls #help {
+/* float:right; */
+}
+/* adapt optical appearance in case the help button is pushed into the header */
+#header #help {
+ float:right;
+ background-color:blue;
+}
+
+/* the shorty desktop, a canvas, contains all elements except of those pushed into the header */
+#desktop {
+ display: inline-block;
+ position: absolute;
+/* width: 100%; height: 100%; */
+ background: transparent;
+ text-align: left;
+ margin-top: 30px;
+ padding: 2.5em 0.5em;
+ padding-top: 20px;
+}
+/* top bar, holding main control elements */
+#controls {
+ display: none;
+}
+/* a dialog for verification of the static backend setup */
+#verification {
+ overflow: hidden;
+}
+#verification p {
+ padding-bottom:0.4em;
+}
+
+/* an activity visualizer, currently a 'rotating timer wheel' */
+#hourglass {
+ display: none;
+ position: absolute;
+ width: 100%; height: 100%;
+ z-index: 1;
+ background: transparent;
+}
+#hourglass img {
+ margin:4em 8em;
+}
+/* a replacement list visualized in case the 'real' list is empty */
+#vacuum {
+ display: none;
+ position: absolute;
+ left:8em; top:9em;
+ width: 100%; height: 100%;
+ z-index: 2;
+ white-space: nowrap;
+ font-style: italic;
+ background: transparent;
+}
+.shorty-standalone #busy {
+ display: none;
+ float: right;
+}
+
+/* the list of shorties */
+#list {
+ display: inline;
+ position: absolute;
+ z-index: 3;
+ margin: 0.4em;
+ background: transparent;
+ table-layout: fixed;
+ width: 100%;
+}
+/* the collapsable toolbar below the lists column title row */
+#list thead #toolbar img {
+ cursor: pointer;
+ vertical-align: middle;
+}
+#list thead #toolbar img.shorty-sorter {
+ opacity:0.6;
+}
+/* last column in the list, content usually invisible, except when hovered */
+#list thead #toolbar img.shorty-active {
+ border:1px solid #888888;
+ border-radius: 0.3em;
+}
+#list thead tr:nth-child(2) > th {
+ padding-bottom:0.5em;
+}
+/* somewhat more fixed layout for the table */
+#list thead #titlebar th span {
+ display:inline-block;
+}
+#list tbody th#title,
+#list tbody td#title,
+#list tbody th#target,
+#list tbody td#target {
+ width:10em;
+}
+#list thead #clicks span {
+ width:3em;
+}
+#list thead #until span {
+ width:5em;
+}
+#list thead #toolbar input,
+#list thead #toolbar .chzn-container {
+ width:6em;
+ font-size: 11px;
+ margin: 0;
+ padding: 0.4em 0.3em 0.2em;
+ vertical-align: baseline;
+ background-color:#FFFFFF;
+}
+
+/* column title apeparance */
+#list thead th {
+ font-weight: bold;
+ padding: 1px 6px;
+}
+#list thead th#clicks,
+#list thead th#until,
+#list thead th#actions {
+ text-align: center;
+}
+/* hide the dummy row */
+#list thead tr:last-child { display: none; }
+/* hide the content of the 'actions' column, except when being hovered */
+#list tbody tr td span.shorty-actions {
+ visibility:visible;
+ opacity:0;
+ transition: opacity 500ms;
+ -moz-transition: opacity 500ms;
+ -webkit-transition: opacity 500ms;
+ -ms-transition: opacity 500ms;
+ -o-transition: opacity 500ms;
+ width:100px;
+ overflow:auto;
+}
+#list tbody tr:hover td span.shorty-actions {
+ opacity:1;
+ transition: opacity 500ms;
+ -moz-transition: opacity 500ms;
+ -webkit-transition: opacity 500ms;
+ -ms-transition: opacity 500ms;
+ -o-transition: opacity 500ms;
+}
+/* highlight a specific, 'clicked' row in the list */
+#list tbody tr.clicked {
+ border-radius: 0.5em;
+ background-color:#EEEEEE;
+ box-shadow: 0 0 0.5px #777777;
+ transition: background-color 500ms;
+ -moz-transition: background-color 500ms;
+ -webkit-transition: background-color 500ms;
+ -ms-transition: background-color 500ms;
+ -o-transition: background-color 500ms;
+}
+/* strike through visible content of rows (entries) marked as deleted */
+#list tbody tr.deleted>#title,
+#list tbody tr.deleted>#target,
+#list tbody tr.deleted>#clicks,
+#list tbody tr.deleted>#until {
+ color: gray;
+ text-decoration: line-through;
+ background: transparent url('%appswebroot%/apps/shorty/img/status/strike.png') 0 50% repeat-x;
+}
+/* replace the 'delete' action icon by an 'undelete' icon */
+#list tbody tr.deleted>#actions #del:after {
+ content: url("%appswebroot%/apps/shorty/img/actions/recycle.png");
+ position: relative;
+ top: 3px;
+ margin-left: -19px;
+ padding: 0;
+ opacity: 0;
+}
+#list tbody tr.deleted:hover>#actions #del>img {
+ opacity: 0;
+}
+#list tbody tr.deleted:hover>#actions #del:after {
+ opacity: 1;
+}
+/* general table cell appearance */
+#list tbody td {
+ padding: 1px 6px;
+}
+#list tbody td#until {
+ text-align: center;
+}
+#list tbody td#clicks {
+ text-align: right;
+ margin-right: 0.4em;
+}
+/* prettyfied select boxes */
+#content span.shorty-select select {
+ width: 6.4em;
+ margin: 0;
+ padding: 0.2em;
+ opacity:0.8;
+ border:0 none;
+ font-size: 10px;
+}
+#content span.shorty-select {
+ border: 1px solid #DDDDDD;
+ border-radius: 0.5em 0.5em 0.5em 0.5em;
+ box-shadow: 0 1px 1px #FFFFFF, 0 1px 0 #BBBBBB inset;
+ outline: medium none;
+ padding: 2px;
+}
+/* general style of interactive dialogs */
+.shorty-dialog {
+ display: none;
+ color: black;
+ border-bottom-left-radius: 1em;
+ border-bottom-right-radius: 1em;
+ box-shadow: 0 2px 1px #777777;
+ vertical-align:middle;
+ z-index:100;
+}
+.shorty-dialog fieldset {
+ position:relative;
+ border-bottom-left-radius: 0.5em;
+ border-bottom-right-radius: 0.5em;
+ border: 1px solid gray;
+ padding: 2px 4px 8px;
+}
+.shorty-dialog br {
+ clear: left;
+}
+.shorty-dialog legend {
+ font-weight: bold;
+ margin: 0 0 0 5px;
+ padding: 0 0.5em 0 0.2em;
+}
+.shorty-dialog legend a {
+ padding: 0;
+}
+.shorty-dialog textarea {
+ width: 80%; height: 5em;
+ background: none repeat scroll 0 0 #F8F8F8;
+ color: #555555;
+ border: 1px solid #DDDDDD;
+ border-radius: 0.5em;
+ cursor: text;
+ vertical-align:baseline;
+ margin: 0.3em;
+}
+.shorty-dialog textarea:hover,
+.shorty-dialog textarea:active {
+ background-color: #FFFFFF;
+ color: #333333;
+ opacity: 1;
+}
+.shorty-dialog input {
+ width: 80%;
+}
+.shorty-dialog a,
+.shorty-dialog input,
+.shorty-dialog select,
+.shorty-dialog textarea,
+.shorty-dialog button {
+ padding: 0.4em 0.2em;
+}
+.shorty-dialog input[readonly],
+.shorty-dialog select[readonly],
+.shorty-dialog textarea[readonly],
+.shorty-dialog button[readonly] {
+ border: 0 none;
+/* box-shadow: 0 0 0; */
+ box-shadow: 0 0 0 #FFFFFF;
+ outline: none;
+ background: none;
+ background-color: #F7F7F7;
+ padding: 0.3em 0.3em;
+}
+.shorty-dialog button#confirm {
+ width: 12em; height: 2em;
+ border: 1px solid lightgray;
+ background-color: #EEEEEE;
+}
+.shorty-dialog button#confirm.sharp {
+ background-color: white;
+}
+.shorty-dialog button#confirm.sharp:hover {
+/* border-color: green; */
+ border: 1px solid gray;
+}
+
+/* dialogs floating above other content ('standing alone') */
+.shorty-standalone {
+ width: 46em;
+ margin: -1px 1em 0;
+ padding: 0.5em;
+ background: none repeat scroll 0 0 #F7F7F7;
+}
+.shorty-standalone fieldset {
+ width: 45em;
+}
+/* dialogs related to elements ('embedded in the content') */
+.shorty-embedded {
+ left: 6em;
+ width: 34em;
+ margin: -1px 4em 0;
+ padding: 0.5em;
+ background: none repeat scroll 0 0 #EEEEEE;
+ position: absolute;
+}
+.shorty-embedded fieldset {
+/* width: 32em; */
+}
+.shorty-embedded a#source,
+.shorty-embedded a#relay,
+.shorty-embedded a#target {
+ display: inline-block;
+ width: 26em;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+}
+.shorty-embedded a.shorty-clickable:before {
+ content:url("%appswebroot%/apps/shorty/img/actions/external.png");
+ margin-right:0.3em;
+}
+.shorty-embedded select {
+ margin: 0 0 0.2em 1.0em;
+ padding: 0.2em 0.4em 0.2em 0.2em;
+ border: 0 none;
+ background-color: #F4F4F4;
+}
+.shorty-embedded img.shorty-usage {
+ opacity: 0.6;
+ margin: 1em 1.5em 0.2em;
+ padding: 0;
+}
+.shorty-embedded img.shorty-usage.disabled {
+ opacity: 0.2;
+}
+.shorty-embedded img.shorty-usage:not(.disabled):hover {
+ cursor: pointer;
+}
+.shorty-dialog label {
+ display: inline-block;
+ clear:none;
+ width: 5.5em;
+ text-align: left;
+ vertical-align: baseline;
+ padding: 0.6em 0.5em 0.4em 0.3em;
+ cursor: default;
+}
+.shorty-dialog span {
+ padding: 0.6em 0.5em 0.4em 0.3em;
+}
+.shorty-dialog span.label-line {
+ display: inline;
+ clear: none;
+ margin: 0;
+ padding: 0;
+}
+/* elements (rows) freshly added to the list, this calss might trigger visual effects like pulsation */
+.shorty-fresh {
+ display: none;
+}
+/* elements (rows) currently filtered out (toolbars column filter options) */
+.shorty-filtered {
+ display: none;
+}
+/* style of all icons (as opposed to all images) */
+.shorty-icon {
+ display: inline;
+ background: transparent;
+ vertical-align: middle;
+ padding: 1px;
+}
+/* dim entries with expired 'until' dates */
+.shorty-expired {
+ color: gray;
+}
+/* but keep embedded dialogs readable */
+.shorty-expired .shorty-dialog {
+ color: black;
+}
+.shorty-expired img {
+ opacity : 0.2;
+}
+.shorty-expired .shorty-dialog img {
+ opacity : 1;
+}
+.shorty-expired #until {
+ text-decoration: line-through;
+}
+/* some general style flavours */
+.shorty-prompt {
+ margin-left: 4px;
+}
+.shorty-value {
+ margin-right: 4px;
+}
+.shorty-single {
+ padding: 0.5em 1em;
+}
+.shorty-single:hover {
+ background-color: #EAEAEA;
+}
+.shorty-single:hover .shorty-actions {
+ display: block;
+}
+.shorty-id {
+ color: black;
+}
+.shorty-date {
+ width: 40%;
+}
+.shorty-notes {
+ color: blue;
+ text-decoration: none;
+}
+.shorty-source {
+ color: gray;
+}
+.shorty-target {
+ color: green;
+}
+.shorty-meta {
+ margin: 0.1em;
+}
+/* close buttons in dialogs, embedded or standalone */
+.shorty-close-button {
+ display:inline;
+ float:left;
+ margin:0 0.8em 0 0.3em;
+ padding:0;
+}
+.shorty-close-button img {
+ vertical-align: middle;
+ margin:0;
+ padding:0;
+}
+/* unlike elsewhere we underline clickable links in dialogs */
+.shorty-clickable {
+ white-space: nowrap;
+ text-decoration: underline;
+}
+.shorty-framed {
+ display:inline-block;
+ padding:1em;
+ background-color:white;
+ border:1px solid black;
+}
+/* keep long entries like urls from disrupting the compact appearance of the list */
+.ellipsis {
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ width: inherit;
+} \ No newline at end of file
diff --git a/apps/shorty/doc/CHANGELOG b/apps/shorty/doc/CHANGELOG
new file mode 100644
index 00000000000..b1b09b50fb8
--- /dev/null
+++ b/apps/shorty/doc/CHANGELOG
@@ -0,0 +1,59 @@
+*****
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*****
+
+Changelog:
+
+* Wed Jun 06 2012 Christian Reiner: version 0.2.2
+Added swallowing of accidential outputs during ajax requests.
+This protects the replies JSON struction from being corrupted.
+Additional server side log entries on level INFO
+* Sat Jun 02 2012 Christian Reiner: version 0.2.1
+Fixed broken Shortlet
+* Fri Jun 01 2012 Christian Reiner: version 0.2.0
+Support of ownCloud version 4 (app interface incompatible to prior versions)
+Additional status 'private' for Shortys only accessible for the owner himself
+Rudimentary SMS support for smart phones (more an assistance, can be disabled)
+Additional QRCodes to publish Shorty source urls on the web (or elsewhere)
+Data migration support as introduced by OC4
+Cleanup of Shortys upon user account deletion
+* Sun May 28 2012 Christian Reiner: version 0.1.6
+Activation of Shorty sharing via SMS.
+Enhanced backend descriptions.
+* Sat Apr 28 2012 Christian Reiner: version 0.1.5
+Target url verification is less strict now, this way also more 'exotic' urls notations are accepted.
+A few changes to the initilization scripts to prevent race conditions at startup.
+Changed url specification in the Shortlet to prevent conflicts with a serverside suhosin protection.
+* Fri Apr 27 2012 Christian Reiner: version 0.1.4
+Visualization of busy state in dialogs whilst fetch meta data from targets.
+* Fri Apr 27 2012 Christian Reiner: version 0.1.3
+Prevented untimely submission of dialogs before having validated their contents.
+* Fri Apr 27 2012 Christian Reiner: version 0.1.2
+Fixed race conditions during initialization.
+* Thu Apr 26 2012 Christian Reiner: version 0.1.1
+Minor appearance corrections.
+* Wed Apr 25 2012 Christian Reiner: version 0.1.0
+First official release.
+* Tue Apr 24 2012 Christian Reiner: version 0.0.9
+Initial release.
+Buggy, but some parts actually working.
diff --git a/apps/shorty/doc/CONFIGURATION b/apps/shorty/doc/CONFIGURATION
new file mode 100644
index 00000000000..2a7dab60d19
--- /dev/null
+++ b/apps/shorty/doc/CONFIGURATION
@@ -0,0 +1,113 @@
+*****
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*****
+
+There are five aspects that can be configured:
+1.) System wide "static backend" (optional)
+2.) Selection of a backend per user (optional)
+3.) Import of the "shortlet" into a web browser (optional)
+4.) Access control inside each single "shorty" (implicit)
+5.) Sending of Shortys as SMS
+
+1.) System wide "static backend" (optional)
+(Requires adminisstrative rights)
+This optional setting enables a 'static backend'. That is a backend generting
+source urls based on a static base url, thus the name. That base url should be
+chosen as short as possible, since this defines the total length of all links
+to be posted and used. Most likely the definition of that url requires the
+configuration of rewrite rules on the server side. So it is only an option for
+experienced adminsitrators with access to the server configuration, be it
+centralized or based on decentralized per-directory rules (".htaccess files").
+A basic example is packed in the .htaccess file (might be hidden in a directory
+listing because of the leading dot (.) in the name). The rules must be
+configured such that all requests to urls of the scheme <base url><shorty key>
+are mapped directly onto the web url of the shorty plugin
+(http://<domain>/<owncloud>/apps/shorty/). The <shorty key> is a string, 6-12
+characters long, hard to predict. It is guaranteed to be unique throughout the
+system (though in a technically crude manner...). The perfect situation for the
+definition of a meaningful static backend would be a domain with a very short
+name and configuration access to something close to the web root.
+A random example is my own domain "christian-reiner.info". Since that is not
+exactly short I would love to use the additional domain "c-r.info" for the sole
+purpose of a static backend. Unfortunately that domain has been reserved by a
+domain grabber who asks thousands of Euros for it ! So that's no option. At
+least you should try to find a setup where the web path of the ownCloud
+application is not part of the base url. So that you get something like
+http://<domain name>/<shorty key>.
+(Note that the shorty key is NOT part of the base url configuration).
+You can easily test your setup any time by clicking on the example dynamically
+visualized next to the configuration option. It runs a simple self-tests of the
+setup in background.
+There is no need that the base url must be served by the same http server that
+serves the owncoud system. A static rewrite setup may well forward short source
+urls to the shorty plugin inside a remote ownCloud installation. However, it is
+a known issue that the internal validation of the setup won't work with such
+scenario.
+
+2.) Selection of a backend per user (optional)
+To generate the a source url that is part of every shorty the plugin uses a
+backend. The configuration is done by using a preference option in the personal
+preference section of the configuration. You can simply chose one of the
+offered backends (combo box). Changing the backend does not affect any
+previously generated shorties. Meaning they stay valid and usable and keep
+their once defined source url.
+
+Different backends are implemented:
+i.) "-none-"
+As you have guessed this is something like a "dummy" backend without any
+implemented logic. That means the source urls generated are exactly based on
+the web url of the shorty plugin in your ownCloud system. This is not a very
+clever setup, but it certainly works and keeps your data private.
+ii.) "static backend"
+If configured in the system administration a "static backend" is offered. For a
+description see C-1. This backend typically offers shorter source urls, but its
+setup required administrative rights on some http server system.
+iii.) online services (url shorteners)
+A few online services are offered as backends to generate short source urls.
+Usage of some of those services requires you to open a free account at their
+site. Detailed configuration requirements are displayed for the chosen backend.
+If you don't care for details and just want short urls then have a try with the
+tin.ny backend. No registration required, reliable service. But keep in mind
+you depend on an external service as opposed to using a local static backend.
+
+3.) Import of the "shortlet" into a web browser (optional)
+Shorty comes with a neat little "Shortlet" offered in the personal preferneces
+section of ownCloud. It is a "button" you can import into your web browsers
+bookmark toolbar or area by simple drag & drop. The Shortlet should work with
+most modern browsers, though probably not all. Just have a try with it.
+
+4.) Access control inside each single "shorty" (implicit)
+There are a few attributres you can configure freely inside each stored shorty:
+- a title just shown inside the shorty plugin (serves recognition).
+- a notes area, maybe you want to write down whom you send the shorty ?
+- a status option that controls the usage of the shorty.
+
+5.) Sending of Shortys as SMS
+Besides sending of Shortys as email message and copying a Shortys source url
+to the clipboard a third action can be enabled inside the sharing dialog:
+To send a Shortys source url as SMS.
+However, the approach is extremely minimalistic, it relies on the client system
+to correctly handle a 'sms url'. This is typically only given on a mobile
+device, a smart phone. In addition, such url does not allow to specify a message
+body, so the Shortys source url has to be copied and pasted manually.
+The option is disabled by default.
diff --git a/apps/shorty/doc/COPYRIGHT b/apps/shorty/doc/COPYRIGHT
new file mode 100644
index 00000000000..5e501826f28
--- /dev/null
+++ b/apps/shorty/doc/COPYRIGHT
@@ -0,0 +1,23 @@
+*****
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*****
diff --git a/apps/shorty/doc/CREDITS b/apps/shorty/doc/CREDITS
new file mode 100644
index 00000000000..8f7a3dd037c
--- /dev/null
+++ b/apps/shorty/doc/CREDITS
@@ -0,0 +1,32 @@
+*****
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*****
+
+The shorty plugin has been developed by:
+
+Christian Reiner, Hamburg, Germany
+E-Mail: foss@christian-reiner.information
+
+Contributions:
+I'd be more than happy to welcome all contributions to this little experiment.
+Drop me a note, an idea, a patch, a good bottle of wine, anything !
diff --git a/apps/shorty/doc/INSTALLATION b/apps/shorty/doc/INSTALLATION
new file mode 100644
index 00000000000..0a98605376a
--- /dev/null
+++ b/apps/shorty/doc/INSTALLATION
@@ -0,0 +1,53 @@
+*****
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*****
+
+This package is a plugin for the ownCloud web application ("ownCloud app").
+
+There are two ways of installation: automatic and manual
+
+* Automatic installation:
+You need login to your ownCloud using an account with administrative rights.
+Open the 'Apps' section of the adminstration and select 'shorty', enable it.
+Then go on below with the basic configuration steps.
+
+* Manual installation:
+Download the package from here:
+http://apps.owncloud.com/content/show.php/Shorty?content=150401
+Create a subfolder 'shorty' in the "apps" subfolder of your ownCloud web root.
+Unpack the contents of the package into the new folder 'shorty'.
+Now load ownCloud in your favorite web browser and login with an administrative
+account.
+Enable the plugin in the "Apps" section of the configuration ("*") inside
+ownClouds web gui (requires admin rights).
+
+* Basic configuration steps for BOTH types of installation:
+The "Admin" section of the configuration allows to configure a base url to
+enable usage of the static backend (see USAGE).
+The "Preferences" section of that configuration now offers two elements:
+- a "Shortlet" to be dragged to the browsers bookmark area (see USAGE).
+- a configuration of a backend to use to generate source urls (see USAGE).
+The main part of the plugin can be accessed in the navigation menu as "Shorty".
+
+***
+Have fun !
diff --git a/apps/shorty/doc/ISSUES b/apps/shorty/doc/ISSUES
new file mode 100644
index 00000000000..083c4648636
--- /dev/null
+++ b/apps/shorty/doc/ISSUES
@@ -0,0 +1,58 @@
+*****
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*****
+
+This is a short list of known issues:
+
+- The datepicker popup in the edit dialog fails to set the expiration date:
+I have no idea what the problem is. Exactly the same object works flawlessly
+in the dialog to create a new shorty. I can't see any difference, except in
+the edit dialog the date is taken from the existing row entry (working fine).
+Maybe there is some sort of magic spell to awake a datepicker after changing
+its date value ? No idea...
+
+- Clicking on a source or relay url from within the sharing dialog updates the
+clicks counter and access time correctly, but the shown list entry is not
+updated accordingly.
+
+- Shorty does not work in the konqueror web browser
+All appears to be working fine except for one annoying thing:
+the standalone dialogs won't open ! (show, create and edit entries)
+Therefore Shorty is currently more or less useless in this browser.
+
+- Shorty does not work in the rekonq web browser
+The basic navigation elements (menu left) work, but the desktop stays empty.
+Apparently no script is executed, an issue with the js document.ready event.
+
+- It appears there is an issue with setting opacity in the Safari browser:
+The status filter in the lists toolbar shows an ugly black brick instead of a
+dimmed icon overlay. Also some other style issues appear, but the plugin seems
+to be completely usable.
+
+- When internally validating the setup of the base url used in the static
+backend you get a false positive ("doesn't work") although things are fine, if
+the mapping of base url to shorty plugin is done via a redirect. This is for
+exaple the case if you implement the mapping on a remote server because you
+want to use a different domain name for statically shortened urls.
+The reason is that jqueries ajax calls won't handle redirects internally. A
+wrapper will have to be implemented.
diff --git a/apps/shorty/doc/README b/apps/shorty/doc/README
new file mode 100644
index 00000000000..4f590f30cd0
--- /dev/null
+++ b/apps/shorty/doc/README
@@ -0,0 +1,42 @@
+*****
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*****
+
+This "shorty" package implements a plugin for the ownCloud web application.
+Shorty offers a service to store, manage and use a collection of short links
+pointing to ressources in the web. The features are a combination of a
+centralized bookmarks collection, an url shortener and a basic access control.
+The collection is presented as a list of "shorties". A shorty can be created
+either manually in the list ("New shorty") or by using the "Shortlet". The
+Shortlet is something like a Bookmarklet, a script based bookmark meant to be
+stored inside the bookmark collection of a web browser. When clicked, the page
+currently open in the browser will be added to the list of existing shorties.
+
+Each Shorty contains a source and a target url. The source url can be used to
+be posted in forums, sent inside an email message or whatever. It is typically
+shorter than the target url, a full blown web url. But that actually depends
+on the target url and the backend used. The target url is the ressource
+identified by the shorty. Basic access control is implemented to control the
+access of shorties posted to forums or sent per email, but not on a per user or
+per group base. Existing shorties can be blocked for usage, set to expire on a
+certain date and obviously can be removed permanently again.
diff --git a/apps/shorty/doc/ROADMAP b/apps/shorty/doc/ROADMAP
new file mode 100644
index 00000000000..b18099c9af2
--- /dev/null
+++ b/apps/shorty/doc/ROADMAP
@@ -0,0 +1,96 @@
+*****
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*****
+
+Future versions might implement additional features and services. Currently
+planned are the following:
+
+* Ability to create Shortys directly from shared files inside ownCloud
+Creating a Shorty based on the public url generated for shared files works like
+charm. However it is not very convenient to do that manually. It would be great
+to offer that option directly where file sharing is done, in that very dialog.
+However I currently do not see any means to achieve that on ownCloud. There is
+no feature offered to hook additional functionality into exsiting apps.
+This means either the existing file sharing app has to be modified or such a
+modifying core feature has to be implemented into the ownCLoud core.
+Or maybe someone has got another idea ?
+
+* Integrated proxy function as an alternative to forwarding:
+Currently all browser requests are forwarded to the final target url, provided
+that the referenced shorty is still valid and accessible. This is a simple but
+effective strategy. Offering a transparent proxy service as an alternative
+would greatly enhance the privacy protection, since the final target url of a
+referenced web ressource would never be handed out.
+
+* Better, more fine grained access control:
+Currently the only means of access control, apart from defining or removing a
+shorty are its "status". The status can block an otherwise valid shorty from
+usage from outside ownCloud, it can restrict its access to authenticated users
+or it can make an existing shorty freely accessible to the public. Other
+ownCloud plugins allow to define individual access to content on per-user or
+per-group rules.
+
+* Support of further backends:
+Currently the usage of six url shortener services is implemented, besides the
+two private or local ones. Unfortunately three of these had to be disabled due
+to stability issues. Besides correcting the implementation for the disabled it
+might make sense to support further backends, especially other types besides
+online services.
+
+* Performance, stability and usability enhancements:
+"Shorty" is my first web application using up to date web technologies.
+Therefore the implementation certainly is far from the quality one could expect
+from a serious and experienced web developer. One could also put this into more
+constructive terms: there is definately much room for enhancements :-)
+
+* Translations of the shorty module itself (i18n/l10n):
+Main development language is english (us), a german translation exists. I do
+not feel fit enough in other languages to create a translation myself. So I
+certainly welcome any assistance in that aspect. The effort consists of editing
+one single file, take a look at the german example in file l10n/de.php.
+
+* Usage of svg icons instead of png graphics:
+I created all icons in svg format. ownCloud implements a mechanism to use those
+in browsers supporting svg directly. However I did not yet figure out how to
+use that feature without adding too much scripting overhead. Is it possible
+to reference icons the ownCloud way directly in a php based template, without
+the need to explicit handling via javascript ?
+I wish a better API documentation would exist for ownCloud !
+
+* Localization for the date picker fields
+Currently date inputs (expiration date) are not validated explicitly.
+A number of formats work, are accepted correct, but there is no localization
+of the input format to the users language.
+
+* Integrated help button
+One thing I miss throughout the ownCloud application is a context sensitive
+help, or a help button at all ! Originally I planned to integrate on into this
+plugin. However I think that is more something that should be implemented as a
+core feature of ownCloud. Maybe as a separate plugin (app). SOmehing like a
+help framework with a documented api where other plugins (and core apps) can
+attach help texts or documents to. Context sensivity would then simply mean to
+update the reference in the help button for each action taken.
+
+Contributions:
+I'd be more than happy to welcome all contributions to this little experiment.
+Drop me a note, an idea, a patch, a good bottle of wine, anything !
diff --git a/apps/shorty/doc/STATUS b/apps/shorty/doc/STATUS
new file mode 100644
index 00000000000..0b85d147028
--- /dev/null
+++ b/apps/shorty/doc/STATUS
@@ -0,0 +1,42 @@
+*****
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*****
+
+The current status of this package is as follows:
+
+The initial release is definately buggy and contains annoying shortcomings.
+It has been developed on a linux system using a firefox browser, so this is
+most likely the best working combination. Basic usage test have been made with
+a few other browsers. The package appears to be working in general though there
+might be some minor differences in apeparance.
+
+Known shortcomings are:
+- only very basic input validation is done.
+- access control is not yet fine grained, no user or group support exists.
+- markup, scripting and style definitions are the work of a web app newbie.
+
+So "sorry for any inconveniences" !
+
+Contributions:
+I'd be more than happy to welcome all contributions to this little experiment.
+Drop me a note, an idea, a patch, a good bottle of wine, anything !
diff --git a/apps/shorty/doc/USAGE b/apps/shorty/doc/USAGE
new file mode 100644
index 00000000000..a82f881992a
--- /dev/null
+++ b/apps/shorty/doc/USAGE
@@ -0,0 +1,64 @@
+*****
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*****
+
+=== Overview ===
+See file README.
+
+=== Installation ===
+See file INSTALLATION.
+
+=== Requirements ===
+ownCloud version 2 or newer on the server side.
+A css3 capable and reasonably standards conform web browser.
+A scripting capable bookmarks feature for the optional Shortlet thing.
+
+=== Usage ===
+This is only a very basic description. The plugin should be fairly intuitive to
+use, just have a try with a few web urls. To get going I suggest that you
+- chose a backend (see configuration below).
+- import the shortlet into your preferred web browser (see configuration below).
+- define a few shorties either manually ("New") or by using the shortlet.
+All actions on existing shorties are accessed using the action icons shown to
+the right of the shorty in the list. Daily uses is probably based on selection
+the 'share' action and grabbing the visualized "source url".
+By clicking on the header row of the list of shorties you can open (and close)
+a "toolbox" offering options to sort, filter and reload the list. The sorting
+you chose is stored in a persistant manner, not so any filters defined. You
+cannot accidentially close the toolbox when there are still filters active.
+You can remove (delete) existing shorties, but those shorties will only be
+marked as deleted. This way you can 'undelete' them if required. Permanent
+removal is done next time you access the shorty plugin. If you really want to
+make sure the removal is done NOW then simply reload the list. This is done in
+the "toolbox" of the list (click on the header row) by chosing the "reload"
+icon on the left (or by a simple browser reload).
+
+=== PRIVACY ===
+Shorty allows you to stay more in control over links you post or send. It is up
+to you to decide wether a once posted link should stay valid and usable or not.
+However it is also very important to keep in mind that such form of control can
+never replace any serious form of access control over the content published via
+a published link. Shorty works as a short url resolver, meaning it forwards any
+requesting web browser to the real address of the published content. This might
+change when if future an integrated proxy function gets implemented, as opposed
+to the current forwarding mechanism.
diff --git a/apps/shorty/img/actions/down.png b/apps/shorty/img/actions/down.png
new file mode 100644
index 00000000000..d2627e525ee
--- /dev/null
+++ b/apps/shorty/img/actions/down.png
Binary files differ
diff --git a/apps/shorty/img/actions/down.svg b/apps/shorty/img/actions/down.svg
new file mode 100644
index 00000000000..b01642681c1
--- /dev/null
+++ b/apps/shorty/img/actions/down.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.8pt" height="12.8pt">
+<defs/>
+<g id="layer1">
+ <path id="path3086" transform="matrix(0.000287724 1 -1 0.000287724 13.9983 1.99816)" fill="#000000" d="M2.22045e-15 0L12 6L0 12Z"/>
+ </g>
+</svg>
diff --git a/apps/shorty/img/actions/external.png b/apps/shorty/img/actions/external.png
new file mode 100644
index 00000000000..5da1c41c45d
--- /dev/null
+++ b/apps/shorty/img/actions/external.png
Binary files differ
diff --git a/apps/shorty/img/actions/external.svg b/apps/shorty/img/actions/external.svg
new file mode 100644
index 00000000000..32453b1ca29
--- /dev/null
+++ b/apps/shorty/img/actions/external.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="9.6pt" height="9.6pt">
+<defs/>
+<g id="defitem0">
+ <rect id="rect" transform="translate(1.5, 4.5)" fill="#ffffff" stroke="#585858" stroke-width="1.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="4.00000000000" width="4.80000000000pt" height="4.80000000000pt"/>
+ <path id="arrow" transform="translate(2.75, 1)" fill="#303030" fill-rule="evenodd" d="M1.25 1.5L2.25 2.5L2.5 3.25L0 5.5L2.75 8.25L5 5.75L5.75 6L6.75 7L8.25 5.5L8.25 0L2.75 0Z"/>
+ <path id="gap" transform="translate(4, 2)" fill="#ffffff" fill-rule="evenodd" d="M2 0L6 0L6 4L5.5 4.75L4.25 2.75L1.25 6L0 4.75L3.25 1.75L1.25 0.5Z"/>
+ </g>
+</svg>
diff --git a/apps/shorty/img/actions/info.png b/apps/shorty/img/actions/info.png
new file mode 100644
index 00000000000..2febafa33e5
--- /dev/null
+++ b/apps/shorty/img/actions/info.png
Binary files differ
diff --git a/apps/shorty/img/actions/info.svg b/apps/shorty/img/actions/info.svg
new file mode 100644
index 00000000000..e92ab4fbf0f
--- /dev/null
+++ b/apps/shorty/img/actions/info.svg
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="595.277pt"
+ height="841.891pt"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:docname="info.svg"
+ inkscape:export-filename="/srv/www/htdocs/owncloud/apps/shorty/img/actions/info.png"
+ inkscape:export-xdpi="1.4732735"
+ inkscape:export-ydpi="1.4732735">
+ <metadata
+ id="metadata11">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="640"
+ inkscape:window-height="480"
+ id="namedview9"
+ showgrid="false"
+ inkscape:zoom="0.22425706"
+ inkscape:cx="372.0481"
+ inkscape:cy="526.18188"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg2" />
+ <defs
+ id="defs4" />
+ <g
+ id="layer0"
+ transform="matrix(1,0,0,0.92242868,0,1.2000589)">
+ <g
+ id="g4314"
+ transform="translate(212.946,163.839)"
+ style="fill:none">
+ <path
+ id="path3536"
+ transform="translate(-61.9006,-148.543)"
+ d="m 0,487.048 c 12.4425,24.828 26.0633,44.836 51.6189,16.019 C 84.1849,485.573 192.482,410.026 184.704,480.779 155.218,612.203 117.891,742.51 90.9063,874.249 59.539,946.936 141.764,1009.03 222.092,959.781 308.42,926.996 381.595,875.821 456.598,828.62 445.035,807.645 436.536,777.24 408.795,806.094 371.289,821.672 291.128,891.94 272.933,836.765 298.192,694.682 351.067,556.912 382.294,415.755 414.151,350.273 353.103,270.879 270.187,326.914 169.549,367.14 86.8111,430.152 0,487.048 z M 357.158,0.174395 C 252.436,-0.955228 204.536,139.982 305.684,174.775 387.572,199.404 471.99,128.261 449.008,60.7166 441.179,25.4089 401.168,-2.47535 357.159,0.174395 z"
+ inkscape:connector-curvature="0"
+ style="fill:#000000" />
+ </g>
+ </g>
+</svg>
diff --git a/apps/shorty/img/actions/left.png b/apps/shorty/img/actions/left.png
new file mode 100644
index 00000000000..e76cf4df2ed
--- /dev/null
+++ b/apps/shorty/img/actions/left.png
Binary files differ
diff --git a/apps/shorty/img/actions/left.svg b/apps/shorty/img/actions/left.svg
new file mode 100644
index 00000000000..670f0c3069a
--- /dev/null
+++ b/apps/shorty/img/actions/left.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.8pt" height="12.8pt">
+<defs/>
+<g id="layer1">
+ <path id="path3086" transform="matrix(-1 0 0 1 14 2)" fill="#000000" d="M0 0L12 6L0 12Z"/>
+ </g>
+</svg>
diff --git a/apps/shorty/img/actions/minus.png b/apps/shorty/img/actions/minus.png
new file mode 100644
index 00000000000..72ee0f69ff2
--- /dev/null
+++ b/apps/shorty/img/actions/minus.png
Binary files differ
diff --git a/apps/shorty/img/actions/open.png b/apps/shorty/img/actions/open.png
new file mode 100644
index 00000000000..54cdd3a2a4c
--- /dev/null
+++ b/apps/shorty/img/actions/open.png
Binary files differ
diff --git a/apps/shorty/img/actions/open.svg b/apps/shorty/img/actions/open.svg
new file mode 100644
index 00000000000..46e137d73f9
--- /dev/null
+++ b/apps/shorty/img/actions/open.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon14, part of koffice: http://www.koffice.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.8pt" height="12.8pt">
+ <defs>
+ </defs>
+ <g id="layer1">
+ <path id="path3086" fill="#000000" d="M9 14L5 14L4 7L0 7L7 0L14 7L10 7Z" transform="matrix(0.704343 0.70986 -0.70986 0.704343 8.03052 -1.90816)" />
+ </g>
+</svg>
diff --git a/apps/shorty/img/actions/plus.png b/apps/shorty/img/actions/plus.png
new file mode 100644
index 00000000000..f18d9c6c31a
--- /dev/null
+++ b/apps/shorty/img/actions/plus.png
Binary files differ
diff --git a/apps/shorty/img/actions/recycle.png b/apps/shorty/img/actions/recycle.png
new file mode 100644
index 00000000000..29444fd7fd9
--- /dev/null
+++ b/apps/shorty/img/actions/recycle.png
Binary files differ
diff --git a/apps/shorty/img/actions/recycle.svg b/apps/shorty/img/actions/recycle.svg
new file mode 100644
index 00000000000..61ee3ac8409
--- /dev/null
+++ b/apps/shorty/img/actions/recycle.svg
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="119.308pt"
+ height="121.472pt"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:docname="recycle_arrows.svg">
+ <title
+ id="title2987">recycle</title>
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>recycle</dc:title>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by/3.0/" />
+ <dc:date>2012</dc:date>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Christian Reiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:rights>
+ <cc:Agent>
+ <dc:title>Copyright 2012: Christian Reiner</dc:title>
+ </cc:Agent>
+ </dc:rights>
+ <dc:description>recycle</dc:description>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by/3.0/">
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Reproduction" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Distribution" />
+ <cc:requires
+ rdf:resource="http://creativecommons.org/ns#Notice" />
+ <cc:requires
+ rdf:resource="http://creativecommons.org/ns#Attribution" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="965"
+ inkscape:window-height="679"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="1.5542677"
+ inkscape:cx="74.567497"
+ inkscape:cy="75.919998"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg2" />
+ <defs
+ id="defs4" />
+ <g
+ id="layer0"
+ transform="matrix(-1,0,0,1,149.13468,0)"
+ inkscape:export-xdpi="9.6599998"
+ inkscape:export-ydpi="9.6599998"
+ inkscape:export-filename="/home/arkascha/Downloads/icons/recycle.png">
+ <path
+ id="path1298"
+ d="M 70.8025,151.722 C 37.2549,149.448 11.1077,128.26 2.59247,96.4481 -1.14036,82.5027 -0.820931,65.3241 3.41386,52.2741 10.1512,31.5121 26.9843,13.3207 46.7093,5.48511 82.2059,-8.61567 120.257,5.14042 139.563,39.0531 c 7.34,12.8923 10.355,26.3175 9.4,41.853 -0.76,12.368 -2.629,19.5529 -7.945,30.5419 -3.422,7.074 -5.716,10.094 -13.664,18 -6.689,6.653 -11.649,10.604 -16.551,13.184 -12.0281,6.329 -27.8135,9.916 -40.0005,9.09 z m 4.5,-19.274 0.5,-7.481 5.5,-0.737 c 12.7535,-1.708 24.6295,-7.529 34.4625,-16.892 11.858,-11.2904 18.207,-24.78 19.251,-40.9052 0.739,-11.3966 -0.975,-20.6043 -5.72,-30.7354 C 122.022,20.167 104.421,6.43624 87.9323,3.42918 78.3665,1.68465 61.6527,2.66382 53.3025,5.45794 32.3445,12.4709 14.6307,28.9197 7.32185,48.1551 c -8.71433,22.9342 -5.7724,50.0896 7.63305,70.4559 4.2466,6.452 11.0409,13.477 12.0541,12.464 0.2731,-0.273 -1.1342,-3.139 -3.1273,-6.368 -6.2607,-10.143 -10.5792,-24.58 -10.5792,-35.3665 0,-5.978 2.4851,-18.5157 4.962,-25.0345 6.7044,-17.6444 24.9878,-32.6283 45.6647,-37.4235 3.7707,-0.8745 7.4223,-2.0601 8.1146,-2.6347 0.7686,-0.6379 1.2587,-3.5049 1.2587,-7.3632 0,-3.4751 0.4448,-6.5934 0.9885,-6.9294 1.3812,-0.85363 8.7657,2.5714 15.6814,7.2734 7.1284,4.8465 13.9516,12.0276 19.3896,20.4063 3.307,5.0959 3.997,6.944 3.578,9.5846 -1.425,8.9891 -17.7832,27.9223 -29.4402,34.0753 -3.4665,1.8297 -6.8857,3.1031 -7.5982,2.8297 -0.7624,-0.2926 -1.5662,-3.0878 -1.9535,-6.793 -0.3619,-3.4627 -1.0505,-6.6884 -1.5303,-7.1681 -1.2221,-1.2221 -9.6586,3.2474 -16.2231,8.5948 -9.0772,7.3942 -17.8922,20.3499 -17.8922,26.2968 0,9.067 14.0387,25.185 28.5,32.72 7.3326,3.821 7.9129,3.458 8.5,-5.327 z"
+ inkscape:connector-curvature="0"
+ style="fill:#010101" />
+ </g>
+</svg>
diff --git a/apps/shorty/img/actions/reload.png b/apps/shorty/img/actions/reload.png
new file mode 100644
index 00000000000..06df1f194e0
--- /dev/null
+++ b/apps/shorty/img/actions/reload.png
Binary files differ
diff --git a/apps/shorty/img/actions/right.png b/apps/shorty/img/actions/right.png
new file mode 100644
index 00000000000..a252a751554
--- /dev/null
+++ b/apps/shorty/img/actions/right.png
Binary files differ
diff --git a/apps/shorty/img/actions/right.svg b/apps/shorty/img/actions/right.svg
new file mode 100644
index 00000000000..997068063e3
--- /dev/null
+++ b/apps/shorty/img/actions/right.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.8pt" height="12.8pt">
+<defs/>
+<g id="layer1">
+ <path id="path3086" transform="translate(2, 2)" fill="#000000" d="M0 0L12 6L0 12Z"/>
+ </g>
+</svg>
diff --git a/apps/shorty/img/actions/shade.png b/apps/shorty/img/actions/shade.png
new file mode 100644
index 00000000000..a49b9ad34c4
--- /dev/null
+++ b/apps/shorty/img/actions/shade.png
Binary files differ
diff --git a/apps/shorty/img/actions/shade.svg b/apps/shorty/img/actions/shade.svg
new file mode 100644
index 00000000000..1823941c790
--- /dev/null
+++ b/apps/shorty/img/actions/shade.svg
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="94.337173"
+ height="94.337173"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:docname="up.svg">
+ <metadata
+ id="metadata11">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="640"
+ inkscape:window-height="480"
+ id="namedview9"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="0.65555556"
+ inkscape:cx="76.57817"
+ inkscape:cy="35.476003"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg2" />
+ <defs
+ id="defs4" />
+ <g
+ id="layer1"
+ transform="translate(-103.42183,-121.13883)">
+ <path
+ id="shape0"
+ transform="translate(132.93,152.141)"
+ d="m 0,28.8704 35.4331,0 L 17.6016,0 z"
+ inkscape:connector-curvature="0"
+ style="fill:#000000;fill-rule:evenodd;stroke:#000000;stroke-width:8.58899117;stroke-linecap:round;stroke-linejoin:round" />
+ <circle
+ id="shape1"
+ transform="translate(106.299,124.016)"
+ r="44.291416"
+ cx="44.291416"
+ cy="44.291416"
+ stroke-miterlimit="2.00000000000"
+ sodipodi:cx="44.291416"
+ sodipodi:cy="44.291416"
+ sodipodi:rx="44.291416"
+ sodipodi:ry="44.291416"
+ style="fill:none;stroke:#000000;stroke-width:5.75434065;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:2"
+ d="m 88.582832,44.291416 c 0,24.461474 -19.829942,44.291416 -44.291416,44.291416 C 19.829942,88.582832 0,68.75289 0,44.291416 0,19.829942 19.829942,0 44.291416,0 68.75289,0 88.582832,19.829942 88.582832,44.291416 z" />
+ </g>
+</svg>
diff --git a/apps/shorty/img/actions/up.png b/apps/shorty/img/actions/up.png
new file mode 100644
index 00000000000..c878f84396c
--- /dev/null
+++ b/apps/shorty/img/actions/up.png
Binary files differ
diff --git a/apps/shorty/img/actions/up.svg b/apps/shorty/img/actions/up.svg
new file mode 100644
index 00000000000..728243b3b5d
--- /dev/null
+++ b/apps/shorty/img/actions/up.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.8pt" height="12.8pt">
+<defs/>
+<g id="layer1">
+ <path id="path3086" transform="matrix(0.000287724 -1 -1 -0.000287724 13.9983 14.0016)" fill="#000000" d="M2.22045e-15 0L12 6L0 12Z"/>
+ </g>
+</svg>
diff --git a/apps/shorty/img/blank.png b/apps/shorty/img/blank.png
new file mode 100644
index 00000000000..9386bf3e17b
--- /dev/null
+++ b/apps/shorty/img/blank.png
Binary files differ
diff --git a/apps/shorty/img/calendar.png b/apps/shorty/img/calendar.png
new file mode 100644
index 00000000000..0e5fdb75bb6
--- /dev/null
+++ b/apps/shorty/img/calendar.png
Binary files differ
diff --git a/apps/shorty/img/drag_me.png b/apps/shorty/img/drag_me.png
new file mode 100644
index 00000000000..3ebac17d2d3
--- /dev/null
+++ b/apps/shorty/img/drag_me.png
Binary files differ
diff --git a/apps/shorty/img/drag_me.svg b/apps/shorty/img/drag_me.svg
new file mode 100644
index 00000000000..f1d44cdfb9d
--- /dev/null
+++ b/apps/shorty/img/drag_me.svg
@@ -0,0 +1,135 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="595.277pt" height="841.891pt">
+<defs/>
+<g id="Bubbles">
+ <ellipse id="shape0" transform="translate(162.259, 571.542)" rx="20.11097874441pt" ry="8.61849622845pt" cx="20.11097874441pt" cy="8.61849622845pt" fill="none" stroke="#989898" stroke-width="3.45827370760" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000"/>
+ <path id="shape1" transform="translate(172.543, 536.388)" fill="none" stroke="#707071" stroke-width="3.45827370760" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" d="M63.9895 13.6082C63.9895 6.09258 49.6649 4.60196e-16 31.9947 0C14.3245 -4.60196e-16 2.16398e-15 6.09258 0 13.6082C-2.16398e-15 21.1237 14.3245 27.2163 31.9947 27.2163C49.6649 27.2163 63.9895 21.1237 63.9895 13.6082"/>
+ <path id="shape2" transform="translate(238.818, 495.564)" fill="none" stroke="#535353" stroke-width="3.45827370760" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" d="M254.815 38.5564C254.815 17.2623 197.773 1.30389e-15 127.408 0C57.0423 -1.30389e-15 8.61726e-15 17.2623 0 38.5564C-8.61726e-15 59.8506 57.0423 77.1129 127.408 77.1129C197.773 77.1129 254.815 59.8506 254.815 38.5564"/>
+ </g><g id="Text">
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny">
+<title>Qt Svg Document</title>
+<desc>Generated with Qt</desc>
+<defs>
+</defs>
+<g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" >
+
+<g fill="none" stroke="none" transform="matrix(1,0,0,1,0,0)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="none" transform="matrix(1,0,0,1,-0.235294,-0.346925)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="none" transform="matrix(1,0,0,1,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="none" transform="matrix(1.25123,0,0,1.25123,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="none" transform="matrix(1.25123,0,0,1.25123,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="#1f1c1b" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1.25123,0,0,1.25123,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="#1f1c1b" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1.25123,0,0,1.25123,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="#1f1c1b" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1.25123,0,0,1.25123,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="#333333" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1.25123,0,0,1.25123,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+<text fill="#333333" fill-opacity="1" stroke="none" xml:space="preserve" x="0" y="33" font-family="Vahika" font-size="32" font-weight="700" font-style="normal"
+ >Drag</text>
+<text fill="#333333" fill-opacity="1" stroke="none" xml:space="preserve" x="59.0156" y="33" font-family="Vahika" font-size="32" font-weight="700" font-style="normal"
+ > me !</text>
+</g>
+
+<g fill="none" stroke="#1f1c1b" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1.25123,0,0,1.25123,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="#1f1c1b" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="#1f1c1b" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1.25123,0,0,1.25123,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="#1f1c1b" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1.25123,0,0,1.25123,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="none" transform="matrix(1.25123,0,0,1.25123,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="none" transform="matrix(1.25123,0,0,1.25123,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="none" transform="matrix(1.25123,0,0,1.25123,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="none" transform="matrix(1,0,0,1,0,0)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="none" transform="matrix(1,0,0,1,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="none" transform="matrix(1,0,0,1,288,508.189)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="none" transform="matrix(1,0,0,1,-0.235294,-0.346925)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="none" transform="matrix(1,0,0,1,-0.235294,-0.346925)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+
+<g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
+font-family="Sans Serif" font-size="9" font-weight="400" font-style="normal"
+>
+</g>
+</g>
+</svg>
+
+ </g>
+</svg>
diff --git a/apps/shorty/img/loading-bar.gif b/apps/shorty/img/loading-bar.gif
new file mode 100644
index 00000000000..b02798a87eb
--- /dev/null
+++ b/apps/shorty/img/loading-bar.gif
Binary files differ
diff --git a/apps/shorty/img/loading-disk.gif b/apps/shorty/img/loading-disk.gif
new file mode 100644
index 00000000000..22c961011cd
--- /dev/null
+++ b/apps/shorty/img/loading-disk.gif
Binary files differ
diff --git a/apps/shorty/img/loading-led.gif b/apps/shorty/img/loading-led.gif
new file mode 100644
index 00000000000..78893c90606
--- /dev/null
+++ b/apps/shorty/img/loading-led.gif
Binary files differ
diff --git a/apps/shorty/img/scheme/A.png b/apps/shorty/img/scheme/A.png
new file mode 100644
index 00000000000..65163556bab
--- /dev/null
+++ b/apps/shorty/img/scheme/A.png
Binary files differ
diff --git a/apps/shorty/img/scheme/B.png b/apps/shorty/img/scheme/B.png
new file mode 100644
index 00000000000..a5562488824
--- /dev/null
+++ b/apps/shorty/img/scheme/B.png
Binary files differ
diff --git a/apps/shorty/img/scheme/C.png b/apps/shorty/img/scheme/C.png
new file mode 100644
index 00000000000..61b873de73f
--- /dev/null
+++ b/apps/shorty/img/scheme/C.png
Binary files differ
diff --git a/apps/shorty/img/scheme/D.png b/apps/shorty/img/scheme/D.png
new file mode 100644
index 00000000000..6ab7451d04a
--- /dev/null
+++ b/apps/shorty/img/scheme/D.png
Binary files differ
diff --git a/apps/shorty/img/scheme/E.png b/apps/shorty/img/scheme/E.png
new file mode 100644
index 00000000000..d9c4e07001b
--- /dev/null
+++ b/apps/shorty/img/scheme/E.png
Binary files differ
diff --git a/apps/shorty/img/scheme/F.png b/apps/shorty/img/scheme/F.png
new file mode 100644
index 00000000000..2663b5eb308
--- /dev/null
+++ b/apps/shorty/img/scheme/F.png
Binary files differ
diff --git a/apps/shorty/img/scheme/G.png b/apps/shorty/img/scheme/G.png
new file mode 100644
index 00000000000..d1d965d34cb
--- /dev/null
+++ b/apps/shorty/img/scheme/G.png
Binary files differ
diff --git a/apps/shorty/img/scheme/H.png b/apps/shorty/img/scheme/H.png
new file mode 100644
index 00000000000..c644a77bfc5
--- /dev/null
+++ b/apps/shorty/img/scheme/H.png
Binary files differ
diff --git a/apps/shorty/img/scheme/J.png b/apps/shorty/img/scheme/J.png
new file mode 100644
index 00000000000..0eaa189f4f6
--- /dev/null
+++ b/apps/shorty/img/scheme/J.png
Binary files differ
diff --git a/apps/shorty/img/scheme/K.png b/apps/shorty/img/scheme/K.png
new file mode 100644
index 00000000000..3d587ae4478
--- /dev/null
+++ b/apps/shorty/img/scheme/K.png
Binary files differ
diff --git a/apps/shorty/img/scheme/L.png b/apps/shorty/img/scheme/L.png
new file mode 100644
index 00000000000..4e37b9f3645
--- /dev/null
+++ b/apps/shorty/img/scheme/L.png
Binary files differ
diff --git a/apps/shorty/img/scheme/M.png b/apps/shorty/img/scheme/M.png
new file mode 100644
index 00000000000..a4a02e60b40
--- /dev/null
+++ b/apps/shorty/img/scheme/M.png
Binary files differ
diff --git a/apps/shorty/img/scheme/N.png b/apps/shorty/img/scheme/N.png
new file mode 100644
index 00000000000..6c860f77347
--- /dev/null
+++ b/apps/shorty/img/scheme/N.png
Binary files differ
diff --git a/apps/shorty/img/scheme/O.png b/apps/shorty/img/scheme/O.png
new file mode 100644
index 00000000000..14a05847b5c
--- /dev/null
+++ b/apps/shorty/img/scheme/O.png
Binary files differ
diff --git a/apps/shorty/img/scheme/P.png b/apps/shorty/img/scheme/P.png
new file mode 100644
index 00000000000..e53d3ee9801
--- /dev/null
+++ b/apps/shorty/img/scheme/P.png
Binary files differ
diff --git a/apps/shorty/img/scheme/Q.png b/apps/shorty/img/scheme/Q.png
new file mode 100644
index 00000000000..d07c42a76de
--- /dev/null
+++ b/apps/shorty/img/scheme/Q.png
Binary files differ
diff --git a/apps/shorty/img/scheme/R.png b/apps/shorty/img/scheme/R.png
new file mode 100644
index 00000000000..9b522d6c5a0
--- /dev/null
+++ b/apps/shorty/img/scheme/R.png
Binary files differ
diff --git a/apps/shorty/img/scheme/S.png b/apps/shorty/img/scheme/S.png
new file mode 100644
index 00000000000..1aff52f7525
--- /dev/null
+++ b/apps/shorty/img/scheme/S.png
Binary files differ
diff --git a/apps/shorty/img/scheme/T.png b/apps/shorty/img/scheme/T.png
new file mode 100644
index 00000000000..1736eedc47b
--- /dev/null
+++ b/apps/shorty/img/scheme/T.png
Binary files differ
diff --git a/apps/shorty/img/scheme/U.png b/apps/shorty/img/scheme/U.png
new file mode 100644
index 00000000000..320938cedf1
--- /dev/null
+++ b/apps/shorty/img/scheme/U.png
Binary files differ
diff --git a/apps/shorty/img/scheme/V.png b/apps/shorty/img/scheme/V.png
new file mode 100644
index 00000000000..2fe5e5a1621
--- /dev/null
+++ b/apps/shorty/img/scheme/V.png
Binary files differ
diff --git a/apps/shorty/img/scheme/W.png b/apps/shorty/img/scheme/W.png
new file mode 100644
index 00000000000..e7b37b011f2
--- /dev/null
+++ b/apps/shorty/img/scheme/W.png
Binary files differ
diff --git a/apps/shorty/img/scheme/X.png b/apps/shorty/img/scheme/X.png
new file mode 100644
index 00000000000..4a3d8bd3816
--- /dev/null
+++ b/apps/shorty/img/scheme/X.png
Binary files differ
diff --git a/apps/shorty/img/scheme/Y.png b/apps/shorty/img/scheme/Y.png
new file mode 100644
index 00000000000..af76045e897
--- /dev/null
+++ b/apps/shorty/img/scheme/Y.png
Binary files differ
diff --git a/apps/shorty/img/scheme/Z.png b/apps/shorty/img/scheme/Z.png
new file mode 100644
index 00000000000..0d00ced9c14
--- /dev/null
+++ b/apps/shorty/img/scheme/Z.png
Binary files differ
diff --git a/apps/shorty/img/shorty.png b/apps/shorty/img/shorty.png
new file mode 100644
index 00000000000..3fa8a55f9d8
--- /dev/null
+++ b/apps/shorty/img/shorty.png
Binary files differ
diff --git a/apps/shorty/img/shorty.svg b/apps/shorty/img/shorty.svg
new file mode 100644
index 00000000000..79b2ee69015
--- /dev/null
+++ b/apps/shorty/img/shorty.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.8pt" height="12.8pt">
+<defs/>
+<g id="g4185">
+ <path id="path4189" transform="translate(-0.0314065, 3.97605)" fill="#d3d3d3" d="M5.90496 5.36366e-05L2.03624 0.0483836C0.896812 0.0603836 -0.00985361 2.07369 8.08645e-05 4.5713C0.0100142 7.06891 0.928138 9.07722 2.06757 9.06522L5.93628 9.01689C6.91266 9.00662 7.71351 7.51489 7.91762 5.5184L8.09775 5.5184C8.29805 7.56949 9.13062 9.09502 10.1261 9.08454L13.9948 9.04589C15.1342 9.03389 16.0409 7.01093 16.031 4.51331C16.021 2.0157 15.1029 0.0170616 13.9635 0.0290566L10.0869 0.0677066C9.14538 0.0776066 8.36671 1.46464 8.12907 3.35358L7.90196 3.35358C7.66674 1.41099 6.86557 -0.0100594 5.90496 5.36366e-05ZM4.99651 1.48836C5.86074 1.48036 6.59686 2.24823 6.90737 3.35358L4.47181 3.35358C4.19414 3.35358 3.9706 3.83634 3.9706 4.43599C3.9706 5.03564 4.19414 5.5184 4.47181 5.5184L6.95436 5.5184C6.67695 6.7007 5.92436 7.54932 5.02784 7.55758L2.99951 7.57691C1.86007 7.58743 0.931832 6.24436 0.924186 4.56163C0.916536 2.8789 1.82875 1.50853 2.96818 1.49803L4.99651 1.48843ZM13.055 1.51735C14.1945 1.50686 15.1149 2.84023 15.1225 4.52297C15.1301 6.2057 14.218 7.57607 13.0785 7.58656L11.0502 7.59616C10.1318 7.60466 9.35598 6.73612 9.0845 5.51833L11.4887 5.51833C11.7664 5.51833 11.99 5.03557 11.99 4.43592C11.99 3.83627 11.7664 3.35351 11.4887 3.35351L9.15499 3.35351C9.46901 2.2831 10.1849 1.53477 11.0267 1.52702L13.055 1.51742Z"/>
+ </g><g id="g4849">
+ <path id="path4851" transform="translate(-8.08646e-05, 2.9323)" fill="#505053" fill-opacity="0.95294117647" d="M5.90496 5.36369e-05L2.03624 0.0483836C0.896812 0.0603756 -0.00985362 2.07368 8.08646e-05 4.5713C0.0100142 7.06891 0.928138 9.07721 2.06757 9.06522L5.93628 9.01689C6.91266 9.00662 7.71351 7.51488 7.91762 5.51839L8.09775 5.51839C8.29805 7.56948 9.13062 9.09502 10.1261 9.08454L13.9948 9.04589C15.1342 9.03389 16.0409 7.01092 16.031 4.51331C16.0211 2.01569 15.1029 0.0170526 13.9635 0.0290466L10.0869 0.0676976C9.14538 0.0775976 8.36671 1.46464 8.12907 3.35358L7.90196 3.35358C7.66674 1.41098 6.86557 -0.0100594 5.90496 5.36369e-05ZM4.99651 1.48836C5.86074 1.48036 6.59686 2.24823 6.90737 3.35358L4.47181 3.35358C4.19414 3.35358 3.9706 3.83634 3.9706 4.43599C3.9706 5.03564 4.19414 5.5184 4.47181 5.5184L6.95436 5.5184C6.67695 6.7007 5.92436 7.54931 5.02784 7.55758L2.99951 7.5769C1.86007 7.5874 0.931832 6.24436 0.924186 4.56162C0.91654 2.87889 1.82875 1.50853 2.96818 1.49803L4.99651 1.48843ZM13.055 1.51735C14.1945 1.50686 15.1149 2.84024 15.1225 4.52297C15.1301 6.2057 14.218 7.57607 13.0785 7.58657L11.0502 7.59617C10.1318 7.60467 9.35598 6.73612 9.0845 5.51833L11.4887 5.51833C11.7664 5.51833 11.99 5.03558 11.99 4.43592C11.99 3.83627 11.7664 3.35352 11.4887 3.35352L9.15499 3.35352C9.46901 2.28304 10.1849 1.53471 11.0267 1.52695Z"/>
+ </g>
+</svg>
diff --git a/apps/shorty/img/status/active.png b/apps/shorty/img/status/active.png
new file mode 100644
index 00000000000..51d013cd058
--- /dev/null
+++ b/apps/shorty/img/status/active.png
Binary files differ
diff --git a/apps/shorty/img/status/active.svg b/apps/shorty/img/status/active.svg
new file mode 100644
index 00000000000..bb4c0573af1
--- /dev/null
+++ b/apps/shorty/img/status/active.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="75.4697pt" height="75.4697pt">
+<defs/>
+<g id="layer1">
+ <circle id="shape1" transform="translate(2.87717, 2.87717)" r="35.43313280000pt" cx="35.43313280000pt" cy="35.43313280000pt" fill="none" stroke="#000000" stroke-width="5.75434065000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000"/>
+ </g>
+</svg>
diff --git a/apps/shorty/img/status/bad.png b/apps/shorty/img/status/bad.png
new file mode 100644
index 00000000000..11627a21fee
--- /dev/null
+++ b/apps/shorty/img/status/bad.png
Binary files differ
diff --git a/apps/shorty/img/status/good.png b/apps/shorty/img/status/good.png
new file mode 100644
index 00000000000..08ce0a4453a
--- /dev/null
+++ b/apps/shorty/img/status/good.png
Binary files differ
diff --git a/apps/shorty/img/status/neutral.png b/apps/shorty/img/status/neutral.png
new file mode 100644
index 00000000000..5b2ad589cb9
--- /dev/null
+++ b/apps/shorty/img/status/neutral.png
Binary files differ
diff --git a/apps/shorty/img/status/strike.png b/apps/shorty/img/status/strike.png
new file mode 100644
index 00000000000..d9a867fa004
--- /dev/null
+++ b/apps/shorty/img/status/strike.png
Binary files differ
diff --git a/apps/shorty/img/usage/64/arrow.png b/apps/shorty/img/usage/64/arrow.png
new file mode 100644
index 00000000000..ac35f78ad1c
--- /dev/null
+++ b/apps/shorty/img/usage/64/arrow.png
Binary files differ
diff --git a/apps/shorty/img/usage/64/clipboard.png b/apps/shorty/img/usage/64/clipboard.png
new file mode 100644
index 00000000000..701bb6a7796
--- /dev/null
+++ b/apps/shorty/img/usage/64/clipboard.png
Binary files differ
diff --git a/apps/shorty/img/usage/64/email.png b/apps/shorty/img/usage/64/email.png
new file mode 100644
index 00000000000..cfa1f767821
--- /dev/null
+++ b/apps/shorty/img/usage/64/email.png
Binary files differ
diff --git a/apps/shorty/img/usage/64/globe.png b/apps/shorty/img/usage/64/globe.png
new file mode 100644
index 00000000000..2d41da4819a
--- /dev/null
+++ b/apps/shorty/img/usage/64/globe.png
Binary files differ
diff --git a/apps/shorty/img/usage/64/home.png b/apps/shorty/img/usage/64/home.png
new file mode 100644
index 00000000000..8cff03b28c6
--- /dev/null
+++ b/apps/shorty/img/usage/64/home.png
Binary files differ
diff --git a/apps/shorty/img/usage/64/info.png b/apps/shorty/img/usage/64/info.png
new file mode 100644
index 00000000000..2069088f64a
--- /dev/null
+++ b/apps/shorty/img/usage/64/info.png
Binary files differ
diff --git a/apps/shorty/img/usage/64/locked.png b/apps/shorty/img/usage/64/locked.png
new file mode 100644
index 00000000000..841d2d4483f
--- /dev/null
+++ b/apps/shorty/img/usage/64/locked.png
Binary files differ
diff --git a/apps/shorty/img/usage/64/network.png b/apps/shorty/img/usage/64/network.png
new file mode 100644
index 00000000000..fac47ac7b75
--- /dev/null
+++ b/apps/shorty/img/usage/64/network.png
Binary files differ
diff --git a/apps/shorty/img/usage/64/qrcode.png b/apps/shorty/img/usage/64/qrcode.png
new file mode 100644
index 00000000000..207741888d3
--- /dev/null
+++ b/apps/shorty/img/usage/64/qrcode.png
Binary files differ
diff --git a/apps/shorty/img/usage/64/sms.png b/apps/shorty/img/usage/64/sms.png
new file mode 100644
index 00000000000..5dcd2c12231
--- /dev/null
+++ b/apps/shorty/img/usage/64/sms.png
Binary files differ
diff --git a/apps/shorty/img/usage/64/team.png b/apps/shorty/img/usage/64/team.png
new file mode 100644
index 00000000000..e6ea3375807
--- /dev/null
+++ b/apps/shorty/img/usage/64/team.png
Binary files differ
diff --git a/apps/shorty/img/usage/64/unlocked.png b/apps/shorty/img/usage/64/unlocked.png
new file mode 100644
index 00000000000..54501bbd11f
--- /dev/null
+++ b/apps/shorty/img/usage/64/unlocked.png
Binary files differ
diff --git a/apps/shorty/img/usage/64/workstation.png b/apps/shorty/img/usage/64/workstation.png
new file mode 100644
index 00000000000..909098c9d8d
--- /dev/null
+++ b/apps/shorty/img/usage/64/workstation.png
Binary files differ
diff --git a/apps/shorty/img/usage/_.svg b/apps/shorty/img/usage/_.svg
new file mode 100644
index 00000000000..e0ef15d83c3
--- /dev/null
+++ b/apps/shorty/img/usage/_.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133.098pt" height="134.778pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="matrix(0.935714 0 0 0.971429 -260.198 309.044)" gradientUnits="objectBoundingBox" x1="1.86062897958" y1="-2.28811738018" x2="1.88918016404" y2="-1.09199169808" spreadMethod="pad">
+ <stop stop-color="#edf4fd" offset="0.00000000000" stop-opacity="0.52845044633"/>
+ <stop stop-color="#175bb3" offset="1.00000000000" stop-opacity="0.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="layer1">
+ <g id="g5381" transform="translate(-5.94477e-06, 2.56921e-06)" fill="none">
+ <path id="path4167" transform="matrix(0.758527 0 0 0.768105 -8.17197e-15 -3.02746e-16)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path4169" transform="matrix(0.709764 0 0 0.736855 5.34768 4.47751)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <g id="text4182" transform="translate(66.8213, 30.5499)" fill="none"/>
+ <path id="path4171" transform="matrix(0.758527 0 0 0.758527 5.33866 4.46647)" fill="url(#gradient0)" d="M102.625 0C45.9507 0 0 47.725 0 106.562C0 110.315 0.1979 114.004 0.5625 117.656C50.6291 136.382 127.144 150.025 205.25 107.062C205.251 106.896 205.25 106.73 205.25 106.562C205.25 47.725 159.299 0 102.625 0Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/apps/shorty/img/usage/arrow.svg b/apps/shorty/img/usage/arrow.svg
new file mode 100644
index 00000000000..94b2bf96abe
--- /dev/null
+++ b/apps/shorty/img/usage/arrow.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133.098pt" height="134.778pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="matrix(0.935714 0 0 0.971429 -260.198 309.044)" gradientUnits="objectBoundingBox" x1="1.86062897958" y1="-2.28811738018" x2="1.88918016404" y2="-1.09199169808" spreadMethod="pad">
+ <stop stop-color="#edf4fd" offset="0.00000000000" stop-opacity="0.52845044633"/>
+ <stop stop-color="#175bb3" offset="1.00000000000" stop-opacity="0.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="layer1">
+ <g id="g5327" transform="translate(5.00882e-07, -1.00277e-06)" fill="none">
+ <path id="path4155" transform="matrix(0.758527 0 0 0.768105 -6.61744e-23 0)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path4157" transform="matrix(0.709764 0 0 0.736855 5.34768 4.47751)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <g id="g4149" transform="translate(32.6557, 32.5571)" fill="none">
+ <path id="path4138" transform="matrix(-0.168373 -0.438415 0.36134 -0.202146 27.838 126.426)" fill="#eeeeee" d="M160.243 0.00621995C156.111 0.109866 152.106 1.44866 148.743 3.84997L8.68012 103.631C2.66423 107.918 -0.608526 115.089 0.0939771 122.442C0.79648 129.796 5.36759 136.217 12.0864 139.287L168.524 210.694C175.246 213.766 183.097 213.018 189.118 208.731C195.139 204.444 198.415 197.27 197.711 189.912L181.336 18.725C180.308 7.91607 171.097 -0.257837 160.243 0.00621995Z"/>
+ <rect id="rect4143" transform="matrix(0.624447 0 0 0.610576 81.3708 26.8172)" fill="#eeeeee" width="16.29353760000pt" height="62.66744960000pt" rx="8.14676880000pt" ry="15.60399600000pt"/>
+ </g>
+ <path id="path4159" transform="matrix(0.758527 0 0 0.758527 5.33866 4.46647)" fill="url(#gradient0)" d="M102.625 0C45.9507 0 0 47.725 0 106.562C0 110.315 0.1979 114.004 0.5625 117.656C50.6291 136.382 127.144 150.025 205.25 107.062C205.251 106.896 205.25 106.73 205.25 106.562C205.25 47.725 159.299 0 102.625 0Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/apps/shorty/img/usage/clipboard.svg b/apps/shorty/img/usage/clipboard.svg
new file mode 100644
index 00000000000..a470bdaae45
--- /dev/null
+++ b/apps/shorty/img/usage/clipboard.svg
@@ -0,0 +1,34 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133.098pt" height="134.778pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="matrix(0.935714 0 0 0.971429 -260.198 309.044)" gradientUnits="objectBoundingBox" x1="1.86062897958" y1="-2.28811738018" x2="1.88918016404" y2="-1.09199169808" spreadMethod="pad">
+ <stop stop-color="#edf4fd" offset="0.00000000000" stop-opacity="0.52845044633"/>
+ <stop stop-color="#175bb3" offset="1.00000000000" stop-opacity="0.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="layer1">
+ <g id="g5381" transform="translate(-5.94477e-06, 2.56921e-06)" fill="none">
+ <path id="path4167" transform="matrix(0.758527 0 0 0.768105 -8.17197e-15 -3.02746e-16)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path4169" transform="matrix(0.709764 0 0 0.736855 5.34768 4.47751)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <g id="text4182" transform="translate(66.8213, 30.5499)" fill="none"/>
+ <path id="path4171" transform="matrix(0.758527 0 0 0.758527 5.33866 4.46647)" fill="url(#gradient0)" d="M102.625 0C45.9507 0 0 47.725 0 106.562C0 110.315 0.1979 114.004 0.5625 117.656C50.6291 136.382 127.144 150.025 205.25 107.062C205.251 106.896 205.25 106.73 205.25 106.562C205.25 47.725 159.299 0 102.625 0Z"/>
+ </g>
+ <g id="g4549" transform="translate(42.322, 7.02379)" fill="none">
+ <g id="g4542" transform="translate(-3.5, 49.2276)" fill="none">
+ <rect id="rect812" transform="translate(3.5, 3.5)" fill="none" stroke="#ffffff" stroke-width="7.00000000000" stroke-linecap="butt" stroke-linejoin="round" width="52.59049680000pt" height="52.21751680000pt" rx="2.07274704000pt" ry="2.07274704000pt"/>
+ <path id="path842" transform="translate(10.9597, 16.8727)" fill="none" stroke="#ffffff" stroke-width="7.00000000000" stroke-linecap="round" stroke-linejoin="round" d="M0 0L49.8864 0"/>
+ <path id="path845" transform="translate(10.9597, 29.7461)" fill="none" stroke="#ffffff" stroke-width="7.00000000000" stroke-linecap="round" stroke-linejoin="round" d="M0 0L49.8864 0"/>
+ <path id="path846" transform="translate(10.9597, 42.6194)" fill="none" stroke="#ffffff" stroke-width="7.00000000000" stroke-linecap="round" stroke-linejoin="round" d="M0 0L49.8864 0"/>
+ <path id="path848" transform="translate(10.8847, 55.4927)" fill="none" stroke="#ffffff" stroke-width="7.00000000000" stroke-linecap="round" stroke-linejoin="round" d="M0 0L27.6572 0"/>
+ </g>
+ <g id="g886" transform="translate(24.5258, 32.1333)" fill="none">
+ <path id="path599" transform="translate(1.56892, 48.6283)" fill="#ffffff" stroke="#b3b3b3" stroke-width="3.13784003000" stroke-linecap="round" stroke-linejoin="round" d="M16.3475 5.47447C0 17.0594 0 17.0594 0 17.0594L10.0728 0Z"/>
+ <rect id="rect598" transform="matrix(-0.911191 -0.824059 0.824059 -0.911191 17.3856 54.0244)" fill="#ffffff" stroke="#b3b3b3" stroke-width="3.13783836000" stroke-linecap="round" stroke-linejoin="round" width="5.23702200000pt" height="37.70656160000pt"/>
+ <path id="path600" transform="translate(4.47967, 60.1754)" fill="#ffffff" stroke="#b3b3b3" stroke-width="2.31749964000" stroke-linecap="butt" stroke-linejoin="round" d="M0.264548 0.337176C0.643913 -0.0822999 1.2915 -0.114817 1.71098 0.264548C2.13045 0.643913 2.16297 1.2915 1.7836 1.71098C1.40424 2.13045 0.756651 2.16297 0.337175 1.7836C-0.0823005 1.40424 -0.114816 0.756651 0.264548 0.337176Z"/>
+ <rect id="rect601" transform="matrix(-0.911191 -0.824059 0.824059 -0.911191 56.463 11.5345)" fill="#ffffff" stroke="#b3b3b3" stroke-width="3.13783836000" stroke-linecap="butt" stroke-linejoin="round" width="5.97020528000pt" height="3.24695360000pt" rx="1.64633232000pt" ry="1.62347680000pt"/>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/apps/shorty/img/usage/email.svg b/apps/shorty/img/usage/email.svg
new file mode 100644
index 00000000000..7283939e37a
--- /dev/null
+++ b/apps/shorty/img/usage/email.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133.098pt" height="134.778pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="matrix(0.935714 0 0 0.971429 -260.198 309.044)" gradientUnits="objectBoundingBox" x1="1.86062868749" y1="-2.28811738018" x2="1.88917986746" y2="-1.09199169808" spreadMethod="pad">
+ <stop stop-color="#edf4fd" offset="0.00000000000" stop-opacity="0.52845044633"/>
+ <stop stop-color="#175bb3" offset="1.00000000000" stop-opacity="0.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="layer1">
+ <g id="g5473" transform="translate(1.28609e-06, 3.64259e-06)" fill="none">
+ <path id="path5063" transform="matrix(0.758527 0 0 0.768105 -2.64698e-22 0)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path5065" transform="matrix(0.709764 0 0 0.736855 5.34768 4.47751)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <g id="text5197" transform="translate(30.5827, 32.9153)" fill="none">
+ <path id="path5202" transform="matrix(0.794213 0 0 0.724445 -1.42109e-13 4.44089e-15)" fill="#eeeeee" d="M80.7728 37.0436L82.4783 29.4711L100.488 29.4711L90.119 78.3851C89.4822 81.4778 89.1638 83.5244 89.1639 84.5249C89.1638 85.571 89.4139 86.3896 89.9143 86.9808C90.46 87.5721 91.0967 87.8677 91.8245 87.8677C94.2803 87.8677 97.4412 86.2759 101.307 83.0923C105.218 79.8632 108.607 75.4971 111.472 69.994C114.382 64.4454 115.838 58.3966 115.838 51.8474C115.838 46.3898 114.792 41.2506 112.7 36.4296C110.653 31.6088 107.674 27.4246 103.763 23.8771C99.8516 20.3297 95.008 17.5554 89.2321 15.5542C83.5015 13.5532 76.9979 12.5526 69.7211 12.5525C62.3078 12.5526 55.5085 13.5304 49.3233 15.486C43.1379 17.3962 37.703 20.1705 33.0186 23.8089C28.3341 27.4019 24.4228 31.8589 21.2847 37.18C18.5104 41.8191 16.3956 46.9583 14.9402 52.5978C13.4849 58.2374 12.7572 63.9906 12.7572 69.8575C12.7572 77.6802 14.3035 84.8433 17.3962 91.3469C20.5343 97.8051 25.0596 103.217 30.972 107.583C36.9299 111.995 44.1157 115.11 52.5296 116.93C60.9434 118.794 70.4487 119.181 81.0457 118.089C89.4594 117.089 96.5771 115.179 102.399 112.359C108.265 109.539 113.223 105.582 117.271 100.488L131.802 100.488C129.436 105.355 126.344 109.698 122.524 113.519C118.749 117.339 114.314 120.545 109.221 123.138C104.127 125.775 98.419 127.777 92.0974 129.141C85.7755 130.505 78.8853 131.188 71.4266 131.188C60.2384 131.188 50.1646 129.709 41.205 126.753C32.2454 123.843 24.6957 119.613 18.5559 114.064C12.4616 108.561 7.84532 101.989 4.7072 94.3486C1.56906 86.6625 -9.99995e-06 78.1805 4.79758e-11 68.9025C-9.99995e-06 59.2152 1.61454 50.1647 4.84364 41.7508C8.07272 33.337 12.7117 26.0375 18.7606 19.8521C24.8549 13.6214 32.2682 8.75503 41.0004 5.25296C49.778 1.75109 59.4425 0.0001 69.994 0C78.6352 0.0001 86.5487 1.27355 93.7347 3.82034C100.966 6.32184 107.151 9.93751 112.291 14.6674C117.43 19.3974 121.341 25.0142 124.024 31.5178C126.753 37.976 128.117 45.1164 128.118 52.9389C128.117 59.0333 127.026 65.0139 124.843 70.8809C122.66 76.7023 119.522 81.9553 115.429 86.6397C111.381 91.2787 106.605 94.9626 101.102 97.6914C95.5992 100.375 89.6641 101.716 83.297 101.716C79.613 101.716 76.7477 101.102 74.7012 99.8745C72.6545 98.601 71.2901 96.5999 70.608 93.8711C66.6511 97.7824 62.1031 100.261 56.9639 101.307C51.8246 102.308 46.8445 101.83 42.0237 99.8745C37.2027 97.8733 33.2915 94.5305 30.2898 89.8461C27.3336 85.1616 25.8555 79.3857 25.8555 72.5181C25.8555 65.3778 27.379 58.3511 30.4262 51.4381C33.5189 44.5251 37.8849 38.8629 43.5245 34.4512C49.2095 30.0397 55.6222 27.8339 62.7627 27.8339C70.949 27.8339 76.9524 30.9038 80.7728 37.0436M43.4563 72.3817C43.4563 77.8393 44.5933 82.0235 46.8673 84.9342C49.1413 87.845 52.1202 89.3003 55.8042 89.3003C60.0793 89.3003 63.8314 87.5948 67.0605 84.1838C70.335 80.7728 72.8137 76.4749 74.4965 71.2902C76.1792 66.06 77.0206 61.0117 77.0207 56.1453C77.0206 53.7349 76.725 51.5746 76.1338 49.6643C75.588 47.7088 74.7466 46.0032 73.6097 44.5478C72.5181 43.0925 71.1764 42.001 69.5847 41.2733C68.0383 40.5002 66.2646 40.1136 64.2635 40.1135C60.2612 40.1136 56.6455 41.6826 53.4165 44.8207C50.2328 47.9134 47.7769 51.9611 46.0487 56.9639C44.3204 61.9213 43.4563 67.0606 43.4563 72.3817"/>
+ </g>
+ <path id="path5067" transform="matrix(0.758527 0 0 0.758527 5.33866 4.46647)" fill="url(#gradient0)" d="M102.625 0C45.9507 0 0 47.725 0 106.562C0 110.315 0.1979 114.004 0.5625 117.656C50.6291 136.382 127.144 150.025 205.25 107.062C205.251 106.896 205.25 106.73 205.25 106.562C205.25 47.725 159.299 0 102.625 0Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/apps/shorty/img/usage/globe.svg b/apps/shorty/img/usage/globe.svg
new file mode 100644
index 00000000000..018f161dbf4
--- /dev/null
+++ b/apps/shorty/img/usage/globe.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133.098pt" height="134.778pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="matrix(0.935714 0 0 0.971429 -260.198 309.044)" gradientUnits="objectBoundingBox" x1="1.86062897958" y1="-2.28811733465" x2="1.88918016404" y2="-1.09199167635" spreadMethod="pad">
+ <stop stop-color="#edf4fd" offset="0.00000000000" stop-opacity="0.52845044633"/>
+ <stop stop-color="#175bb3" offset="1.00000000000" stop-opacity="0.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="layer1">
+ <g id="g5362" transform="translate(-3.40191e-06, 5.08678e-06)" fill="none">
+ <path id="path4239" transform="matrix(0.758527 0 0 0.768105 5.29396e-22 0)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path4241" transform="matrix(0.709764 0 0 0.736855 5.34768 4.47748)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path244" transform="matrix(0.758527 0 0 0.758527 23.1973 27.4649)" fill="#eeeeee" d="M80.5927 0.0948932C60.0949 -0.911807 40.4347 6.11839 25.2406 19.8968C10.0239 33.6889 1.10091 52.5655 0.0948972 73.0487C-0.911853 93.5465 6.11847 113.207 19.9112 128.409C33.6819 143.61 52.5585 152.533 73.0563 153.54C115.377 155.618 151.469 122.891 153.547 80.5855C155.625 38.2798 122.913 2.17339 80.5927 0.0948932ZM141.304 79.9841C140.952 87.1558 139.42 93.9908 136.945 100.317C131.792 99.6461 126.706 98.7947 121.689 97.7487C126.836 85.2521 128.594 71.5547 126.644 57.7998C124.785 44.717 119.689 32.8544 112.313 22.9264C130.73 35.0912 142.463 56.3835 141.304 79.9841ZM92.0321 26.1704C79.4068 40.6691 68.6314 56.7845 60.0807 74.2049C52.8482 70.0132 45.8857 65.4019 39.2203 60.4163C44.6575 50.0833 52.8087 41.3948 63.0583 35.2373C72.1673 29.7501 82.0974 26.8121 92.0321 26.1704ZM54.9821 85.4129C49.529 98.3126 45.2546 111.857 42.3271 125.908C41.8765 125.226 41.4339 124.529 41.0136 123.826C32.1223 109.048 29.5275 91.6818 33.686 74.9699C33.9167 74.0056 34.1893 73.0873 34.4639 72.1252C41.0431 76.9231 47.8895 81.3601 54.9821 85.4129ZM59.6738 132.526C57.583 132.827 55.4987 132.996 53.4253 133.092C56.1068 118.523 60.2937 104.519 65.8325 91.2201C77.7539 97.1755 90.2767 102.089 103.311 105.84C102.95 106.335 102.586 106.868 102.209 107.377C91.8412 121.166 76.7464 130.085 59.6738 132.526ZM70.8853 80.0467C79.4919 62.3869 90.5152 46.1516 103.52 31.6862C109.148 39.7311 113.028 49.1426 114.5 59.5069C116.233 71.7252 114.484 83.8752 109.573 94.8576C96.0784 91.2018 83.1341 86.2087 70.8853 80.0467ZM33.472 28.994C46.248 17.4001 62.7647 11.4919 79.9914 12.338C83.8846 12.5292 87.6863 13.0901 91.3568 13.9305C79.4801 14.6823 67.6153 18.1782 56.7378 24.723C45.2255 31.6472 35.9361 41.2556 29.501 52.6399C26.5147 50.1165 23.5977 47.5232 20.7443 44.8302C24.0834 38.9789 28.3438 33.6351 33.472 28.994ZM12.3453 73.6503C12.6332 67.7886 13.7123 62.0978 15.4974 56.669C18.3272 59.2435 21.2192 61.7475 24.1803 64.1891C23.2561 66.7258 22.4531 69.3346 21.7789 72.0084C18.4881 85.2417 18.776 98.7975 22.4326 111.573C15.2371 100.355 11.6786 87.2253 12.3453 73.6503ZM75.343 141.335C89.8738 136.43 102.603 127.284 112.015 114.74C113.396 112.9 114.692 111.006 115.896 109.055C120.881 110.158 125.911 111.094 131.015 111.873C119.175 130.1 98.4601 141.884 75.343 141.335Z"/>
+ <path id="path4243" transform="matrix(0.758527 0 0 0.758527 5.33865 4.46648)" fill="url(#gradient0)" d="M102.625 0C45.9507 0 0 47.725 0 106.562C0 110.315 0.1979 114.004 0.5625 117.656C50.6291 136.382 127.144 150.025 205.25 107.062C205.251 106.896 205.25 106.73 205.25 106.562C205.25 47.725 159.299 -1.13687e-13 102.625 0Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/apps/shorty/img/usage/home.svg b/apps/shorty/img/usage/home.svg
new file mode 100644
index 00000000000..8c29644503c
--- /dev/null
+++ b/apps/shorty/img/usage/home.svg
@@ -0,0 +1,23 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133.098pt" height="134.778pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="matrix(0.935714 0 0 0.971429 -260.198 309.044)" gradientUnits="objectBoundingBox" x1="1.86062897958" y1="-2.28811738018" x2="1.88918016404" y2="-1.09199169808" spreadMethod="pad">
+ <stop stop-color="#edf4fd" offset="0.00000000000" stop-opacity="0.52845044633"/>
+ <stop stop-color="#175bb3" offset="1.00000000000" stop-opacity="0.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="layer1">
+ <g id="g5314" transform="translate(-8.17503e-06, -2.94544e-06)" fill="none">
+ <path id="path4087" transform="matrix(0.758527 0 0 0.768105 1.05879e-21 0)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path4089" transform="matrix(0.709764 0 0 0.736855 5.34768 4.47751)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <g id="g4126" transform="translate(27.3419, 27.8174)" fill="none">
+ <path id="path4109" transform="matrix(0.646168 0 0 0.646168 2.22045e-14 1.77636e-14)" fill="#eeeeee" d="M83.9733 0.0146219C81.7822 0.0969449 79.6354 0.511418 77.6608 1.23337C74.7217 2.30249 72.2731 4.0111 70.6295 6.13962L2.22329 94.9209C-0.141455 97.987 -0.646597 101.673 0.836575 105.04C2.31975 108.407 5.648 111.13 9.97329 112.515L71.4108 32.7959C73.0479 30.6558 75.4972 28.9358 78.442 27.8584C86.1818 25.036 95.4931 27.2205 99.7545 32.8584L160.005 112.39C163.905 110.818 166.796 108.12 168.021 104.912C169.245 101.704 168.698 98.2572 166.505 95.3584L98.9733 6.23337C95.9221 2.19242 90.1245 -0.211173 83.9733 0.0146219Z"/>
+ <path id="rect4116" transform="matrix(0.646168 0 0 0.646168 20.0253 26.3303)" fill="#eeeeee" d="M52.6562 0.00858529C51.3133 0.0537124 49.9957 0.308743 48.7812 0.758585C46.9652 1.43702 45.452 2.5257 44.4375 3.88359L2.4062 60.2898C2.26096 60.4729 2.12543 60.6606 1.99995 60.8523C1.98995 60.8666 2.00995 60.9004 1.99995 60.9148C1.96841 60.9459 1.93718 60.9771 1.90625 61.0085C0.67894 62.8827 0 65.1221 0 67.5398L0 116.571C0 123.169 5.28309 128.477 11.875 128.477L36.4375 128.477L36.4375 85.571C36.4375 78.9733 41.7206 73.6648 48.3125 73.6648L57.4375 73.6648C64.0294 73.6648 69.3125 78.9733 69.3125 85.571L69.3125 128.477L93.8437 128.477C100.436 128.477 105.75 123.169 105.75 116.571L105.75 67.5398C105.75 64.4233 104.538 61.6268 102.594 59.5085L61.8437 3.94604C59.9691 1.39179 56.4219 -0.128436 52.6562 0.00853529Z"/>
+ <rect id="rect4124" transform="matrix(0.646168 0 0 0.646168 21.5365 9.64032)" fill="#eeeeee" width="18.17356080000pt" height="31.33372480000pt" rx="9.08678040000pt" ry="14.74450800000pt"/>
+ </g>
+ <path id="path4091" transform="matrix(0.758527 0 0 0.758527 5.33866 4.46647)" fill="url(#gradient0)" d="M102.625 0C45.9507 0 0 47.725 0 106.562C0 110.315 0.1979 114.004 0.5625 117.656C50.6291 136.382 127.144 150.025 205.25 107.062C205.251 106.896 205.25 106.73 205.25 106.562C205.25 47.725 159.299 0 102.625 0Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/apps/shorty/img/usage/info.svg b/apps/shorty/img/usage/info.svg
new file mode 100644
index 00000000000..31a3b6387eb
--- /dev/null
+++ b/apps/shorty/img/usage/info.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133.098pt" height="134.778pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="matrix(0.935714 0 0 0.971429 -260.198 309.044)" gradientUnits="objectBoundingBox" x1="1.86062897958" y1="-2.28811738018" x2="1.88918016404" y2="-1.09199169808" spreadMethod="pad">
+ <stop stop-color="#edf4fd" offset="0.00000000000" stop-opacity="0.52845044633"/>
+ <stop stop-color="#175bb3" offset="1.00000000000" stop-opacity="0.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="layer1">
+ <g id="g5381" transform="translate(-5.94477e-06, 2.56921e-06)" fill="none">
+ <path id="path4167" transform="matrix(0.758527 0 0 0.768105 0 0)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path4169" transform="matrix(0.709764 0 0 0.736855 5.34768 4.47751)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <g id="text4182" transform="translate(66.8213, 30.5499)" fill="none">
+ <path id="path4187" transform="matrix(1.54651 0 0 0.965127 -2.30926e-13 8.88178e-15)" fill="#eeeeee" d="M19.1953 38.6016L19.1953 94.0781C19.1953 97.9219 18.2812 100.828 16.4531 102.797C14.625 104.766 12.3047 105.75 9.49218 105.75C6.67967 105.75 4.38279 104.742 2.60156 102.727C0.86717 100.711 -9.99991e-06 97.8281 8.66294e-11 94.0781L8.66294e-11 39.1641C-9.99991e-06 35.3673 0.86717 32.5079 2.60156 30.5859C4.38279 28.6641 6.67967 27.7032 9.49218 27.7031C12.3047 27.7032 14.625 28.6641 16.4531 30.5859C18.2812 32.5079 19.1953 35.1798 19.1953 38.6016M9.70312 18.7734C7.03123 18.7735 4.73436 17.9532 2.8125 16.3125C0.93748 14.672 -9.99991e-06 12.3517 8.66294e-11 9.35156C-9.99991e-06 6.63291 0.96092 4.40634 2.88281 2.67187C4.85154 0.89072 7.12498 0.0001 9.70312 0C12.1875 0.0001 14.3906 0.79697 16.3125 2.39062C18.2343 3.98447 19.1953 6.30478 19.1953 9.35156C19.1953 12.3048 18.2578 14.6251 16.3828 16.3125C14.5078 17.9532 12.2812 18.7735 9.70312 18.7734"/>
+ </g>
+ <path id="path4171" transform="matrix(0.758527 0 0 0.758527 5.33866 4.46647)" fill="url(#gradient0)" d="M102.625 0C45.9507 0 0 47.725 0 106.562C0 110.315 0.1979 114.004 0.5625 117.656C50.6291 136.382 127.144 150.025 205.25 107.062C205.251 106.896 205.25 106.73 205.25 106.562C205.25 47.725 159.299 0 102.625 0Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/apps/shorty/img/usage/locked.svg b/apps/shorty/img/usage/locked.svg
new file mode 100644
index 00000000000..1691a9dfa4d
--- /dev/null
+++ b/apps/shorty/img/usage/locked.svg
@@ -0,0 +1,23 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133.098pt" height="134.778pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="matrix(0.935714 0 0 0.971429 -260.198 309.044)" gradientUnits="objectBoundingBox" x1="1.86062897958" y1="-2.28811738018" x2="1.88918016404" y2="-1.09199169808" spreadMethod="pad">
+ <stop stop-color="#edf4fd" offset="0.00000000000" stop-opacity="0.52845044633"/>
+ <stop stop-color="#175bb3" offset="1.00000000000" stop-opacity="0.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="layer1">
+ <g id="g5393" transform="translate(-2.11465e-06, -2.863e-06)" fill="none">
+ <path id="path4197" transform="matrix(0.758527 0 0 0.768105 2.64698e-22 0)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path4199" transform="matrix(0.709764 0 0 0.736855 5.34768 4.47751)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <g id="g4220" transform="translate(46.2074, 35.1119)" fill="none">
+ <path id="rect4189" transform="matrix(0.758527 0 0 0.758527 10.0979 0)" fill="#eeeeee" d="M11.875 0C5.28309 0 0 5.3085 0 11.9062L0 37.0938L12.7188 37.0938L12.7188 22.9688C12.7188 16.371 18.0331 11.0625 24.625 11.0625L50.125 11.0625C56.7169 11.0625 62.0312 16.371 62.0312 22.9688L62.0312 37.0938L74.2188 37.0938L74.2188 11.9062C74.2188 5.3085 68.9044 0 62.3125 0Z"/>
+ <path id="rect4193" transform="matrix(0.758527 0 0 0.758527 0 32.6878)" fill="#eeeeee" d="M11.9062 0C5.31434 0 0 5.3085 0 11.9062L0 26.875C0 33.4728 5.31434 38.7812 11.9062 38.7812L87.8125 38.7812C93.6895 38.7812 98.5472 34.5601 99.5312 28.9687L87.9375 28.9687C81.3456 28.9687 76.0312 25.575 76.0312 21.3438C76.0312 17.1125 81.3456 13.6875 87.9375 13.6875L99.7188 13.6875L99.7188 11.9062C99.7188 5.3085 94.4044 0 87.8125 0Z"/>
+ <path id="path4218" transform="matrix(0.758527 0 0 0.758527 0 67.4477)" fill="#eeeeee" d="M11.9062 0C5.31434 0 0 5.3085 0 11.9062L0 26.875C0 33.4728 5.31434 38.7812 11.9062 38.7812L87.8125 38.7812C93.6895 38.7812 98.5472 34.5601 99.5312 28.9687L87.9375 28.9687C81.3456 28.9687 76.0312 25.575 76.0312 21.3438C76.0312 17.1125 81.3456 13.6875 87.9375 13.6875L99.7188 13.6875L99.7188 11.9062C99.7188 5.3085 94.4044 0 87.8125 0Z"/>
+ </g>
+ <path id="path4201" transform="matrix(0.758527 0 0 0.758527 5.33866 4.46647)" fill="url(#gradient0)" d="M102.625 0C45.9507 0 0 47.725 0 106.562C0 110.315 0.1979 114.004 0.5625 117.656C50.6291 136.382 127.144 150.025 205.25 107.062C205.251 106.896 205.25 106.73 205.25 106.562C205.25 47.725 159.299 0 102.625 0Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/apps/shorty/img/usage/network.svg b/apps/shorty/img/usage/network.svg
new file mode 100644
index 00000000000..1d3a0257121
--- /dev/null
+++ b/apps/shorty/img/usage/network.svg
@@ -0,0 +1,34 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133.098pt" height="134.778pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="matrix(0.935714 0 0 0.971429 -260.198 309.044)" gradientUnits="objectBoundingBox" x1="1.86062886879" y1="-2.28811738018" x2="1.88918005155" y2="-1.09199169808" spreadMethod="pad">
+ <stop stop-color="#edf4fd" offset="0.00000000000" stop-opacity="0.52845044633"/>
+ <stop stop-color="#175bb3" offset="1.00000000000" stop-opacity="0.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="layer1">
+ <g id="g5449" transform="translate(-8.40215e-06, -2.8318e-06)" fill="none">
+ <path id="path5051" transform="matrix(0.758527 0 0 0.768105 1.05879e-21 0)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path5053" transform="matrix(0.709764 0 0 0.736855 5.34768 4.47751)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <g id="g5152" transform="translate(24.1282, 25.4609)" fill="none">
+ <path id="path5092" transform="matrix(0.7132 0 0 0.7132 29.7461 0)" fill="#eeeeee" d="M9.9163 0C4.4218 0 0 4.14395 0 9.293L0 44.7222C0 49.8712 4.4218 54.0153 9.9163 54.0153L73.1505 54.0153C78.645 54.0153 83.0668 49.8712 83.0668 44.7222L83.0668 9.293C83.0668 4.14395 78.645 0 73.1505 0ZM19.0295 8.14427L64.0374 8.14427C69.5318 8.14427 73.9537 12.2882 73.9537 17.4373L73.9537 36.5778C73.9537 41.7269 69.5318 45.8709 64.0374 45.8709L19.0295 45.8709C13.535 45.8709 9.1132 41.7269 9.1132 36.5778L9.1132 17.4373C9.1132 12.2882 13.535 8.14427 19.0295 8.14427Z"/>
+ <rect id="rect5098" transform="matrix(0.7132 0 0 0.7132 0 75.1881)" fill="#eeeeee" width="38.99506800000pt" height="30.13255280000pt" rx="11.21705440000pt" ry="11.21705440000pt"/>
+ <rect id="rect5114" transform="matrix(0.7132 0 0 0.7132 86.1201 75.9782)" fill="#eeeeee" width="38.99506800000pt" height="30.13255280000pt" rx="11.21705440000pt" ry="11.21705440000pt"/>
+ <rect id="rect5116" transform="matrix(0.7132 0 0 0.7132 42.665 75.9782)" fill="#eeeeee" width="38.99506800000pt" height="30.13255280000pt" rx="11.21705440000pt" ry="11.21705440000pt"/>
+ <g id="g5138" transform="translate(8.18351, 44.967)" fill="none">
+ <rect id="rect5126" transform="matrix(0.7132 0 0 0.7132 48.3081 8.88178e-15)" fill="#eeeeee" width="7.31157528000pt" height="29.91099280000pt"/>
+ <rect id="rect5128" transform="matrix(0.7132 0 0 0.7132 91.8912 8.69104)" fill="#eeeeee" width="7.53313824000pt" height="20.16222400000pt"/>
+ <rect id="rect5134" transform="matrix(0.7132 0 0 0.7132 0 8.69104)" fill="#eeeeee" width="7.53313824000pt" height="20.16222400000pt"/>
+ <rect id="rect5136" transform="matrix(0.7132 0 0 0.7132 -8.88178e-15 8.69104)" fill="#eeeeee" width="110.60805600000pt" height="8.61677440000pt"/>
+ </g>
+ <rect id="rect5144" transform="matrix(0.7132 0 0 0.7132 42.2636 10.4152)" fill="#eeeeee" width="39.63716160000pt" height="4.23005296000pt"/>
+ <rect id="rect5146" transform="matrix(0.7132 0 0 0.7132 42.2636 10.4152)" fill="#eeeeee" width="39.63716160000pt" height="4.23005296000pt"/>
+ <rect id="rect5148" transform="matrix(0.7132 0 0 0.7132 42.2636 17.5472)" fill="#eeeeee" width="39.63716160000pt" height="4.23005296000pt"/>
+ <rect id="rect5150" transform="matrix(0.7132 0 0 0.7132 42.2636 24.6792)" fill="#eeeeee" width="39.63716160000pt" height="4.23005296000pt"/>
+ </g>
+ <path id="path5055" transform="matrix(0.758527 0 0 0.758527 5.33866 4.46647)" fill="url(#gradient0)" d="M102.625 0C45.9507 0 0 47.725 0 106.562C0 110.315 0.1979 114.004 0.5625 117.656C50.6291 136.382 127.144 150.025 205.25 107.062C205.251 106.896 205.25 106.73 205.25 106.562C205.25 47.725 159.299 0 102.625 0Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/apps/shorty/img/usage/qrcode.svg b/apps/shorty/img/usage/qrcode.svg
new file mode 100644
index 00000000000..83802042f7a
--- /dev/null
+++ b/apps/shorty/img/usage/qrcode.svg
@@ -0,0 +1,271 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133.098pt" height="134.778pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="matrix(0.935714 0 0 0.971429 -260.198 309.044)" gradientUnits="objectBoundingBox" x1="1.86062897958" y1="-2.28811738018" x2="1.88918016404" y2="-1.09199169808" spreadMethod="pad">
+ <stop stop-color="#edf4fd" offset="0.00000000000" stop-opacity="0.52845044633"/>
+ <stop stop-color="#175bb3" offset="1.00000000000" stop-opacity="0.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="layer1">
+ <g id="g5381" transform="translate(-5.94477e-06, 2.56921e-06)" fill="none">
+ <path id="path4167" transform="matrix(0.758527 0 0 0.768105 1.41962e-15 -6.05492e-16)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path4169" transform="matrix(0.709764 0 0 0.736855 5.34768 4.47751)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <g id="text4182" transform="translate(5.94477e-06, -2.56921e-06)" fill="none"/>
+ <path id="path4171" transform="matrix(0.758527 0 0 0.758527 5.33866 4.46647)" fill="url(#gradient0)" d="M102.625 0C45.9507 0 0 47.725 0 106.562C0 110.315 0.1979 114.004 0.5625 117.656C50.6291 136.382 127.144 150.025 205.25 107.062C205.251 106.896 205.25 106.73 205.25 106.562C205.25 47.725 159.299 0 102.625 0Z"/>
+ </g>
+ <g id="group0" transform="translate(31.4638, 31.6895)" fill="none">
+ <rect id="p" transform="translate(0, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p1" transform="translate(0, 7.66166)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p2" transform="translate(0, 12.5286)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p3" transform="translate(0, 17.3955)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p4" transform="translate(0, 22.2624)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p5" transform="translate(0, 27.1293)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p6" transform="translate(0, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p7" transform="translate(0, 41.7301)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p8" transform="translate(0, 56.3308)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p9" transform="translate(0, 61.1977)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p10" transform="translate(0, 70.9316)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p11" transform="translate(0, 75.7985)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p12" transform="translate(0, 80.6654)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p13" transform="translate(0, 85.5323)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p14" transform="translate(0, 90.3992)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p15" transform="translate(0, 95.2662)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p16" transform="translate(0, 100.133)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p17" transform="translate(4.95608, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p18" transform="translate(4.95608, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p19" transform="translate(4.95608, 51.4639)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p20" transform="translate(4.95608, 61.1977)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p21" transform="translate(4.95608, 70.9316)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p22" transform="translate(4.95608, 100.133)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p23" transform="translate(9.91217, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p24" transform="translate(9.91217, 12.5286)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p25" transform="translate(9.91217, 17.3955)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p26" transform="translate(9.91217, 22.2624)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p27" transform="translate(9.91217, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p28" transform="translate(9.91217, 46.597)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p29" transform="translate(9.91217, 51.4639)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p30" transform="translate(9.91217, 56.3308)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p31" transform="translate(9.91217, 61.1977)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p32" transform="translate(9.91217, 70.9316)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p33" transform="translate(9.91217, 80.6654)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p34" transform="translate(9.91217, 85.5323)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p35" transform="translate(9.91217, 90.3992)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p36" transform="translate(9.91217, 100.133)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p37" transform="translate(14.8683, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p38" transform="translate(14.8683, 12.5286)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p39" transform="translate(14.8683, 17.3955)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p40" transform="translate(14.8683, 22.2624)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p41" transform="translate(14.8683, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p42" transform="translate(14.8683, 46.597)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p43" transform="translate(14.8683, 51.4639)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p44" transform="translate(14.8683, 56.3308)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p45" transform="translate(14.8683, 70.9316)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p46" transform="translate(14.8683, 80.6654)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p47" transform="translate(14.8683, 85.5323)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p48" transform="translate(14.8683, 90.3992)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p49" transform="translate(14.8683, 100.133)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p50" transform="translate(19.8243, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p51" transform="translate(19.8243, 12.5286)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p52" transform="translate(19.8243, 17.3955)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p53" transform="translate(19.8243, 22.2624)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p54" transform="translate(19.8243, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p55" transform="translate(19.8243, 46.597)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p56" transform="translate(19.8243, 61.1977)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p57" transform="translate(19.8243, 70.9316)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p58" transform="translate(19.8243, 80.6654)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p59" transform="translate(19.8243, 85.5323)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p60" transform="translate(19.8243, 90.3992)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p61" transform="translate(19.8243, 100.133)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p62" transform="translate(24.7804, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p63" transform="translate(24.7804, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p64" transform="translate(24.7804, 46.597)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p65" transform="translate(24.7804, 51.4639)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p66" transform="translate(24.7804, 56.3308)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p67" transform="translate(24.7804, 70.9316)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p68" transform="translate(24.7804, 100.133)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p69" transform="translate(29.7365, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p70" transform="translate(29.7365, 7.66166)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p71" transform="translate(29.7365, 12.5286)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p72" transform="translate(29.7365, 17.3955)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p73" transform="translate(29.7365, 22.2624)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p74" transform="translate(29.7365, 27.1293)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p75" transform="translate(29.7365, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p76" transform="translate(29.7365, 41.7301)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p77" transform="translate(29.7365, 51.4639)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p78" transform="translate(29.7365, 61.1977)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p79" transform="translate(29.7365, 70.9316)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p80" transform="translate(29.7365, 75.7985)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p81" transform="translate(29.7365, 80.6654)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p82" transform="translate(29.7365, 85.5323)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p83" transform="translate(29.7365, 90.3992)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p84" transform="translate(29.7365, 95.2662)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p85" transform="translate(29.7365, 100.133)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p86" transform="translate(34.6926, 46.597)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p87" transform="translate(34.6926, 51.4639)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p88" transform="translate(34.6926, 56.3308)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p89" transform="translate(39.6487, 7.66166)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p90" transform="translate(39.6487, 12.5286)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p91" transform="translate(39.6487, 17.3955)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p92" transform="translate(39.6487, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p93" transform="translate(39.6487, 36.8632)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p94" transform="translate(39.6487, 41.7301)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p95" transform="translate(39.6487, 46.597)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p96" transform="translate(39.6487, 56.3308)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p97" transform="translate(39.6487, 61.1977)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p98" transform="translate(39.6487, 66.0647)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p99" transform="translate(39.6487, 100.133)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p100" transform="translate(44.6048, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p101" transform="translate(44.6048, 12.5286)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p102" transform="translate(44.6048, 17.3955)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p103" transform="translate(44.6048, 27.1293)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p104" transform="translate(44.6048, 36.8632)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p105" transform="translate(44.6048, 41.7301)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p106" transform="translate(44.6048, 46.597)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p107" transform="translate(44.6048, 51.4639)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p108" transform="translate(44.6048, 56.3308)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p109" transform="translate(44.6048, 80.6654)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p110" transform="translate(44.6048, 90.3992)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p111" transform="translate(44.6048, 100.133)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p112" transform="translate(49.5608, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p113" transform="translate(49.5608, 17.3955)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p114" transform="translate(49.5608, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p115" transform="translate(49.5608, 56.3308)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p116" transform="translate(49.5608, 61.1977)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p117" transform="translate(49.5608, 66.0647)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p118" transform="translate(49.5608, 75.7985)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p119" transform="translate(49.5608, 85.5323)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p120" transform="translate(49.5608, 90.3992)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p121" transform="translate(54.5169, 12.5286)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p122" transform="translate(54.5169, 22.2624)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p123" transform="translate(54.5169, 36.8632)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p124" transform="translate(54.5169, 51.4639)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p125" transform="translate(54.5169, 66.0647)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p126" transform="translate(54.5169, 75.7985)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p127" transform="translate(54.5169, 100.133)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p128" transform="translate(59.473, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p129" transform="translate(59.473, 22.2624)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p130" transform="translate(59.473, 27.1293)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p131" transform="translate(59.473, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p132" transform="translate(59.473, 36.8632)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p133" transform="translate(59.473, 41.7301)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p134" transform="translate(59.473, 46.597)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p135" transform="translate(59.473, 66.0647)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p136" transform="translate(59.473, 70.9316)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p137" transform="translate(59.473, 75.7985)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p138" transform="translate(59.473, 80.6654)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p139" transform="translate(59.473, 85.5323)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p140" transform="translate(64.4291, 41.7301)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p141" transform="translate(64.4291, 46.597)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p142" transform="translate(64.4291, 51.4639)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p143" transform="translate(64.4291, 61.1977)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p144" transform="translate(64.4291, 66.0647)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p145" transform="translate(64.4291, 75.7985)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p146" transform="translate(64.4291, 100.133)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p147" transform="translate(69.3852, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p148" transform="translate(69.3852, 7.66166)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p149" transform="translate(69.3852, 12.5286)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p150" transform="translate(69.3852, 17.3955)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p151" transform="translate(69.3852, 22.2624)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p152" transform="translate(69.3852, 27.1293)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p153" transform="translate(69.3852, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p154" transform="translate(69.3852, 41.7301)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p155" transform="translate(69.3852, 51.4639)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p156" transform="translate(69.3852, 61.1977)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p157" transform="translate(69.3852, 66.0647)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p158" transform="translate(69.3852, 70.9316)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p159" transform="translate(69.3852, 90.3992)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p160" transform="translate(74.3413, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p161" transform="translate(74.3413, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p162" transform="translate(74.3413, 51.4639)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p163" transform="translate(74.3413, 56.3308)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p164" transform="translate(74.3413, 70.9316)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p165" transform="translate(74.3413, 80.6654)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p166" transform="translate(74.3413, 85.5323)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p167" transform="translate(74.3413, 90.3992)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p168" transform="translate(74.3413, 95.2662)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p169" transform="translate(74.3413, 100.133)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p170" transform="translate(79.2974, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p171" transform="translate(79.2974, 12.5286)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p172" transform="translate(79.2974, 17.3955)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p173" transform="translate(79.2974, 22.2624)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p174" transform="translate(79.2974, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p175" transform="translate(79.2974, 56.3308)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p176" transform="translate(79.2974, 66.0647)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p177" transform="translate(79.2974, 80.6654)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p178" transform="translate(79.2974, 85.5323)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p179" transform="translate(79.2974, 90.3992)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p180" transform="translate(79.2974, 95.2662)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p181" transform="translate(84.2534, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p182" transform="translate(84.2534, 12.5286)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p183" transform="translate(84.2534, 17.3955)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p184" transform="translate(84.2534, 22.2624)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p185" transform="translate(84.2534, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p186" transform="translate(84.2534, 41.7301)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p187" transform="translate(84.2534, 46.597)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p188" transform="translate(84.2534, 56.3308)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p189" transform="translate(84.2534, 66.0647)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p190" transform="translate(84.2534, 75.7985)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p191" transform="translate(84.2534, 80.6654)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p192" transform="translate(84.2534, 90.3992)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p193" transform="translate(84.2534, 95.2662)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p194" transform="translate(84.2534, 100.133)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p195" transform="translate(89.2095, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p196" transform="translate(89.2095, 12.5286)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p197" transform="translate(89.2095, 17.3955)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p198" transform="translate(89.2095, 22.2624)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p199" transform="translate(89.2095, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p200" transform="translate(89.2095, 41.7301)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p201" transform="translate(89.2095, 46.597)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p202" transform="translate(89.2095, 51.4639)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p203" transform="translate(89.2095, 56.3308)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p204" transform="translate(89.2095, 66.0647)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p205" transform="translate(89.2095, 70.9316)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p206" transform="translate(89.2095, 75.7985)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p207" transform="translate(89.2095, 85.5323)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p208" transform="translate(89.2095, 90.3992)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p209" transform="translate(89.2095, 95.2662)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p210" transform="translate(94.1656, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p211" transform="translate(94.1656, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p212" transform="translate(94.1656, 41.7301)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p213" transform="translate(94.1656, 46.597)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p214" transform="translate(94.1656, 51.4639)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p215" transform="translate(94.1656, 61.1977)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p216" transform="translate(94.1656, 70.9316)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p217" transform="translate(94.1656, 90.3992)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p218" transform="translate(94.1656, 100.133)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p219" transform="translate(99.1217, 2.79474)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p220" transform="translate(99.1217, 7.66166)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p221" transform="translate(99.1217, 12.5286)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p222" transform="translate(99.1217, 17.3955)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p223" transform="translate(99.1217, 22.2624)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p224" transform="translate(99.1217, 27.1293)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p225" transform="translate(99.1217, 31.9962)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p226" transform="translate(99.1217, 66.0647)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p227" transform="translate(99.1217, 75.7985)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p228" transform="translate(99.1217, 80.6654)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ <rect id="p229" transform="translate(99.1217, 90.3992)" fill="#ffffff" stroke="#000000" stroke-width="0.00000000000" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="3.96486751181pt" height="3.89353363095pt"/>
+ </g>
+ <rect id="shape0" transform="translate(73.7513, 37.3739)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape1" transform="translate(84.0513, 39.0469)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape2" transform="translate(82.9589, 61.8606)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape3" transform="translate(73.7513, 37.3739)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape4" transform="translate(72.6589, 52.2789)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape5" transform="translate(83.8952, 72.507)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape6" transform="translate(48.4696, 74.18)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape7" transform="translate(34.2682, 77.8302)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape8" transform="translate(73.9074, 95.1686)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape9" transform="translate(73.4392, 120.112)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape10" transform="translate(72.0347, 107.792)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape11" transform="translate(118.384, 85.7389)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape12" transform="translate(125.719, 114.18)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape13" transform="translate(90.6058, 123.762)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape14" transform="translate(87.7967, 92.8872)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape15" transform="translate(104.651, 105.207)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape16" transform="translate(93.8831, 115.853)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape17" transform="translate(106.836, 76.3093)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape18" transform="translate(108.553, 125.739)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ <rect id="shape19" transform="translate(125.251, 75.5488)" fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" width="7.74057149730pt" height="8.39543189174pt"/>
+ </g>
+</svg>
diff --git a/apps/shorty/img/usage/sms.svg b/apps/shorty/img/usage/sms.svg
new file mode 100644
index 00000000000..5d519df9570
--- /dev/null
+++ b/apps/shorty/img/usage/sms.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133.098pt" height="134.778pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="matrix(0.935714 0 0 0.971429 -260.198 309.044)" gradientUnits="objectBoundingBox" x1="1.86062891915" y1="-2.28811733465" x2="1.88918010268" y2="-1.09199167635" spreadMethod="pad">
+ <stop stop-color="#edf4fd" offset="0.00000000000" stop-opacity="0.52845044633"/>
+ <stop stop-color="#175bb3" offset="1.00000000000" stop-opacity="0.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="layer1">
+ <g id="g5434" transform="translate(-6.50233e-06, 2.52429e-06)" fill="none">
+ <path id="path5075" transform="matrix(0.758527 0 0 0.768105 1.05879e-21 0)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path5077" transform="matrix(0.709764 0 0 0.736855 5.34768 4.47756)" fill="#000000" stroke="#000000" stroke-width="1.25000000000" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2.00000000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="rect5206" transform="matrix(0.758527 0 0 0.758527 28.5135 43.1225)" fill="#eeeeee" d="M12 0C5.352 0 0 5.352 0 12L0 72.5937C0 79.2417 5.352 84.5937 12 84.5937L83.4687 84.5937C92.9385 97.8632 117.244 131.838 117.469 130.938C117.697 130.027 118.047 97.894 118.188 84.5937L129 84.5937C135.648 84.5937 141 79.2417 141 72.5937L141 12C141 5.352 135.648 1.13687e-13 129 1.13687e-13ZM29.8125 25.2812C31.4969 25.2813 33.0089 25.5233 34.3437 26C35.6946 26.4768 36.7976 27.1045 37.6875 27.9063C38.5932 28.7081 39.2892 29.6186 39.75 30.5937C40.2268 31.5689 40.4687 32.5057 40.4687 33.4375C40.4687 34.456 40.1959 35.2874 39.6875 35.9375C39.1948 36.5876 38.4842 36.9063 37.5625 36.9062C36.8951 36.9063 36.3205 36.6451 35.8437 36.125C35.383 35.6049 34.8537 34.8527 34.25 33.8125C33.7573 32.9457 33.1984 32.2388 32.5313 31.7187C31.8637 31.1987 30.9566 30.9375 29.8125 30.9375C28.6366 30.9375 27.6536 31.2753 26.875 31.9687C26.0963 32.6405 25.7187 33.4815 25.7187 34.5C25.7187 35.4318 25.9904 36.2057 26.5625 36.8125C27.1345 37.3976 27.9056 37.8912 28.875 38.2812C29.8444 38.6713 31.1747 39.1553 32.875 39.7187C34.8932 40.3905 36.5412 41.1932 37.8125 42.125C39.0997 43.0568 40.0673 44.1589 40.7187 45.4375C41.3863 46.6944 41.7187 48.1247 41.7187 49.75C41.7187 52.1988 41.2802 54.3072 40.4063 56.0625C39.5322 57.7961 38.2474 59.1211 36.5313 60.0312C34.8308 60.9197 32.7597 61.375 30.3125 61.375C27.9765 61.375 25.9811 60.8814 24.3125 59.9062C22.6439 58.9311 21.3884 57.7019 20.5937 56.25C19.7993 54.7764 19.4063 53.3173 19.4063 51.8437C19.4063 50.8686 19.679 50.0372 20.1875 49.3437C20.696 48.6503 21.3151 48.2813 22.0937 48.2812C22.7773 48.2813 23.322 48.5137 23.6875 48.9687C24.053 49.4238 24.3852 50.0828 24.7187 50.9062C25.3863 52.4882 26.2033 53.6573 27.125 54.4375C28.0625 55.2176 29.3325 55.625 30.9375 55.625C32.2405 55.625 33.2987 55.2176 34.125 54.4375C34.9672 53.6357 35.375 52.7373 35.375 51.7187C35.375 50.1585 34.9364 48.9964 34.0625 48.2812C33.2044 47.5661 31.7988 46.9001 29.8125 46.25C27.5719 45.4915 25.7268 44.6985 24.3125 43.875C22.9141 43.0299 21.811 41.9277 20.9687 40.5625C20.1267 39.1973 19.6875 37.5249 19.6875 35.5312C19.6875 33.7543 20.0964 32.0507 20.875 30.4687C21.6536 28.8868 22.8028 27.6506 24.3125 26.7188C25.838 25.7653 27.6673 25.2813 29.8125 25.2812ZM49.9687 25.2812C50.8587 25.2813 51.5995 25.7078 52.1875 26.5312C52.7912 27.3331 53.0937 28.4448 53.0937 29.875L53.0937 30.7187C54.2221 28.8768 55.4316 27.5231 56.7187 26.6563C58.006 25.7895 59.4268 25.3438 61 25.3438C62.6368 25.3438 64.0429 25.799 65.2187 26.6875C66.3947 27.576 67.3621 28.9201 68.125 30.7187C69.2214 28.8985 70.4007 27.5544 71.6563 26.6875C72.9115 25.799 74.3182 25.3438 75.8437 25.3438C77.6236 25.3438 79.1503 25.8278 80.4375 26.7812C81.7247 27.7348 82.6768 29.098 83.3125 30.875C83.8686 32.4786 84.1561 34.9919 84.1563 38.4375L84.1563 55.7812C84.156 57.6449 83.8385 59.0369 83.2187 59.9687C82.5991 60.9006 81.7982 61.375 80.7813 61.375C79.7959 61.375 78.9794 60.9006 78.3437 59.9687C77.7241 59.0152 77.4063 57.6232 77.4063 55.7812L77.4063 40.8437C77.4063 38.9368 77.3463 37.4298 77.2187 36.2812C77.1078 35.1327 76.7744 34.1551 76.25 33.375C75.7256 32.5949 74.9397 32.2188 73.875 32.2187C73.0169 32.2188 72.2161 32.5566 71.4375 33.25C70.6747 33.9435 70.0541 34.8611 69.625 36.0312C69.1483 37.5265 68.9063 40.1764 68.9063 43.9687L68.9063 55.7812C68.9063 57.6449 68.6197 59.0369 68 59.9687C67.3802 60.9006 66.5482 61.375 65.5313 61.375C64.5459 61.375 63.7447 60.9006 63.125 59.9687C62.5211 59.0369 62.2187 57.6449 62.2187 55.7812L62.2187 41.625C62.2187 39.3929 62.1737 37.6727 62.0625 36.4375C61.9514 35.2023 61.6487 34.1864 61.1563 33.4062C60.6636 32.6045 59.8772 32.2188 58.8125 32.2187C56.6831 32.2188 55.2772 33.1938 54.5937 35.1875C53.9264 37.1812 53.5937 40.0539 53.5937 43.7812L53.5937 55.7812C53.5937 57.6232 53.2759 59.0152 52.6563 59.9687C52.0523 60.9006 51.2511 61.375 50.25 61.375C49.2648 61.375 48.4482 60.9006 47.8125 59.9687C47.1928 59.0152 46.9061 57.6232 46.9063 55.7812L46.9063 30.375C46.9063 28.7064 47.1627 27.4606 47.7187 26.5937C48.291 25.727 49.0471 25.2813 49.9687 25.2812ZM99.5 25.2812C101.184 25.2813 102.697 25.5233 104.031 26C105.382 26.4768 106.516 27.1045 107.406 27.9063C108.312 28.7081 108.977 29.6186 109.438 30.5937C109.914 31.5689 110.156 32.5057 110.156 33.4375C110.156 34.456 109.915 35.2874 109.406 35.9375C108.914 36.5876 108.203 36.9063 107.281 36.9062C106.614 36.9063 106.039 36.6451 105.562 36.125C105.102 35.6049 104.573 34.8527 103.969 33.8125C103.476 32.9457 102.886 32.2388 102.219 31.7187C101.551 31.1987 100.644 30.9375 99.5 30.9375C98.324 30.9375 97.3412 31.2753 96.5625 31.9687C95.7839 32.6405 95.4062 33.4815 95.4063 34.5C95.4063 35.4318 95.678 36.2057 96.25 36.8125C96.8221 37.3976 97.5931 37.8912 98.5625 38.2812C99.5318 38.6713 100.862 39.1553 102.562 39.7187C104.581 40.3905 106.229 41.1932 107.5 42.125C108.787 43.0568 109.755 44.1589 110.406 45.4375C111.074 46.6944 111.406 48.1247 111.406 49.75C111.406 52.1988 110.968 54.3072 110.094 56.0625C109.22 57.7961 107.935 59.1211 106.219 60.0312C104.519 60.9197 102.447 61.375 100 61.375C97.6641 61.375 95.6686 60.8814 94 59.9062C92.3314 58.9311 91.1071 57.7019 90.3125 56.25C89.518 54.7764 89.0938 53.3173 89.0937 51.8437C89.0937 50.8686 89.3665 50.0372 89.875 49.3437C90.3834 48.6503 91.0339 48.2813 91.8125 48.2812C92.4958 48.2813 93.0095 48.5137 93.375 48.9687C93.7404 49.4238 94.1038 50.0828 94.4375 50.9062C95.1049 52.4882 95.8907 53.6573 96.8125 54.4375C97.75 55.2176 99.0199 55.625 100.625 55.625C101.928 55.625 102.986 55.2176 103.813 54.4375C104.655 53.6357 105.094 52.7373 105.094 51.7187C105.094 50.1585 104.655 48.9964 103.781 48.2812C102.923 47.5661 101.486 46.9001 99.5 46.25C97.2593 45.4915 95.4456 44.6985 94.0313 43.875C92.6329 43.0299 91.4985 41.9277 90.6563 40.5625C89.814 39.1973 89.4063 37.5249 89.4063 35.5312C89.4063 33.7543 89.7839 32.0507 90.5625 30.4687C91.3411 28.8868 92.4903 27.6506 94 26.7188C95.5255 25.7653 97.3548 25.2813 99.5 25.2812Z"/>
+ <path id="path5079" transform="matrix(0.758527 0 0 0.758527 5.33866 4.46648)" fill="url(#gradient0)" d="M102.625 0C45.9507 0 0 47.725 0 106.562C0 110.315 0.1979 114.004 0.5625 117.656C50.6291 136.382 127.144 150.025 205.25 107.062C205.251 106.896 205.25 106.73 205.25 106.562C205.25 47.725 159.299 0 102.625 0Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/apps/shorty/img/usage/team.svg b/apps/shorty/img/usage/team.svg
new file mode 100644
index 00000000000..c2d3c0075d2
--- /dev/null
+++ b/apps/shorty/img/usage/team.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133.098pt" height="134.778pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="matrix(0.935714 0 0 0.971429 -260.198 309.044)" gradientUnits="objectBoundingBox" x1="1.86062898865" y1="-2.28811738018" x2="1.88918017325" y2="-1.09199169808" spreadMethod="pad">
+ <stop stop-color="#edf4fd" offset="0.00000000000" stop-opacity="0.52845044633"/>
+ <stop stop-color="#175bb3" offset="1.00000000000" stop-opacity="0.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="layer1">
+ <g id="g5258" transform="translate(-2.67397e-06, 5.0533e-06)" fill="none">
+ <path id="path4049" transform="matrix(0.758527 0 0 0.768105 5.29396e-22 0)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path4051" transform="matrix(0.709764 0 0 0.736855 5.34768 4.47748)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path4066" transform="matrix(0.758527 0 0 0.758527 28.6825 27.5818)" fill="#eeeeee" d="M51.3117 0C34.1148 0 20.1891 15.2419 20.1891 34.0447C20.1891 44.7741 24.7371 54.3331 31.8306 60.5733C16.506 70.8805 4.63895 92.9893 0 120.395L55.1922 120.395C59.0298 99.7894 68.1076 83.2199 79.7599 75.3826C75.6622 71.7778 72.6402 66.7497 71.3435 60.9909C71.1645 60.8664 70.999 60.7224 70.8191 60.6012C70.9527 60.4838 71.0806 60.359 71.2124 60.2393C70.8546 58.4247 70.6355 56.5482 70.6355 54.6162C70.6355 44.9453 75.3345 36.4865 82.3819 31.8177C81.3314 14.0536 67.8239 0 51.3117 0Z"/>
+ <path id="path4076" transform="matrix(0.758527 0 0 0.758527 79.2535 53.036)" fill="#eeeeee" d="M40.1579 0C26.6991 0 15.8005 11.9287 15.8005 26.6442C15.8005 35.0414 19.3599 42.5225 24.9114 47.4063C12.918 55.4729 3.63055 72.7758 0 94.2243L80.3158 94.2243C76.6873 72.791 67.4064 55.5019 55.4248 47.428C60.9821 42.5445 64.5358 35.0463 64.5358 26.6442C64.5358 11.9287 53.6166 0 40.1579 0Z"/>
+ <path id="path4053" transform="matrix(0.758527 0 0 0.758527 5.33866 4.46644)" fill="url(#gradient0)" d="M102.625 0C45.9507 0 0 47.725 0 106.562C0 110.315 0.1979 114.004 0.5625 117.656C50.6291 136.382 127.144 150.025 205.25 107.062C205.251 106.896 205.25 106.73 205.25 106.562C205.25 47.725 159.299 0 102.625 0Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/apps/shorty/img/usage/unlocked.svg b/apps/shorty/img/usage/unlocked.svg
new file mode 100644
index 00000000000..b993bd1458b
--- /dev/null
+++ b/apps/shorty/img/usage/unlocked.svg
@@ -0,0 +1,23 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133.098pt" height="134.778pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="matrix(0.935714 0 0 0.971429 -260.198 309.044)" gradientUnits="objectBoundingBox" x1="1.86062897958" y1="-2.28811733465" x2="1.88918016404" y2="-1.09199167635" spreadMethod="pad">
+ <stop stop-color="#edf4fd" offset="0.00000000000" stop-opacity="0.52845044633"/>
+ <stop stop-color="#175bb3" offset="1.00000000000" stop-opacity="0.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="layer1">
+ <g id="g5416" transform="translate(-2.95322e-06, -2.15252e-07)" fill="none">
+ <path id="path4251" transform="matrix(0.758527 0 0 0.768105 5.29396e-22 0)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path4253" transform="matrix(0.709764 0 0 0.736855 5.34768 4.47756)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <g id="g5406" transform="translate(23.3294, 40.9941)" fill="none">
+ <path id="path4263" transform="matrix(0.758527 0 0 0.758527 0 0)" fill="#eeeeee" d="M11.875 0C5.28309 0 0 5.3085 0 11.9062L0 37.0938L12.7188 37.0938L12.7188 22.9688C12.7188 16.371 18.0331 11.0625 24.625 11.0625L50.125 11.0625C56.7169 11.0625 62.0312 16.371 62.0312 22.9688L62.0312 37.0938L74.2188 37.0938L74.2188 11.9062C74.2188 5.3085 68.9044 0 62.3125 0Z"/>
+ <path id="path4265" transform="matrix(0.758527 0 0 0.758527 37.7996 31.8475)" fill="#eeeeee" d="M11.9062 1.13687e-13C5.31434 1.13687e-13 0 5.3085 0 11.9062L0 26.875C0 33.4727 5.31434 38.7812 11.9062 38.7812L87.8125 38.7812C93.6895 38.7812 98.5472 34.5601 99.5312 28.9687L87.9375 28.9687C81.3456 28.9687 76.0312 25.575 76.0312 21.3437C76.0312 17.1125 81.3456 13.6875 87.9375 13.6875L99.7188 13.6875L99.7188 11.9062C99.7188 5.3085 94.4044 0 87.8125 0Z"/>
+ <path id="path4267" transform="matrix(0.758527 0 0 0.758527 37.7996 66.6074)" fill="#eeeeee" d="M11.9062 0C5.31434 0 0 5.3085 0 11.9063L0 26.875C0 33.4728 5.31434 38.7813 11.9062 38.7813L87.8125 38.7813C93.6895 38.7813 98.5472 34.5601 99.5312 28.9688L87.9375 28.9688C81.3456 28.9688 76.0312 25.575 76.0312 21.3438C76.0312 17.1126 81.3456 13.6875 87.9375 13.6875L99.7188 13.6875L99.7188 11.9063C99.7188 5.3085 94.4044 0 87.8125 0Z"/>
+ </g>
+ <path id="path4255" transform="matrix(0.758527 0 0 0.758527 5.33866 4.46648)" fill="url(#gradient0)" d="M102.625 0C45.9507 0 0 47.725 0 106.562C0 110.315 0.1979 114.004 0.5625 117.656C50.6291 136.382 127.144 150.025 205.25 107.062C205.251 106.895 205.25 106.73 205.25 106.562C205.25 47.725 159.299 -1.13687e-13 102.625 0Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/apps/shorty/img/usage/workstation.svg b/apps/shorty/img/usage/workstation.svg
new file mode 100644
index 00000000000..104d98f8552
--- /dev/null
+++ b/apps/shorty/img/usage/workstation.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<!-- Created using Karbon, part of Calligra: http://www.calligra.org/karbon -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="133.098pt" height="134.778pt">
+<defs>
+ <linearGradient id="gradient0" gradientTransform="matrix(0.935714 0 0 0.971429 -260.198 309.044)" gradientUnits="objectBoundingBox" x1="1.86062897958" y1="-2.28811733465" x2="1.88918016404" y2="-1.09199167635" spreadMethod="pad">
+ <stop stop-color="#edf4fd" offset="0.00000000000" stop-opacity="0.52845044633"/>
+ <stop stop-color="#175bb3" offset="1.00000000000" stop-opacity="0.00000000000"/>
+ </linearGradient>
+ </defs>
+<g id="layer1">
+ <g id="g5228" transform="translate(7.95475e-07, 1.12198e-05)" fill="none">
+ <path id="path4227" transform="matrix(0.758527 0 0 0.768105 0 0)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <path id="path4229" transform="matrix(0.709764 0 0 0.736855 5.34768 4.47748)" fill="#000000" d="M219.336 109.668C219.336 170.236 170.236 219.336 109.668 219.336C49.1001 219.336 0 170.236 0 109.668C0 49.1001 49.1001 0 109.668 0C170.236 0 219.336 49.1001 219.336 109.668Z"/>
+ <g id="g5179" transform="translate(32.168, 32.4772)" fill="none">
+ <path id="rect5085" transform="matrix(0.758527 0 0 0.758527 5.76033 -8.88178e-15)" fill="#eeeeee" d="M14.0643 1.13687e-13C6.27157 1.13687e-13 0 6.74806 0 15.1328L0 72.8262C0 81.211 6.27157 87.959 14.0643 87.959L103.749 87.959C111.542 87.959 117.814 81.211 117.814 72.8262L117.814 15.1328C117.814 6.74806 111.542 0 103.749 0ZM26.9895 13.2622L90.8242 13.2622C98.6169 13.2622 104.889 20.0103 104.889 28.3951L104.889 59.5638C104.889 67.9486 98.6169 74.6968 90.8242 74.6968L26.9895 74.6968C19.1968 74.6968 12.9252 67.9486 12.9252 59.5638L12.9252 28.3951C12.9252 20.0103 19.1968 13.2622 26.9895 13.2622Z"/>
+ <path id="rect5167" transform="matrix(0.758527 0 0 0.758527 -4.44089e-15 73.5695)" fill="#eeeeee" d="M5 0C2.23 0 0 2.23 0 5L0 31.125C0 33.895 2.23 36.125 5 36.125L127.938 36.125C130.708 36.125 132.938 33.895 132.938 31.125L132.938 5C132.938 2.23 130.708 0 127.938 0ZM15.375 9C19.5823 9 22.9688 12.0823 22.9688 15.9062C22.9688 19.7302 19.5823 22.8438 15.375 22.8438C11.1677 22.8438 7.75 19.7302 7.75 15.9062C7.75 12.0823 11.1677 9 15.375 9ZM78.0938 9.53125L118.5 9.53125C121.27 9.53125 123.5 11.7613 123.5 14.5312L123.5 16.1875C123.5 18.9575 121.27 21.1875 118.5 21.1875L78.0938 21.1875C75.3237 21.1875 73.0938 18.9575 73.0938 16.1875L73.0938 14.5312C73.0938 11.7613 75.3237 9.53125 78.0938 9.53125Z"/>
+ </g>
+ <path id="path4231" transform="matrix(0.758527 0 0 0.758527 5.33866 4.46648)" fill="url(#gradient0)" d="M102.625 0C45.9507 0 0 47.725 0 106.563C0 110.315 0.1979 114.004 0.5625 117.656C50.6291 136.382 127.144 150.025 205.25 107.063C205.251 106.896 205.25 106.73 205.25 106.563C205.25 47.725 159.299 1.13687e-13 102.625 0Z"/>
+ </g>
+ </g>
+</svg>
diff --git a/apps/shorty/index.php b/apps/shorty/index.php
new file mode 100644
index 00000000000..8c729bae149
--- /dev/null
+++ b/apps/shorty/index.php
@@ -0,0 +1,149 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file index.php
+ * This is the plugins central position
+ * All requests to the plugin are handled by this file.
+ * Exceptions: system settings, user preferences and relaying
+ * @access public
+ * @author Christian Reiner
+ */
+
+OCP\App::setActiveNavigationEntry ( 'shorty_index' );
+
+OCP\Util::addStyle ( 'shorty', 'shorty' );
+
+OCP\Util::addScript ( 'shorty/3rdparty','jquery.tinysort.min' );
+OCP\Util::addScript ( 'shorty', 'shorty' );
+OCP\Util::addScript ( 'shorty', 'init' );
+if ( OC_Log::DEBUG==OC_Config::getValue( "loglevel", OC_Log::WARN ) )
+ OCP\Util::addScript ( 'shorty', 'debug' );
+
+// strategy:
+// - first: decide which action is requested
+// - second: execute that action with an optional argument provided
+
+// defaults:
+$act = 'index';
+$arg = NULL;
+// we try to guess what the request indicates:
+// - a (shorty) id to be looked up in the database resulting in a forwarding to the stored target
+// - a (target) url to be added as a new shorty
+// - none of the two, so just a plain list of existing shortys
+foreach ($_GET as $key=>$val) // in case there are unexpected, additional arguments like a timestamp added by some stupid proxy
+{
+ switch ($key)
+ {
+ // this is the OC4 argument used to identify the app called, we ignore it:
+ case 'app':
+ break;
+ // any recognizable argument key indicating a url to be added as new shorty ?
+ case 'url':
+ case 'uri':
+ case 'target':
+ case 'link':
+ // example: http://.../shorty/index.php?url=http%...
+ $act = 'acquire';
+ $arg = OC_Shorty_Type::req_argument($key,OC_Shorty_Type::URL,FALSE);
+ break 2; // skip switch AND foreach
+ // no recognizable key but something else, hm...
+ // this _might_ be some unexcepted argument, or:
+ // it is an expected argument, but without recognizable key, so we try to guess by examining the content
+ // we restrict this 'guessing' to cases where only a single argument is specified
+ default:
+ if ( (1==sizeof($_GET)) // only one single request argument
+ &&( ! reset($_GET)) ) // no value, so maybe just an id
+ {
+ // use that source instead of $key, since $key contains replaced chars (php specific exceptions due to var name problems)
+ $raw = urldecode($_SERVER['QUERY_STRING']);
+ // now try to interpret its content
+ if (NULL!==($value=OC_Shorty_Type::normalize($raw,OC_Shorty_Type::URL,FALSE)))
+ {
+ // the query string is a url, acquire it as a new shorty
+ $act = 'acquire';
+ $arg = $raw;
+ break 2;
+ }
+ else
+ {
+ // no pattern recognized, so we assume an ordinary index action
+ $act = 'index';
+ break 2;
+ }
+ } // if
+ $act='index';
+ break 2;
+ } // switch key
+} // foreach key
+
+// next, execute the "act" whilst considering the 'arg'
+switch ($act)
+{
+ case 'acquire': // add url as new shorty
+ // keep the url specified as referer, that is the one we want to store
+ $_SESSION['shorty-referrer'] = $arg;
+ OCP\Util::writeLog( 'shorty', sprintf("Detected an incoming Shortlet request for url '%s...'",substr($arg,0,80)), OC_Log::INFO );
+ header ( sprintf('Location: %s', OCP\Util::linkTo('shorty','index.php')) );
+ exit();
+ // =====
+ case 'index': // action 'index': list of shortys
+ default:
+ try
+ {
+ // is this a redirect from a call with a target url to be added ?
+ if ( isset($_SESSION['shorty-referrer']) )
+ {
+ // this takes care of handling the url on the client side
+ OCP\Util::addScript ( 'shorty', 'add' );
+ // add url taked from the session vars to anything contained in the query string
+ $_SERVER['QUERY_STRING'] = implode('&',array_merge(array('url'=>$_SESSION['shorty-referrer']),explode('&',$_SERVER['QUERY_STRING'])));
+ }
+ else
+ {
+ // simple desktop initialization, no special actions contained
+ OCP\Util::addScript ( 'shorty', 'list' );
+ }
+ $tmpl = new OCP\Template( 'shorty', 'tmpl_index', 'user' );
+ // the (remote) base url of the qrcode generator
+ $tmpl->assign ( 'qrcode-url', sprintf('%s?service=%s&url=',OCP\Util::linkToAbsolute("", "public.php"),'shorty_qrcode') );
+ // available status (required for select filter in toolbox)
+ $shorty_status['']=sprintf('- %s -',OC_Shorty_L10n::t('all'));
+ foreach ( OC_Shorty_Type::$STATUS as $status )
+ $shorty_status[$status] = OC_Shorty_L10n::t($status);
+ $tmpl->assign ( 'shorty-status', $shorty_status );
+ // any referrer we want to hand over to the browser ?
+ if ( array_key_exists('shorty-referrer',$_SESSION) )
+ $tmpl->assign ( 'shorty-referrer', $_SESSION['shorty-referrer'] );
+ // is sending sms enabled in the personal preferences ?
+ $tmpl->assign ( 'sms-control', OCP\Config::getUserValue(OCP\User::getUser(),'shorty','sms-control','disabled') );
+ // clean up session var so that a browser reload does not trigger the same action again
+ unset ( $_SESSION['shorty-referrer'] );
+ $tmpl->printPage();
+ } catch ( OC_Shorty_Exception $e ) { OCP\JSON::error ( array ( 'message'=>$e->getTranslation(), 'data'=>$result ) ); }
+} // switch
+
+?>
diff --git a/apps/shorty/js/add.js b/apps/shorty/js/add.js
new file mode 100644
index 00000000000..7b120d888ce
--- /dev/null
+++ b/apps/shorty/js/add.js
@@ -0,0 +1,51 @@
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file js/add.js
+ * @brief Client side desktop initialization in case of a call with an url to add
+ * @author Christian Reiner
+ */
+
+$(document).ready(function(){
+ // initialize desktop
+ var dialog = $('#dialog-add');
+ $.when(
+ Shorty.WUI.Controls.init()
+ ).pipe(function(){
+ Shorty.WUI.List.build();
+ }).done(function(){
+ $.when(
+ Shorty.WUI.Dialog.toggle(dialog)
+ ).done(function(){
+ // any referrer handed over from php (explicitly in markup) ?
+ var target=$('#controls').attr('data-referrer');
+ $('#controls').removeAttr('data-referrer');
+ dialog.find('#target').val(target);
+ dialog.find('#title').focus();
+ Shorty.WUI.Meta.collect(dialog);
+ })
+ })
+}); // document.ready
diff --git a/apps/shorty/js/debug.js b/apps/shorty/js/debug.js
new file mode 100644
index 00000000000..a45819152ee
--- /dev/null
+++ b/apps/shorty/js/debug.js
@@ -0,0 +1,71 @@
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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 wpayloadanty 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/>.
+*
+*/
+
+/**
+ * @file js/debug.js
+ * @brief Client side debugging methods
+ * The inclusion of these definitions trigger additional debug outputs
+ * @author Christian Reiner
+ */
+
+$(document).ready(function(){
+ $.extend(Shorty,{
+ Debug:{
+ log: function(issue){
+ switch (typeof(issue)){
+ case 'string':
+ console.log(issue);
+ break;
+ default:
+ console.log(this.dump(issue));
+ } // switch
+ }, // Shorty.Debuglog()
+
+ dump: function(payload,level){
+ var dumped_text = "";
+ if(!level) level = 0;
+ // some padding given at the beginning of the line
+ var level_padding = "";
+ for(var j=0;j<level+1;j++) level_padding += " ";
+
+ if(typeof(payload) == 'object') {
+ for(var item in payload) {
+ var value = payload[item];
+
+ if(typeof(value) == 'object') {
+ dumped_text += level_padding + "'" + item + "' ...\n";
+ dumped_text += dump(value,level+1);
+ } else {
+ dumped_text += level_padding + "'" + item + "' => \"" + value + "\"\n";
+ }
+ }
+ } else { //Stings/Chars/Numbers etc.
+ dumped_text = "==>"+payload+"<==("+typeof(payload)+")";
+ }
+ return dumped_text;
+ } // Shorty.Debug.dump()
+ } // Shorty.Debug
+ });
+});
diff --git a/apps/shorty/js/init.js b/apps/shorty/js/init.js
new file mode 100644
index 00000000000..3ab9fa7091a
--- /dev/null
+++ b/apps/shorty/js/init.js
@@ -0,0 +1,92 @@
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file js/init.js
+ * @brief Client side initialization of desktop actionss
+ * @author Christian Reiner
+ */
+
+$(document).ready(function(){
+ // make notification closeable
+ $('#content #notification').bind('click',Shorty.WUI.Notification.hide);
+ // button to open the 'add' dialog
+ $('#controls #add').bind('click',function(){Shorty.WUI.Dialog.toggle($('#dialog-add'))});
+ // close button in dialogs
+ $('.shorty-dialog #close').bind('click',function(){Shorty.WUI.Dialog.hide($(this).parents('form').eq(0));});
+ // status selection in embedded share dialog
+ $('.shorty-embedded#dialog-share #status').bind('change',function(){
+ Shorty.Action.Url.status($(this).siblings('#id').val(),$(this).val());
+ });
+ // button to open the tools header row in the list
+ $('#list #titlebar').bind('click',Shorty.WUI.List.Toolbar.toggle);
+ // button to reload the list
+ $('#list #toolbar').find('#reload').bind('click',Shorty.WUI.List.build);
+ // sort buttons
+ $('#list #toolbar').find('shorty-sorter').bind('click',Shorty.WUI.List.sort);
+ // add date picker options
+ $.datepicker.setDefaults({
+ dateFormat :'yy-mm-dd',
+ appendText: "(yyyy-mm-dd)",
+ changeMonth: true,
+ changeYear: true,
+ showOtherMonths: true,
+ selectOtherMonths: true,
+ showOn: 'button',
+ buttonImage: $('#controls').find('#until').eq(0).attr('icon'),
+ buttonImageOnly: true
+ });
+ $('#controls #until:not([readonly])').datepicker();
+ // bind usage to the usage icons
+ $('#dialog-share img.shorty-usage:not(.disabled)').live('click',function(e){Shorty.WUI.Entry.send(e,$(this));});
+ // bind actions to the actions icons
+ $('#list tbody .shorty-actions a').live('click',function(e){Shorty.WUI.Entry.click(e,$(this));});
+ // bind highlighting to clicks on a row, except for the action icons
+ $('#list tbody tr td:not(#actions)').live('click',function(){
+ Shorty.WUI.List.highlight($(this).parents('tr'));
+ Shorty.WUI.Dialog.hide($('.shorty-embedded').eq(0));
+ });
+ // pretty select boxes where applicable (class controlled)
+ $('.chosen').chosen();
+ // title & target filter reaction
+ $('#list thead tr#toolbar').find('th#target,th#title').find('#filter').bind('keyup',function(){
+ Shorty.WUI.List.filter(
+ $($(this).context.parentElement.parentElement).attr('id'),
+ $(this).val()
+ );
+ });
+ // status filter reaction
+ $('#list thead tr#toolbar th#status select').change(function(){
+ Shorty.WUI.List.filter(
+ $(this).parents('th').attr('id'),
+ $(this).find(':selected').val()
+ );
+ });
+ // column sorting reaction
+ $('#list thead tr#toolbar div img.shorty-sorter').bind('click',function(){
+ Shorty.WUI.List.sort($(this).attr('data-sort-code'));
+ });
+}); // document.ready
+
diff --git a/apps/shorty/js/list.js b/apps/shorty/js/list.js
new file mode 100644
index 00000000000..89f319e2e33
--- /dev/null
+++ b/apps/shorty/js/list.js
@@ -0,0 +1,39 @@
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file js/list.js
+ * @brief Client side desktop initialization for normal calls of the plugin
+ * @author Christian Reiner
+ */
+
+$(document).ready(function(){
+ // initialize desktop
+ $.when(
+ Shorty.WUI.Controls.init()
+ ).then(function(){
+ Shorty.WUI.List.build();
+ });
+}); // document.ready
diff --git a/apps/shorty/js/preferences.js b/apps/shorty/js/preferences.js
new file mode 100644
index 00000000000..4cda3ab37ac
--- /dev/null
+++ b/apps/shorty/js/preferences.js
@@ -0,0 +1,69 @@
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file js/preferences.js
+ * @brief Client side activity initialization for the user preferences dialog
+ * @author Christian Reiner
+ */
+
+$(document).ready(function(){
+ // backend preferences, activate hints for currently selected backend
+ var type=$('#shorty #backend-type').val()||'';
+ if (type.length){
+ $('#shorty #backend-'+type).show();
+ }
+ // backend 'static': initialize example that depends on backend-base system setting
+ if ($('#shorty #backend-static #backend-static-base').val().length)
+ $('#shorty #backend-static #example').text($('#shorty #backend-static #backend-static-base').val()+'<shorty id>');
+ // backend 'static': offer a clickable example link to verify the correct setup
+ $('#shorty #backend-static #example').bind('click',function(event){
+ event.preventDefault();
+ Shorty.Action.Setting.verify();
+ });
+ // react with a matching explanation and example url when backend type is chosen
+ $('.chosen').chosen();
+ $('#shorty #backend-type').change(
+ function(){
+ var type=$('#shorty #backend-type').val();
+ $('#shorty .backend-supplement').hide();
+ if (type.length){
+ $('#shorty .backend-supplement').filter('#backend-'+type).fadeIn('slow');
+ // save preference
+ Shorty.Action.Preference.set($('#shorty #backend-type').serialize());
+ return false;
+ }
+ }
+ );
+ // safe preferences
+ $('#shorty .backend-supplement').focusout(function(){
+ // save preference
+ Shorty.Action.Preference.set($(this).find('input').serialize());
+ });
+ // safe sms-control
+ $('#shorty #sms-control').change(function(){
+ Shorty.Action.Preference.set($('#shorty #sms-control').serialize());
+ });
+});
diff --git a/apps/shorty/js/settings.js b/apps/shorty/js/settings.js
new file mode 100644
index 00000000000..254ab4f0c13
--- /dev/null
+++ b/apps/shorty/js/settings.js
@@ -0,0 +1,53 @@
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file js/settings.js
+ * @brief Client side activity initialization for the system settings dialog
+ * @author Christian Reiner
+ */
+
+$(document).ready(function(){
+ // initialize example that depends on backend-base
+ if ($('#shorty #backend-static-base').val().length)
+ $('#shorty #backend-static #example').text($('#shorty #backend-static-base').val()+'<shorty id>');
+ // modify example upon input of a base
+ $('#shorty #backend-static-base').bind('input',function(){
+ $('#shorty #backend-static #example').text($('#shorty #backend-static-base').val()+'<shorty id>');
+ });
+ // backend 'static': offer a clickable example link to verify the correct setup
+ $('#shorty #backend-static #example').bind('click',function(event){
+ event.preventDefault();
+ Shorty.Action.Setting.verify();
+ });
+ // store setting
+ $('#shorty #backend-static-base').focusout(function(){
+ // modify example
+ $('#shorty #backend-static #example').text($('#shorty #backend-static-base').val()+'<shorty id>');
+ // save setting
+ Shorty.Action.Setting.set($('#shorty #backend-static-base').serialize());
+ return false;
+ });
+});
diff --git a/apps/shorty/js/shorty.js b/apps/shorty/js/shorty.js
new file mode 100644
index 00000000000..688c3e6e158
--- /dev/null
+++ b/apps/shorty/js/shorty.js
@@ -0,0 +1,1489 @@
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file js/shorty.js
+ * @brief Client side activity library
+ * @author Christian Reiner
+ */
+
+// max()-selector
+// usage: var maxWidth = $("a").max(function() {return $(this).width(); });
+$.fn.max = function(selector) {
+ return Math.max.apply(null, this.map(function(index, el) { return selector.apply(el); }).get() );
+}
+// min()-selector
+// usage: var minWidth = $("a").min(function() {return $(this).width(); });
+$.fn.min = function(selector) {
+ return Math.min.apply(null, this.map(function(index, el) { return selector.apply(el); }).get() );
+}
+
+/**
+ * @class Shorty
+ * @brief Central activity library for the client side
+ * @author Christian Reiner
+ */
+Shorty =
+{
+ // ===== Shorty.WUI =====
+ WUI:
+ {
+ // ===== Shorty.WUI.Controls =====
+ Controls:
+ {
+ // ===== Shorty.WUI.Controls.init =====
+ init: function(){
+ if (Shorty.Debug) Shorty.Debug.log("init controls");
+ var dfd = new $.Deferred();
+ $.when(
+ Shorty.WUI.Controls.toggle(),
+ Shorty.WUI.Sums.fill()
+ ).done(dfd.resolve)
+ return dfd.promise();
+ }, // Shorty.WUI.Controls.init
+ // ===== Shorty.WUI.Controls.toggle =====
+ toggle: function(){
+ if (Shorty.Debug) Shorty.Debug.log("toggle controls");
+ var dfd = new $.Deferred();
+ Shorty.WUI.Notification.hide();
+ // show or hide dialog
+ var controls = $('#controls');
+ if ( ! controls.is(':visible')){
+ $.when(
+ $.when(
+ controls.slideDown('slow')
+ ).done(Shorty.WUI.Sums.fill)
+ ).done(dfd.resolve)
+ }else{
+ $.when(
+ controls.slideUp('fast')
+ ).done(dfd.resolve)
+ }
+ return dfd.promise();
+ }, // Shorty.WUI.Controls.toggle
+ }, // Shorty.WUI.Controls
+ // ===== Shorty.WUI.Desktop =====
+ Desktop:
+ {
+ // ===== Shorty.WUI.Desktop.show =====
+ show: function(duration){
+ if (Shorty.Debug) Shorty.Debug.log("show desktop");
+ duration = duration || 'slow';
+ var dfd = new $.Deferred();
+ $.when($('#desktop').fadeTo(duration,1.0)).done(dfd.resolve)
+ return dfd.promise();
+ }, // Shorty.WUI.Desktop.show
+ // ===== Shorty.WUI.Desktop.hide =====
+ hide: function(duration){
+ if (Shorty.Debug) Shorty.Debug.log("hide desktop");
+ duration = duration || 'slow';
+ var dfd = new $.Deferred();
+ $.when($('#desktop').fadeTo(duration,0.3)).done(dfd.resolve)
+ return dfd.promise();
+ }, // Shorty.WUI.Desktop.hide
+ }, // Shorty.WUI.Desktop
+ // ===== Shorty.WUI.Dialog =====
+ Dialog:
+ {
+ // ===== Shorty.WUI.Dialog.execute =====
+ execute: function(dialog){
+ if (Shorty.Debug) Shorty.Debug.log("execute dialog "+dialog.attr('id'));
+ var dfd = new $.Deferred();
+ switch ( dialog.attr('id') ){
+ case 'dialog-add':
+ $.when(
+ Shorty.WUI.Notification.hide(),
+ Shorty.Action.Url.add()
+ ).done(dfd.resolve)
+ break;
+ case 'dialog-edit':
+ $.when(
+ Shorty.WUI.Notification.hide(),
+ Shorty.Action.Url.edit()
+ ).done(dfd.resolve)
+ break;
+ case 'dialog-del':
+ $.when(
+ Shorty.WUI.Notification.hide(),
+ Shorty.Action.Url.del()
+ ).done(dfd.resolve)
+ break;
+ default:
+ dfd.resolve();
+ } // switch
+ return dfd.promise();
+ }, // Shorty.WUI.Dialog.execute
+ // ===== Shorty.WUI.Dialog.hide =====
+ hide: function(dialog){
+ if (Shorty.Debug) Shorty.Debug.log("hide dialog "+dialog.attr('id'));
+ var duration = 'slow';
+ var dfd = new $.Deferred();
+ if (!dialog.is(':visible'))
+ dfd.resolve();
+ else{
+ $.when(
+ dialog.slideUp(duration)
+ ).pipe(function(){
+ switch ( dialog.attr('id') ){
+ case 'dialog-add':
+ dialog.find('#confirm').unbind('click');
+ dialog.find('#target').unbind('focusout');
+ break;
+ default:
+ } // switch
+ }).pipe(function(){
+ if (dialog.hasClass('shorty-standalone'))
+ Shorty.WUI.Desktop.show();
+ }).done(dfd.resolve)
+ }
+ return dfd.promise();
+ }, // Shorty.WUI.Dialog.hide
+ // ===== Shorty.WUI.Dialog.reset =====
+ reset: function(dialog){
+ if (Shorty.Debug) Shorty.Debug.log("reset dialog "+dialog.attr('id'));
+ var dfd = new $.Deferred();
+ if (dialog){
+ // reset dialog fields
+ $.when(
+ $.each(dialog.find('#status'),function(){
+ if($(this).is('[data]'))
+ $(this).val($(this).attr('data'));
+ else $(this).val('');
+ }),
+ $.each(dialog.find('input,textarea'),function(){
+ if($(this).is('[data]'))
+ $(this).val($(this).attr('data')).attr('placeholder',$(this).attr('data'));
+ else $(this).val('').attr('placeholder','');
+ }),
+ $.each(dialog.find('.shorty-value'),function(){
+ if($(this).is('[data]'))
+ $(this).text($(this).attr('data'));
+ else $(this).text('');
+ }),
+ $.each(dialog.find('.shorty-icon'), function(){
+ if($(this).is('[data]'))
+ $(this).attr('src',$(this).attr('data'));
+ else $(this).attr('src','');
+ }),
+ Shorty.WUI.Dialog.sharpen(dialog,false)
+ ).done(dfd.resolve)
+ }
+ else
+ dfd.resolve();
+ return dfd.promise();
+ }, // Shorty.WUI.Dialog.reset
+ // ===== Shorty.WUI.Dialog.sharpen =====
+ sharpen: function(dialog,sharpness){
+ if (Shorty.Debug) Shorty.Debug.log("toggle sharpness of dialog '"+dialog.attr('id')+"' to "+sharpness);
+ var confirm=dialog.find('#confirm');
+ if (sharpness){
+ $('#dialog-add #busy').fadeOut('slow');
+ confirm.unbind('click');
+ confirm.bind('click',{dialog: dialog}, function(event){event.preventDefault();Shorty.WUI.Dialog.execute(event.data.dialog);});
+ confirm.addClass('sharp');
+ }else{
+ confirm.unbind('click');
+ confirm.bind('click',function(event){event.preventDefault();dialog.find('#target').effect('highlight',{'color':'#CCCCCC'},500);});
+ confirm.removeClass('sharp');
+ }
+ }, // Shorty.WUI.Dialog.sharpen
+ // ===== Shorty.WUI.Dialog.show =====
+ show: function(dialog){
+ if (Shorty.Debug) Shorty.Debug.log("show dialog "+dialog.attr('id'));
+ var duration = 'slow';
+ var dfd = new $.Deferred();
+ if (dialog.is(':visible'))
+ // dialog already open, nothing to do...
+ dfd.resolve();
+ else{
+ $('#content form.shorty-dialog').each(function(){
+ Shorty.WUI.Dialog.hide($(this));
+ });
+ // hide 'old' notifications
+ Shorty.WUI.Notification.hide(),
+ // some preparations
+ $.when(
+ function(){
+ var dfd = new $.Deferred();
+ if (dialog.hasClass('shorty-standalone'))
+ $.when(Shorty.WUI.Desktop.hide()).done(dfd.resolve)
+ else dfd.resolve();
+ return dfd.promise();
+ }()
+ ).pipe(function(){
+ // prevent submission before entering anything
+ Shorty.WUI.Dialog.sharpen(dialog,false);
+ // show dialog
+ dialog.slideDown(duration);
+ }).pipe(function(){
+ // initialize dialog actions
+ switch(dialog.attr('id')){
+ case 'dialog-add':
+ dialog.find('#target').focus();
+ dialog.find('#target').bind('focusout', {dialog: dialog}, function(event){Shorty.WUI.Meta.collect(event.data.dialog);});
+ break;
+ case 'dialog-edit':
+ dialog.find('#title').focus();
+ Shorty.WUI.Dialog.sharpen(dialog,true);
+ break;
+ } // switch
+ }).done(dfd.resolve)
+ }
+ return dfd.promise();
+ }, // Shorty.WUI.Dialog.show
+ // ===== Shorty.WUI.Dialog.toggle =====
+ toggle: function(dialog){
+ if (Shorty.Debug) Shorty.Debug.log("toggle dialog "+dialog.attr('id'));
+ var dfd = new $.Deferred();
+ Shorty.WUI.Notification.hide();
+ // show or hide dialog
+ if ( ! dialog.is(':visible'))
+ $.when(Shorty.WUI.Dialog.show(dialog)).done(dfd.resolve)
+ else
+ $.when(Shorty.WUI.Dialog.hide(dialog)).done(dfd.resolve)
+ return dfd.promise();
+ }, // Shorty.WUI.Dialog.toggle
+ // ===== Shorty.WUI.Dialog.wipe =====
+ wipe: function(dialog){
+ if (Shorty.Debug) Shorty.Debug.log("wipe dialog "+dialog.attr('id'));
+ var dfd = new $.Deferred();
+ if (dialog){
+ // wipe dialog fields
+ $.when(
+ $.each(dialog.find('#status'), function(){$(this).attr('data','blocked');$(this).val('blocked');}),
+ $.each(dialog.find('input'), function(){$(this).attr('data','');$(this).val('');}),
+ $.each(dialog.find('textarea'), function(){$(this).attr('data','');$(this).val('');}),
+ $.each(dialog.find('.shorty-value'),function(){$(this).attr('data','');$(this).text('');}),
+ $.each(dialog.find('.shorty-icon'), function(){$(this).attr('data','');$(this).attr('src','');}),
+ Shorty.WUI.Dialog.sharpen(dialog,false)
+ ).done(dfd.resolve)
+ }
+ else
+ dfd.resolve();
+ return dfd.promise();
+ }, // Shorty.WUI.Dialog.wipe
+ }, // Shorty.WUI.Dialog
+ // ===== Shorty.WUI.Entry =====
+ Entry:
+ {
+ // ===== Shorty.WUI.Entry.click =====
+ click: function(event,element){
+ var dfd = new $.Deferred();
+ var entry=element.parents('tr').eq(0);
+ if (Shorty.Debug) Shorty.Debug.log(event.type+" on action "+element.attr('id')+" for entry "+entry.attr('id'));
+ //
+ if ($('.shorty-dialog').is(':visible'))
+ $('.shorty-dialog').each(function(){Shorty.WUI.Dialog.hide($(this));});
+ else{
+ // highlight clicked row as active entry
+ $.when(
+ Shorty.WUI.List.highlight(entry)
+ ).pipe(function(){
+ if ('click'==event.type){
+ switch(element.attr('id')){
+ case 'del': Shorty.WUI.Entry.del(entry); break;
+ case 'edit': Shorty.WUI.Entry.edit(entry); break;
+ case 'open': Shorty.Action.Url.forward(entry); break;
+ case 'share': Shorty.WUI.Entry.share(entry); break;
+ case 'show': Shorty.WUI.Entry.show(entry); break;
+ } // switch
+ } // if click
+ }).done(dfd.resolve)
+ } // else
+ return dfd.promise();
+ }, // Shorty.WUI.Entry.click
+ // ===== Shorty.WUI.Entry.del =====
+ del: function(entry){
+ if (Shorty.Debug) Shorty.Debug.log("delete entry "+entry.attr('id'));
+ if (entry.hasClass('deleted')){
+ // change status to deleted
+ Shorty.Action.Url.status(entry.attr('data-id'),'blocked');
+ // mark row as undeleted
+ entry.removeClass('deleted');
+ }else{
+ // change status to deleted
+ Shorty.Action.Url.status(entry.attr('data-id'),'deleted');
+ // mark row as deleted
+ entry.addClass('deleted');
+ }
+ }, // Shorty.WUI.Entry.del
+ // ===== Shorty.WUI.Entry.edit =====
+ edit: function(entry){
+ if (Shorty.Debug) Shorty.Debug.log("modify entry "+entry.attr('id'));
+ var dfd = new $.Deferred();
+ // use the existing edit dialog for this
+ var dialog=$('#controls #dialog-edit');
+ // load entry into dialog
+ dialog.find('#id').val(entry.attr('data-id'));
+ dialog.find('#status').val(entry.attr('data-status')||'');
+ dialog.find('#source').val(entry.attr('data-source'||''));
+ dialog.find('#target').val(entry.attr('data-target'||''));
+ dialog.find('#title').val(entry.attr('data-title')||'');
+ dialog.find('#clicks').val(entry.attr('data-clicks')||'');
+ dialog.find('#created').val(entry.attr('data-created')||'');
+ dialog.find('#accessed').val(entry.attr('data-accessed')||'');
+ dialog.find('#notes').val(entry.attr('data-notes')||'');
+// dialog.find('#until').datepicker('setDate',new Date(entry.attr('data-until'))||'');
+ dialog.find('#until').datepicker('setDate',new Date(entry.attr('data-until'))||'')
+ .datepicker('refresh');
+ // open edit dialog
+ Shorty.WUI.Dialog.show(dialog)
+ $.when(
+ Shorty.WUI.Meta.get(entry.attr('data-target'))
+ ).pipe(function(response){
+ var meta=response.data;
+ if (meta.final)
+ dialog.find('#target').val(meta.final);
+ dialog.find('#title').attr('placeholder',meta.title);
+ dialog.find('#meta').fadeTo('fast',0,function(){
+ Shorty.WUI.Meta.reset(dialog);
+ // specify the icons and information to be shown as meta data
+ dialog.find('#staticon').attr('src',meta.staticon);
+ dialog.find('#schemicon').attr('src',meta.schemicon);
+ dialog.find('#favicon').attr('src',meta.favicon);
+ dialog.find('#mimicon').attr('src',meta.mimicon);
+ dialog.find('#explanation').html(meta.title?meta.title:'[ '+meta.explanation+' ]');
+ dialog.find('#meta').fadeTo('fast',1);
+ })
+ }).done(function(){
+ Shorty.WUI.Dialog.sharpen(dialog,true);
+ dfd.resolve();
+ }).fail(function(){
+ Shorty.WUI.Dialog.sharpen(dialog,false);
+ dfd.reject();
+ })
+ return dfd.promise();
+ }, // Shorty.WUI.Entry.edit
+ // ===== Shorty.WUI.Entry.send =====
+ send: function(event,element){
+ var dfd = new $.Deferred();
+ var action=element.attr('id');
+ var entry=element.parents('tr');
+ if (Shorty.Debug) Shorty.Debug.log("send action "+action+" on entry "+entry.attr('data-id'));
+ // take action
+ $.when(
+ Shorty.Action.Url.send(action,entry)
+ ).done(dfd.resolve)
+ return dfd.promise();
+ }, // Shorty.WUI.Entry.send
+ // ===== Shorty.WUI.Entry.share =====
+ share: function(entry){
+ if (Shorty.Debug) Shorty.Debug.log("share entry "+entry.attr('id'));
+ var dfd = new $.Deferred();
+ // use the existing 'share' dialog for this
+ var dialog=$('#dialog-share');
+ // fill and show dialog
+ dialog.find('#id').val(entry.attr('data-id'))
+ .attr('data',entry.attr('data-id'));
+ dialog.find('#source').attr('href',entry.attr('data-source'))
+ .text(entry.attr('data-source'));
+ dialog.find('#relay').attr('href',entry.attr('data-relay'))
+ .text(entry.attr('data-relay'));
+ dialog.find('#target').attr('href',entry.attr('data-target'))
+ .text(entry.attr('data-target'));
+ dialog.find('#status').attr('value',entry.attr('data-status'))
+ .attr('data',entry.attr('data-status'));
+ // move 'share' dialog towards entry
+ dialog.appendTo(entry.find('td#actions')),
+ // open dialog
+ $.when(
+ Shorty.WUI.Dialog.show(dialog)
+ ).done(dfd.resolve)
+ return dfd.promise();
+ }, // Shorty.WUI.Entry.share
+ // ===== Shorty.WUI.Entry.show =====
+ show: function(entry){
+ if (Shorty.Debug) Shorty.Debug.log("show entry "+entry.attr('id'));
+ var dfd = new $.Deferred();
+ // use the existing edit dialog for this
+ var dialog=$('#controls #dialog-show');
+ // load entry into dialog
+ dialog.find('#id').attr('data-id',entry.attr('data-id')).val(entry.attr('data-id'));
+ dialog.find('#status').attr('data-status',entry.attr('data-status')||'').val(t('shorty',entry.attr('data-status'))||'');
+ dialog.find('#source').attr('data-source',entry.attr('data-source')).val(entry.attr('data-source'));
+ dialog.find('#target').attr('data-target',entry.attr('data-target')).val(entry.attr('data-target'));
+ dialog.find('#title').attr('data-title',entry.attr('data-title')).val(entry.attr('data-title'));
+ dialog.find('#until').attr('data-until',entry.attr('data-until')||'').val(entry.attr('data-until')||'');
+ dialog.find('#clicks').attr('data-clicks',entry.attr('data-clicks')||'').val(entry.attr('data-clicks')||'');
+ dialog.find('#created').attr('data-created',entry.attr('data-created')||'').val(entry.attr('data-created')||'');
+ dialog.find('#accessed').attr('data-accessed',entry.attr('data-accessed')||'').val(entry.attr('data-accessed')||'');
+ dialog.find('#notes').attr('data-notes',entry.attr('data-notes')).val(entry.attr('data-notes'));
+ // open edit dialog
+ Shorty.WUI.Dialog.show(dialog)
+ $.when(
+ Shorty.WUI.Meta.get(entry.attr('data-target'))
+ ).pipe(function(response){
+ var meta=response.data;
+ if (meta.final)
+ dialog.find('#target').val(meta.final);
+ dialog.find('#title').attr('placeholder',meta.title);
+ dialog.find('#meta').fadeTo('fast',0,function(){
+ Shorty.WUI.Meta.reset(dialog);
+ // specify the icons and information to be shown as meta data
+ dialog.find('#staticon').attr('src',meta.staticon);
+ dialog.find('#schemicon').attr('src',meta.schemicon);
+ dialog.find('#favicon').attr('src',meta.favicon);
+ dialog.find('#mimicon').attr('src',meta.mimicon);
+ dialog.find('#explanation').html(meta.title?meta.title:'[ '+meta.explanation+' ]');
+ dialog.find('#meta').fadeTo('fast',1);
+ })
+ }).done(function(){
+ dfd.resolve();
+ }).fail(function(){
+ dfd.reject();
+ })
+ return dfd.promise();
+ } // Shorty.WUI.Entry.show
+ }, // Shorty.WUI.Entry
+ // ===== Shorty.WUI.Hourglass =====
+ // Shorty.WUI.Hourglass
+ Hourglass:
+ {
+ // ===== Shorty.WUI.Hourglass.toggle =====
+ toggle: function(show){
+ if (Shorty.Debug) Shorty.Debug.log("toggle hourglass to "+show?"true":"false");
+ var dfd = new $.Deferred();
+ var hourglass = $('#desktop .shorty-hourglass');
+ if (show){
+ if (hourglass.is(':visible'))
+ dfd.resolve();
+ else
+ $.when(
+ hourglass.fadeIn('fast')
+ ).done(dfd.resolve)
+ }else{
+ if (!hourglass.is(':visible'))
+ dfd.resolve();
+ else
+ $.when(
+ hourglass.fadeOut('slow')
+ ).done(dfd.resolve)
+ }
+ return dfd.promise();
+ }, // Shorty.WUI.Hourglass.toggle
+ }, // Shorty.WUI.Hourglass
+ // ===== Shorty.WUI.List =====
+ List:
+ {
+ // ===== Shorty.WUI.List.add =====
+ add: function(list,hidden){
+ if (Shorty.Debug) Shorty.Debug.log("add entry to list holding "+list.length+" entries");
+ var dfd = new $.Deferred();
+ // insert list elements (sets) one by one
+ var row,set;
+ $.each(list,function(i,set){
+ // clone dummy row from list header: dummy is the last row
+ row = $('#desktop #list thead tr:last-child').eq(0).clone();
+ // set row id to entry id
+ row.attr('id',set.id);
+ // add attributes to row, as data and value
+ $.each(['id','status','title','source','relay','target','clicks','created','accessed','until','notes','favicon'],
+ function(j,aspect){
+ if (hidden)
+ row.addClass('shorty-fresh'); // might lead to a pulsate effect later
+ // we wrap the cells content into a span tag
+ var span=$('<span>');
+ // enhance row with real set values
+ if ('undefined'==set[aspect])
+ row.attr('data-'+this,'');
+ else row.attr('data-'+this,set[aspect]);
+ // fill data into corresponsing column
+ var title, content, classes=[];
+ switch(aspect)
+ {
+ case 'favicon':
+ span.html('<img class="shorty-icon" width="16" src="'+set[aspect]+'">');
+ break;
+ case 'until':
+ if (null==set[aspect])
+ span.text('-never-');
+ else{
+ span.text(set[aspect]);
+ if (Shorty.Date.expired(set[aspect]))
+ row.addClass('shorty-expired');
+ }
+ break;
+ case 'title':
+ span.text(set[aspect]);
+ span.addClass('ellipsis');
+ break;
+ case 'target':
+ span.text(set[aspect]);
+ span.attr('title',set[aspect]);
+ span.addClass('ellipsis');
+ break;
+ case 'status':
+ if ('deleted'==set[aspect])
+ row.addClass('deleted');
+// span.text(set[aspect]);
+ span.text(t('shorty',set[aspect]));
+ break;
+ default:
+ span.text(set[aspect]);
+ } // switch
+ row.find('td#'+aspect).empty().append(span);
+ }) // each aspect
+ // insert new row in table
+ $('#desktop #list tbody').prepend(row);
+ }) // each
+ return dfd.promise();
+ }, // Shorty.WUI.List.add
+ // ===== Shorty.WUI.List.build =====
+ build: function()
+ {
+ if (Shorty.Debug) Shorty.Debug.log("build list");
+ var dfd = new $.Deferred();
+ // prepare loading
+ $.when(
+ Shorty.WUI.Hourglass.toggle(true),
+ Shorty.WUI.List.dim(false)
+ ).done(function(){
+ // retrieve new entries
+ $.when(
+ Shorty.WUI.List.get()
+ ).pipe(function(response){
+ Shorty.WUI.List.fill(response.data);
+ }).done(function(){
+ $.when(
+ Shorty.WUI.List.show(),
+ Shorty.WUI.List.dim(true)
+ ).always(function(){
+ Shorty.WUI.Hourglass.toggle(false)
+ dfd.resolve();
+ })
+ }).fail(function(){
+ dfd.reject();
+ })
+ })
+ return dfd.promise();
+ }, // Shorty.WUI.List.build
+ // ===== Shorty.WUI.List.dim =====
+ dim: function(show){
+ if (Shorty.Debug) Shorty.Debug.log("dim list to "+(show?"true":"false"));
+ var duration='slow';
+ var dfd =new $.Deferred();
+ var list=$('#desktop #list');
+ var body=list.find('tbody');
+ if (show)
+ {
+ var rows=body.find('tr.shorty-fresh');
+ Shorty.WUI.List.highlight(rows.eq(0));
+ rows.each(function(){
+ $(this).removeClass('shorty-fresh');
+ $(this).find('td').effect('pulsate');
+ });
+ $.when(
+ Shorty.WUI.List.vacuum(),
+ body.fadeIn(duration)
+ ).done(dfd.resolve)
+ }else{
+ if (!body.is(':visible'))
+ dfd.resolve();
+ else
+ {
+ $.when(
+ body.fadeOut(duration)
+ ).done(dfd.resolve)
+ }
+ }
+ return dfd.promise();
+ }, // Shorty.WUI.List.dim
+ // ===== Shorty.WUI.List.empty =====
+ empty: function(){
+ if (Shorty.Debug) Shorty.Debug.log("empty list");
+ var dfd = new $.Deferred();
+ // move embedded dialogs back to their safe place in the controls
+ $('.shorty-embedded').appendTo($('#controls #dialog-show'));
+ // remove all rows, one by one
+ $.when(
+ $('#desktop').find('#list tbody tr').each(function(){
+ if(''!=$(this).attr('id'))
+ $(this).remove();
+ })
+ ).done(dfd.resolve)
+ return dfd.promise();
+ }, // Shorty.WUI.List.empty
+ // ===== Shorty.WUI.List.fill =====
+ fill: function(list){
+ if (Shorty.Debug) Shorty.Debug.log("fill list");
+ var dfd = new $.Deferred();
+ $.when(
+ Shorty.WUI.Sums.fill(),
+ Shorty.WUI.List.empty(),
+ Shorty.WUI.List.add(list,false)
+ ).pipe(
+ // filter list
+ Shorty.WUI.List.filter('target',$('#list thead tr#toolbar th#target #filter').val()),
+ Shorty.WUI.List.filter('title', $('#list thead tr#toolbar th#title #filter').val()),
+ Shorty.WUI.List.filter('status',$('#list thead tr#toolbar th#status select :selected').val())
+ ).pipe(
+ // sort list
+ $.when(
+ Shorty.Action.Preference.get('list-sort-code')
+ ).done(function(pref){
+ Shorty.WUI.List.sort(pref['list-sort-code']);
+ })
+ ).done(dfd.resolve)
+ return dfd.promise();
+ }, // Shorty.WUI.List.fill
+ // ===== Shorty.WUI.List.filter =====
+ filter: function(column,pattern){
+ if (Shorty.Debug) Shorty.Debug.log("filter list by column "+column);
+ var dfd = new $.Deferred();
+ $.when(
+ $('#list tbody tr').filter(function(){
+ return (-1==$(this).find('td#'+column+' span').text().toLowerCase().indexOf(pattern.toLowerCase()));
+ }).addClass('shorty-filtered'),
+ $('#list tbody tr').not(function(){
+ return (-1==$(this).find('td#'+column+' span').text().toLowerCase().indexOf(pattern.toLowerCase()));
+ }).removeClass('shorty-filtered')
+ ).done(dfd.resolve)
+ return dfd.promise();
+ }, // Shorty.WUI.List.filter
+ // ===== Shorty.WUI.List.get =====
+ get: function(){
+ if (Shorty.Debug) Shorty.Debug.log("get list");
+ var dfd = new $.Deferred();
+ $.when(
+ $.ajax({
+ type: 'GET',
+ url: OC.filePath('shorty','ajax','list.php'),
+ cache: false
+ }).pipe(
+ function(response){return Shorty.Ajax.eval(response)},
+ function(response){return Shorty.Ajax.fail(response)}
+ )
+ ).done(function(response){
+ dfd.resolve(response);
+ }).fail(function(response){
+ dfd.reject(response);
+ })
+ return dfd.promise();
+ }, // Shorty.WUI.List.get
+ // ===== Shorty.WUI.List.hide =====
+ hide: function(duration){
+ if (Shorty.Debug) Shorty.Debug.log("hide list");
+ duration = 'slow';
+ var dfd = new $.Deferred();
+ var list = $('#desktop #list');
+ if ( ! list.is(':visible'))
+ dfd.resolve();
+ else
+ {
+ $.when(
+ list.fadeOut(duration)
+ ).done(dfd.resolve)
+ }
+ return dfd.promise();
+ }, // Shorty.WUI.List.hide
+ // ===== Shorty.WUI.List.highlight =====
+ highlight: function(entry){
+ if (Shorty.Debug) Shorty.Debug.log("highlighting list entry "+entry.attr('id'));
+ var dfd = new $.Deferred();
+ // close any open embedded dialog
+ $.when(
+ Shorty.WUI.Dialog.hide($('.shorty-dialog'))
+ ).pipe(function(){
+ // neutralize all rows that might have been highlighted
+ $('#desktop #list tr').removeClass('clicked');
+ entry.addClass('clicked');
+ }).always(dfd.resolve);
+ return dfd.promise();
+ }, // Shorty.WUI.List.highlight
+ // ===== Shorty.WUI.List.modify =====
+ modify: function(list,hidden){
+ if (Shorty.Debug) Shorty.Debug.log("modify entry in list holding "+list.length+" entries");
+ var dfd = new $.Deferred();
+ // modify list elements (sets) one by one
+ var row,set;
+ $.each(list,function(i,set){
+ // select row from list by id
+ row=$('#desktop #list tbody tr#'+set.id);
+ // modify attributes in row, as data and value
+ $.each(['status','title','until','notes'],
+ function(j,aspect){
+ if (set[aspect]){
+ // enhance row with actual set values
+ row.attr('data-'+this,set[aspect]);
+ if (hidden) row.addClass('shorty-fresh');
+ // fill data into corresponsing column
+ var content, classes=[];
+ switch(aspect)
+ {
+ case 'until':
+ if (null==set[aspect])
+ content='-never-';
+ else{
+ content=set[aspect];
+ if (Shorty.Date.expired(set[aspect]))
+ row.addClass('shorty-expired');
+ }
+ break;
+ case 'title':
+ classes.push('ellipsis');
+ content=set[aspect];
+ break;
+ case 'status':
+ if ('deleted'==set[aspect])
+ row.addClass('deleted');
+ content=set[aspect];
+ break;
+ default:
+ content=set[aspect];
+ } // switch
+ // show modified column immediately or keep it for a later pulsation effect ?
+ row.find('td').filter('#'+aspect).html('<span class="'+classes.join(' ')+'">'+content+'</span>');
+ } // if aspect
+ }) // each aspect
+ }) // each entry
+ return dfd.resolve().promise();
+ }, // Shorty.WUI.List.modify
+ // ===== Shorty.WUI.List.show =====
+ show: function(duration){
+ if (Shorty.Debug) Shorty.Debug.log("show list");
+ duration = 'slow';
+ var dfd = new $.Deferred();
+ var list = $('#desktop #list');
+ if (list.is(':visible'))
+ dfd.resolve();
+ else
+ {
+ // list currently not visible, show it
+ $.when(
+ list.find('tbody').show(),
+ list.fadeIn(duration)
+ ).done(function(){
+ dfd.resolve();
+ Shorty.WUI.List.vacuum();
+ })
+ }
+ return dfd.promise();
+ }, // Shorty.WUI.List.show
+ // ===== Shorty.WUI.List.sort =====
+ sort: function(sortCode){
+ sortCore = sortCode || 'cd';
+ var icon=$('#list thead tr#toolbar th div img[data-sort-code="'+sortCode+'"]');
+ var sortCol=icon.parents('th').attr('id');
+ var sortDir=icon.attr('data-sort-direction');
+ if (Shorty.Debug) Shorty.Debug.log("sorting list column "+sortCol+" "+(sortDir=='asc'?'ascending':'descending'));
+ // use the 'tinysort' jquery plugin for sorting
+ switch (sortCol){
+ case 'until':
+ $('#list tbody>tr').tsort('td#until',{order:sortDir});
+ break;
+ default:
+ $('#list tbody>tr').tsort({attr:'data-'+sortCol,order:sortDir});
+ } // switch
+ // mark currently active sort icon
+ var icons=$('#list thead tr#toolbar img.shorty-sorter');
+ icons.removeClass('shorty-active');
+ icons.filter('[data-sort-code="'+sortCode+'"]').addClass('shorty-active');
+ // store the sorting code as preference, for returning list retrievals
+ Shorty.Action.Preference.set({'list-sort-code':sortCode});
+ }, // Shorty.WUI.List.sort
+ // ===== Shorty.WUI.List.toggle =====
+ toggle: function(duration){
+ if (Shorty.Debug) Shorty.Debug.log("toggle list");
+ duration = 'slow';
+ var dfd = new $.Deferred();
+ if (list.is(':visible'))
+ return Shorty.WUI.List.hide();
+ else
+ return Shorty.WUI.List.show();
+ }, // Shorty.WUI.List.toggle
+ // ===== Shorty.WUI.List.vacuum =====
+ vacuum: function(){
+ if (Shorty.Debug) Shorty.Debug.log("vacuum list");
+ // list is empty if no row exists
+ if (0!=$('#list tbody').find('tr').length)
+ $('#vacuum').fadeOut('fast');
+ else
+ $('#vacuum').fadeIn('slow');
+ }, // Shorty.WUI.List.vacuum
+ // ===== Shorty.WUI.List.Toolbar =====
+ Toolbar:
+ {
+ // ===== Shorty.WUI.List.Toolbar.toggle =====
+ toggle: function(duration){
+ if (Shorty.Debug) Shorty.Debug.log("toggle list toolbar");
+ duration = duration || 'slow';
+ var button=$('#list #tools');
+ var toolbar=$('#list #toolbar');
+ var dfd = new $.Deferred();
+ if (!toolbar.find('div').is(':visible')){
+ // tool NOT visible: open toolbar
+ $.when(
+ toolbar.find('div').slideDown(duration)
+ ).pipe(
+ button.attr('src',button.attr('data-minus'))
+ ).done(dfd.resolve)
+ }else{ // toolbar IS visible
+ // any filters active? prevent closing of toolbar !
+ if ( ( (toolbar.find('th#title,#target').find('div input#filter:[value!=""]').length)
+ &&(toolbar.find('th#title,#target').find('div input#filter:[value!=""]').effect('pulsate')) )
+ ||( (toolbar.find('th#status select :selected').val())
+// &&(toolbar.find('#status div.chzn-container').effect('pulsate')) )
+ &&(toolbar.find('#status').effect('pulsate')) )
+ ) {
+ if (Shorty.Debug) Shorty.Debug.log('active filter prevents closing of toolbar');
+ }else{
+ // close toolbar
+ $.when(
+ toolbar.find('div').slideUp(duration)
+ ).pipe(
+ button.attr('src',button.attr('data-plus'))
+ ).done(dfd.resolve)
+ }
+ }
+ return dfd.promise();
+ }, // Shorty.WUI.List.Toolbar.toggle
+ }, // Shorty.WUI.List.Toolbar
+ }, // Shorty.WUI.List
+ // ===== Shorty.WUI.Notification =====
+ Notification:
+ {
+ // ===== Shorty.WUI.Notification.hide =====
+ hide: function(){
+ if (Shorty.Debug) Shorty.Debug.log("hide notification");
+ var dfd = new $.Deferred();
+ $.when(
+ $('#notification').slideUp('fast')
+ ).pipe(function(){
+ $('#notification').text('');
+ }).done(dfd.resolve)
+ return dfd.promise();
+ }, // Shorty.WUI.Notification.hide
+ // ===== Shorty.WUI.Notification.show =====
+ show: function(message,level){
+ if (Shorty.Debug) Shorty.Debug.log("show notification with level "+level);
+ level = level || 'info';
+ var dfd = new $.Deferred();
+ var duration = 'slow';
+ var notification = $('#notification');
+ if (message && message.length){
+ $.when(
+ notification.slideUp('fast')
+ ).done(function(){
+ switch(level){
+ case 'debug':
+ // detect debug mode by checking, of function 'debug()' exists
+ if ( Shorty.Debug ){
+ Shorty.Debug.log('Debug: '+message);
+ $.when(
+ notification.attr('title', 'debug message'),
+ notification.text('Debug: '+message),
+ notification.slideDown(duration)
+ ).done(dfd.resolve)
+ }
+ else
+ dfd.resolve();
+ break;
+ case 'error':
+ if (Shorty.Debug)
+ Shorty.Debug.log('Error: '+message);
+ $.when(
+ notification.attr('title', 'error message'),
+ notification.text('Error: ' + message),
+ notification.slideDown(duration)
+ ).done(dfd.resolve)
+ break;
+ default: // 'info'
+ if ( message.length ){
+ if (Shorty.Debug)
+ Shorty.Debug.log('Info: '+message);
+ $.when(
+ notification.text(message),
+ notification.slideDown(duration)
+ ).done(dfd.resolve)
+ }else{
+ $.when(
+ notification.text('')
+ ).done(dfd.resolve)
+ }
+ } // switch
+ })
+ } // if message
+ return dfd.promise();
+ }, // Shorty.WUI.Notification.show
+ }, // Shorty.WUI.Notification
+ // ===== Shorty.WUI.Meta: =====
+ Meta:
+ {
+ // ===== Shorty.WUI.Meta.collect =====
+ collect: function(dialog){
+ if (Shorty.Debug) Shorty.Debug.log("collect meta data");
+ var dfd = new $.Deferred();
+ var target = $('#dialog-add #target').val().trim();
+ // don't bother getting active on empty input
+ if ( ! target.length ){
+ dialog.find('#target').focus();
+ dfd.resolve();
+ return dfd.promise();
+ }
+ // start expressing activity
+ $('#dialog-add #busy').fadeIn('fast');
+ // fill in fallback protocol scheme 'http' if none is specified
+ var regexp = /^[a-zA-Z0-9]+\:\//;
+ if ( ! regexp.test(target) ){
+ target = 'http://' + target;
+ dialog.find('#target').val(target);
+ }
+ // query meta data from target
+ $.when(
+ Shorty.WUI.Meta.get(target)
+ ).done(function(response){
+ var meta=response.data;
+ if (meta.final)
+ dialog.find('#target').val(meta.final);
+ dialog.find('#title').attr('placeholder',meta.title);
+ dialog.find('#meta').fadeTo('fast',0,function(){
+ Shorty.WUI.Meta.reset(dialog);
+ // specify the icons and information to be shown as meta data
+ dialog.find('#staticon').attr('src',meta.staticon);
+ dialog.find('#schemicon').attr('src',meta.schemicon);
+ dialog.find('#favicon').attr('src',meta.favicon);
+ dialog.find('#mimicon').attr('src',meta.mimicon);
+ dialog.find('#explanation').html(meta.title?meta.title:'[ '+meta.explanation+' ]');
+ dialog.find('#meta').fadeTo('fast',1);
+ Shorty.WUI.Dialog.sharpen(dialog,true);
+ // stop expressing activity
+ $('#dialog-add #busy').fadeOut('slow');
+ });
+ dfd.resolve(response);
+ }).fail(function(reponse){
+ Shorty.WUI.Dialog.sharpen(dialog,false);
+ dfd.reject(response);
+ })
+ return dfd.promise();
+ }, // Shorty.WUI.Meta.collect
+ // ===== Shorty.WUI.Meta.get =====
+ get: function(target){
+ if (Shorty.Debug) Shorty.Debug.log("get meta data for target "+target);
+ var dfd = new $.Deferred();
+ $.ajax({
+ type: 'GET',
+ url: OC.filePath('shorty','ajax','meta.php'),
+ cache: false,
+ data: { target: encodeURIComponent(target) }
+ }).pipe(
+ function(response){return Shorty.Ajax.eval(response);},
+ function(response){return Shorty.Ajax.fail(response);}
+ ).done(function(response){
+ dfd.resolve(response);
+ }).fail(function(response){
+ dfd.reject(response);
+ })
+ return dfd.promise();
+ }, // Shorty.WUI.Meta.get
+ // ===== Shorty.WUI.Meta.reset =====
+ reset: function(dialog){
+ if (Shorty.Debug) Shorty.Debug.log("reset meta data");
+ dialog.find('#staticon').attr('src',dialog.find('#staticon').attr('data'));
+ dialog.find('#schemicon').attr('src',dialog.find('#schemicon').attr('data'));
+ dialog.find('#favicon').attr('src',dialog.find('#favicon').attr('data'));
+ dialog.find('#mimicon').attr('src',dialog.find('#mimicon').attr('data'));
+ dialog.find('#explanation').html(dialog.find('#explanation').attr('data'));
+ dialog.find('#meta').fadeTo('fast',1);
+ }, // Shorty.WUI.Meta.reset
+ }, // Shorty.WUI.Meta
+ // ===== Shorty.WUI.Sums =====
+ Sums:
+ {
+ // ===== Shorty.WUI.Sums.fill =====
+ fill: function(){
+ if (Shorty.Debug) Shorty.Debug.log("fill sums");
+ var dfd = new $.Deferred();
+ $.when(
+ // update (set) sum values in the control bar
+ Shorty.WUI.Sums.get(function(data){
+ $('#controls #sum_shortys').text(data.sum_shortys);
+ $('#controls #sum_clicks').text(data.sum_clicks);
+ })
+ ).done(dfd.resolve)
+ return dfd.promise();
+ }, // Shorty.WUI.Sums.fill
+ // ===== Shorty.WUI.Sums.get =====
+ get: function(callback){
+ if (Shorty.Debug) Shorty.Debug.log("get sums");
+ var dfd = new $.Deferred();
+ $.when(
+ $.ajax({
+ type: 'GET',
+ url: OC.filePath('shorty','ajax','count.php'),
+ cache: false,
+ data: { }
+ }).pipe(
+ function(response){return Shorty.Ajax.eval(response)},
+ function(response){return Shorty.Ajax.fail(response)}
+ )
+ ).done(function(response){
+ if (callback) callback(response.data);
+ dfd.resolve(response);
+ }).fail(function(response){
+ dfd.reject(response);
+ })
+ return dfd.promise();
+ }, // Shorty.WUI.Sums.get
+ }, // Shorty.WUI.Sums
+ }, // Shorty.WUI
+
+ //==========
+
+ Action:
+ {
+ // ===== Shorty.Action.Preference =====
+ Preference:
+ {
+ // ===== Shorty.Action.Preference.get =====
+ get:function(data){
+ if (Shorty.Debug){Shorty.Debug.log("get preference(s):");Shorty.Debug.log(data);}
+ var dfd = new $.Deferred();
+ $.ajax({
+ type: 'GET',
+ url: OC.filePath('shorty','ajax','preferences.php'),
+ cache: false,
+ data: data
+ }).pipe(
+ function(response){return Shorty.Ajax.eval(response)},
+ function(response){return Shorty.Ajax.fail(response)}
+ ).always(function(response){
+ if (Shorty.Debug){Shorty.Debug.log("got preference(s):");Shorty.Debug.log(response.data);}
+ }).done(function(response){
+ dfd.resolve(response.data);
+ }).fail(function(response){
+ dfd.reject({});
+ })
+ return dfd.promise();
+ }, // Shorty.Action.Preference.get
+ // ===== Shorty.Action.Preference.set =====
+ set:function(data){
+ if (Shorty.Debug){Shorty.Debug.log("set preference(s):");Shorty.Debug.log(data);}
+ var dfd = new $.Deferred();
+ $.ajax({
+ type: 'POST',
+ url: OC.filePath('shorty','ajax','preferences.php'),
+ cache: false,
+ data: data
+ }).pipe(
+ function(response){return Shorty.Ajax.eval(response)},
+ function(response){return Shorty.Ajax.fail(response)}
+ ).always(function(response){
+ if (Shorty.Debug){Shorty.Debug.log("got preference(s):");Shorty.Debug.log(response.data);}
+ }).done(function(response){
+ dfd.resolve(response.data);
+ }).fail(function(response){
+ dfd.reject({});
+ })
+ return dfd.promise();
+ }, // Shorty.Action.Preference.set
+ }, // Shorty.Action.Preference
+ // ===== Shorty.Action.Setting =====
+ Setting:
+ {
+ // ===== Shorty.Action.Setting.get =====
+ get:function(data){
+ if (Shorty.Debug){Shorty.Debug.log("get setting(s):");Shorty.Debug.log(data);}
+ var dfd = new $.Deferred();
+ $.ajax({
+ type: 'GET',
+ url: OC.filePath('shorty','ajax','settings.php'),
+ cache: false,
+ data: data
+ }).pipe(
+ function(response){return Shorty.Ajax.eval(response)},
+ function(response){return Shorty.Ajax.fail(response)}
+ ).always(function(response){
+ if (Shorty.Debug){Shorty.Debug.log("got preference(s):");Shorty.Debug.log(response.data);}
+ }).done(function(response){
+ dfd.resolve(response.data);
+ }).fail(function(response){
+ dfd.reject({});
+ })
+ return dfd.promise();
+ }, // Shorty.Action.Setting.get
+ // ===== Shorty.Action.Setting.set =====
+ set:function(data){
+ if (Shorty.Debug){Shorty.Debug.log("set setting(s):");Shorty.Debug.log(data);}
+ var dfd = new $.Deferred();
+ $.ajax({
+ type: 'POST',
+ url: OC.filePath('shorty','ajax','settings.php'),
+ cache: false,
+ data: data
+ }).pipe(
+ function(response){return Shorty.Ajax.eval(response)},
+ function(response){return Shorty.Ajax.fail(response)}
+ ).always(function(response){
+ if (Shorty.Debug){Shorty.Debug.log("got preference(s):");Shorty.Debug.log(response.data);}
+ }).done(function(response){
+ dfd.resolve(response.data);
+ }).fail(function(response){
+ dfd.reject({});
+ })
+ return dfd.promise();
+ }, // Shorty.Action.Setting.set
+ // ===== Shorty.Action.Setting.popup =====
+ popup:{},
+ // ===== Shorty.Action.Setting.verify =====
+ verify:function(){
+ if (!Shorty.Action.Setting.popup.dialog){
+ Shorty.Action.Setting.popup=$('#shorty #dialog-verification');
+ Shorty.Action.Setting.popup.dialog({show:'fade',autoOpen:false,modal:true});
+ Shorty.Action.Setting.popup.dialog('option','minHeight',240 );
+ }
+ var dfd = new $.Deferred();
+ $.when(
+ this.check(Shorty.Action.Setting.popup,
+ $('#shorty #backend-static #backend-static-base').val())
+ ).done(dfd.resolve)
+ return dfd.promise();
+ }, // Shorty.Action.Setting.verify
+ // ===== Shorty.Action.Setting.check =====
+ check:function(popup,target){
+ popup.find('#verification-target').text(target);
+ popup.dialog('open');
+ popup.find('#success').hide();
+ popup.find('#failure').hide();
+ popup.find('#hourglass').fadeIn('fast');
+ var dfd = new $.Deferred();
+ $.ajax({
+ // the '0000000000' below is a special id recognized for testing purposes
+ url: target+'0000000000',
+ cache: false,
+ data: { },
+ }).pipe(
+ function(response){return Shorty.Ajax.eval(response)},
+ function(response){return Shorty.Ajax.fail(response)}
+ ).done(function(response){
+ $.when(
+ popup.find('#hourglass').fadeOut('fast')
+ ).then(function(){
+ popup.find('#success').fadeIn('fast');
+ dfd.resolve(response);
+ })
+ }).fail(function(response){
+ $.when(
+ popup.find('#hourglass').fadeOut('fast')
+ ).then(function(){
+ popup.find('#failure').fadeIn('fast');
+ dfd.reject(response);
+ })
+ })
+ return dfd.promise();
+ } // Shorty.Action.Setting.check
+ }, // Shorty.Action.Setting
+ // ===== Shorty.Action.Url =====
+ Url:
+ {
+ // ===== Shorty.Action.Url.add =====
+ add:function(){
+ if (Shorty.Debug) Shorty.Debug.log("action add url");
+ var dfd=new $.Deferred();
+ var dialog=$('#dialog-add');
+ var status=dialog.find('#status').val()||'public';
+ var target=dialog.find('#target').val()||'';
+ var title =dialog.find('#title').val()||'';
+ var notes =dialog.find('#notes').val()||'';
+ var until =dialog.find('#until').val()||'';
+ // store favicon from meta data, except it is the internal default blank
+ var favicon = dialog.find('#meta #favicon').attr('src');
+ favicon=(favicon==dialog.find('#meta #favicon').attr('data'))?'':favicon;
+ // perform upload of new shorty
+ $.when(
+ Shorty.WUI.Notification.hide(),
+ // close and neutralize dialog
+ Shorty.WUI.Dialog.hide(dialog),
+ Shorty.WUI.List.dim(false),
+ Shorty.WUI.List.show()
+ ).done(function(){
+ var data={status: status,
+ target: target,
+ title: title,
+ notes: notes,
+ until: until,
+ favicon: favicon};
+ if (Shorty.Debug) Shorty.Debug.log(data);
+ $.ajax({
+ type: 'POST',
+ url: OC.filePath('shorty','ajax','add.php'),
+ cache: false,
+ data: data
+ }).pipe(
+ function(response){return Shorty.Ajax.eval(response)},
+ function(response){return Shorty.Ajax.fail(response)}
+ ).done(function(response){
+ // wipe entries in dialog
+ Shorty.WUI.Dialog.reset(dialog)
+ }).done(function(response){
+ // add shorty to existing list
+ Shorty.WUI.List.add([response.data],true);
+ Shorty.WUI.List.dim(true)
+ dfd.resolve(response);
+ }).fail(function(response){
+ Shorty.WUI.List.dim(true)
+ dfd.reject(response);
+ })
+ })
+ return dfd.promise();
+ }, // ===== Shorty.Action.Url.add =====
+ // ===== Shorty.Action.Url.edit =====
+ edit: function(){
+ if (Shorty.Debug) Shorty.Debug.log("action modify url");
+ var dfd=new $.Deferred();
+ var dialog=$('#dialog-edit');
+ var id =dialog.find('#id').val();
+ var status=dialog.find('#status').val()||'blocked';
+ var title =dialog.find('#title').val()||'';
+ var until =dialog.find('#until').val()||'';
+ var notes =dialog.find('#notes').val()||'';
+ // perform modification of existing shorty
+ $.when(
+ Shorty.WUI.Notification.hide(),
+ // close and neutralize dialog
+ Shorty.WUI.Dialog.hide(dialog),
+ Shorty.WUI.List.dim(false),
+ Shorty.WUI.List.show()
+ ).done(function(){
+ var data={id: id,
+ status: status,
+ title: title,
+ notes: notes,
+ until: until};
+ if (Shorty.Debug) Shorty.Debug.log(data);
+ $.ajax({
+ type: 'POST',
+ url: OC.filePath('shorty','ajax','edit.php'),
+ cache: false,
+ data: data,
+ }).pipe(
+ function(response){return Shorty.Ajax.eval(response)},
+ function(response){return Shorty.Ajax.fail(response)}
+ ).done(function(response){
+ // wipe entries in dialog
+ Shorty.WUI.Dialog.reset(dialog);
+ // modify existing entry in list
+ Shorty.WUI.List.modify([response.data],true);
+ Shorty.WUI.List.dim(true)
+ dfd.resolve(response);
+ }).fail(function(response){
+ dfd.reject(response);
+ })
+ })
+ return dfd.promise();
+ }, // ===== Shorty.Action.Url.edit =====
+ // ===== Shorty.Action.Url.del =====
+ del: function(){
+ if (Shorty.Debug) Shorty.Debug.log("action delete url");
+ var dfd = new $.Deferred();
+ var dialog = $('#dialog-edit');
+ var id = dialog.find('#id').val();
+ $.when(
+// Shorty.WUI.Notification.hide(),
+ $.ajax({
+ type: 'GET',
+ url: OC.filePath('shorty','ajax','del.php'),
+ cache: false,
+ data: { id: id }
+ }).pipe(
+ function(response){return Shorty.Ajax.eval(response)},
+ function(response){return Shorty.Ajax.fail(response)}
+ )
+ ).done(function(response){
+ // close and neutralize dialog
+ Shorty.WUI.Dialog.hide(dialog);
+ // hide and remove deleted entry
+ // ...
+ dfd.resolve(response.data);
+ }).fail(function(response){
+ dfd.reject(response.data);
+ })
+ return dfd.promise();
+ }, // ===== Shorty.Action.Url.del =====
+ // ===== Shorty.Action.Url.forward =====
+ forward: function(entry){
+ if (Shorty.Debug) Shorty.Debug.log("action forward to entry "+entry.attr('id'));
+ var url=entry.attr('data-target');
+ if (Shorty.Debug) Shorty.Debug.log("opening target url '"+url+"' in new window");
+ window.open(url);
+ }, // Shorty.Action.Url.forward
+ // ===== Shorty.Action.Url.send =====
+ send: function(action,entry){
+ if (Shorty.Debug) Shorty.Debug.log("action send via "+action+" with entry "+entry.attr('id'));
+ switch (action){
+ case 'usage-email':
+ var mailSubject=entry.attr('data-title')||'';
+ var mailBody=entry.attr('data-notes')+"\n\n"+entry.attr('data-source');
+ window.location='mailto:""?'
+ +'subject='+encodeURIComponent(mailSubject)
+ +'&body='+encodeURIComponent(mailBody);
+ break;
+ case 'usage-sms':
+ var smsBody=entry.attr('data-title')+" - "+entry.attr('data-notes')+" - "+entry.attr('data-source');
+ // unfortunately there is no way to get the body over into the sms application on "sms urls"...
+ window.prompt(t('shorty',"Copy to clipboard: Ctrl+C, then paste into SMS: Ctrl-V"), smsBody );
+ window.location='sms:';
+ break;
+ case 'usage-qrcode':
+ var title =entry.attr('data-title');
+ var source=entry.attr('data-source');
+ var target=entry.attr('data-target');
+ Shorty.Action.Usage.Dialog.qrcode(title,source,target);
+ break;
+ case 'usage-clipboard':
+ window.prompt(t('shorty',"Copy to clipboard: Ctrl+C"), entry.attr('data-source'));
+ break;
+ default:
+ if (Shorty.Debug) Shorty.Debug.log("usage action '"+action+"' is disabled, refusing to comply");
+ }
+ }, // Shorty.Action.Url.send
+ // ===== Shorty.Action.Url.show =====
+ show: function(){
+ var dfd = new $.Deferred();
+ var dialog = $('#dialog-show');
+ var id = dialog.find('#id').val();
+ var record = $(this).parent().parent();
+ $('#shorty-add-id').val(record.attr('data-id'));
+ $('#shorty-add-id').val(record.attr('data-status'));
+ $('#shorty-add-source').val(record.children('.shorty-source:first').text());
+ $('#shorty-add-target').val(record.children('.shorty-target:first').text());
+ $('#shorty-add-notes').val(record.children('.shorty-notes:first').text());
+ $('#shorty-add-until').val(record.children('.shorty-until:first').text());
+ $.when(
+ function(){
+ if ($('.shorty-add').css('display') == 'none'){
+ $('.shorty-add').slideToggle();
+ }
+ },
+ $('html, body').animate({ scrollTop: $('.shorty-menu').offset().top }, 500)
+ ).done(dfd.resolve)
+ return dfd.promise();
+ }, // ===== Shorty.Action.Url.show =====
+ // ===== Shorty.Action.Url.status =====
+ status: function(id,status){
+ if (Shorty.Debug) Shorty.Debug.log("changing status of id "+id+" to "+status);
+ var dfd = new $.Deferred();
+ $.ajax({
+ type: 'GET',
+ url: OC.filePath('shorty','ajax','status.php'),
+ cache: false,
+ data: { id : id,
+ status: status }
+ }).pipe(
+ function(response){return Shorty.Ajax.eval(response)},
+ function(response){return Shorty.Ajax.fail(response)}
+ ).done(function(){
+ // update the rows content
+ var row=$('#list tbody tr#'+id);
+ row.attr('data-status',status);
+ row.find('td#status span').text(t('shorty',status));
+ dfd.resolve();
+ }).fail(dfd.reject)
+ return dfd.promise();
+ } // Shorty.Action.Url.status
+ }, // ===== Shorty.Action.Url =====
+ // ===== Shorty.Action.Usage =====
+ Usage:
+ {
+ // ===== Shorty.Action.Usage.Popup =====
+ Popup:
+ {
+ // ===== Shorty.Action.Usage.Popup.qrcode =====
+ qrcode:{},
+ }, // Shorty.Action.Usage.Popup
+ // ===== Shorty.Action.Usage.Dialog =====
+ Dialog:
+ {
+ // ===== Shorty.Action.Usage.Dialog.qrcode =====
+ qrcode:function(title,source,target){
+ var qrcode=Shorty.Action.Usage.Popup.qrcode;
+ if (!qrcode.dialog){
+ qrcode=$('#dialog-qrcode');
+ qrcode.dialog({show:'fade',autoOpen:false,modal:true});
+ qrcode.dialog('option','width',240 );
+ //qrcode.dialog('option','height',80 );
+ }
+ // a hidden input field ('qrcode-url') holds the base url to the qrcode generator
+ // we just add the url parameter for this specific entrys source url
+ var url=qrcode.find('#qrcode-url').val()+encodeURIComponent(source);
+ qrcode.dialog('option','title',title);
+ qrcode.find('#qrcode-img img').attr('src',url).attr('title',source);
+ qrcode.find('#qrcode-val a').text(url);
+ qrcode.bind('click',function(){
+ qrcode.find('#qrcode-img').toggle();
+ qrcode.find('#qrcode-val').toggle();
+ });
+ qrcode.dialog('open');
+ } // Shorty.Action.Usage.Dialog.qrcode
+ } // Shorty.Action.Usage.Dialog
+ } // Shorty.Action.Usage
+ }, // Shorty.Action
+
+ // ===========
+
+ // ===== Shorty.Ajax =====
+ Ajax:
+ {
+ // ===== Shorty.Ajax.eval =====
+ eval:function(response){
+ if (Shorty.Debug) Shorty.Debug.log("eval ajax response of status "+response.status);
+ // Check to see if the response is truely successful.
+ if (response.status){
+ // this is a valid response
+ if ('success'==response.status){
+ Shorty.WUI.Notification.show(response.message,'debug');
+ return new $.Deferred().resolve(response);
+ } else {
+ Shorty.WUI.Notification.show(response.message,'error');
+ return new $.Deferred().reject(response);
+ }
+// }else{
+ // TEST (regex) if this is a DB error:
+ // DB Error: "SQLSTATE[HY000]: General error: 1 near "WHERE": syntax error".....
+// // not a valid response, maybe a DB error ?
+// if ('DB error'==response)
+ }
+ }, // Shorty.Ajax.eval
+
+ // ===== Shorty.Ajax.fail =====
+ fail:function(response){
+ if (Shorty.Debug) Shorty.Debug.log("handle ajax failure");
+ return new $.Deferred().reject({
+ status: 'error',
+ data: null,
+ message: [ "Unexpected error: " + response.status + " " + response.statusText ]
+ });
+ } // Shorty.Ajax.fail
+ }, // Shorty.Ajax
+
+ // ==== Shorty.Date =====
+ Date:
+ {
+ // ===== Shorty.Date.expired =====
+ expired:function(date){
+ return (Date.parse(date)<=Date.parse(Date()));
+ } // Shorty.Date.expired
+ } // Shorty.Date
+
+} // Shorty
diff --git a/apps/shorty/l10n/de.php b/apps/shorty/l10n/de.php
new file mode 100644
index 00000000000..cc4eb624a02
--- /dev/null
+++ b/apps/shorty/l10n/de.php
@@ -0,0 +1,157 @@
+<?php $TRANSLATIONS = array(
+// folder js
+"all" => "alle",
+"blocked" => "geblockt",
+"deleted" => "gelöscht",
+"disabled" => "deaktiviert",
+"enabled" => "aktiviert",
+"never" => "niemals",
+"private" => "privat",
+"public" => "öffentlich",
+"shared" => "geteilt",
+
+"bitly.com service" => "bitly.com-Service",
+"cli.gs service" => "cli.gs-Service",
+"goo.gl service" => "goo.gl-Service",
+"is.gd service" => "is.gd-Service",
+"ti.ny service" => "ti.ny-Service",
+"tiny.cc service" => "tiny.cc-Service",
+"static backend" => "statisches Backend",
+
+"Access" => "Zugriff",
+"Add a new shorty" => "Neuen Shorty hinzufügen",
+"Add as new" => "Neu hinzufügen",
+"Add page as 'Shorty' to ownCloud" => "Seite als 'Shorty' zu ownCloud hinzufügen",
+"Anything that appears helpful …" => "Alles, was hilfreich erscheint …",
+"Backend" => "Backend",
+"Base url" => "Basis-Url",
+"Choose a service…" => "Wähle einen Dienst…",
+"Click for qrcode image" => "Klicken für QRCode-Bild",
+"Click for qrcode url" => "Klicken für QRCode-Url",
+"Click it, for whatever site you want to create a Shorty." => "Klicke es, für welche Seite auch immer Du ein 'Shorty' generieren willst.",
+"Click registered" => "Click registriert",
+"Clicks" => "Klicks",
+"Close" => "Schließen",
+"Copy to clipboard" => "In die Zwischenablage kopieren",
+"Copy to clipboard: Ctrl+C" => "In Zwischenablage kopieren: Strg-C",
+"Copy to clipboard: Ctrl+C, then paste into SMS: Ctrl-V" => "In Zwischenablage kopieren: Strg-C, dann in SMS einfügen: Strg-V",
+"Counted entries and clicks" => "Einträge und Klicks gezählt",
+"Creation" => "Generierung",
+"Delete shorty" => "Shorty löschen",
+"Drag this to your browser bookmarks." => "Zieh' dies in die Lesezeichen Deines Browsers.",
+"Example" => "Beispiel",
+"Exception" => "Ausnahme",
+"Exception (%s)" => "Ausnahme (%s)",
+"Expiration" => "Verfall",
+"List currently empty." => "Liste derzeit leer.",
+"Loading" => "Lade",
+"Modifications for shorty with id '%s' saved" => "Änderungen zu Shorty mit Kennung '%s' gespeichert",
+"Modify attributes" => "Attribute ändern",
+"Modify shorty" => "Shorty ändern",
+"New Shorty" => "Neuer Shorty",
+"Notes" => "Notizen",
+"Number of entries" => "Anzahl der Einträge",
+"Number of entries: %s" => "Anzahl der Einträge: %s",
+"QRCode" => "QRCode",
+"Open source url" => "Quell-Url öffnen",
+"Open relay url" => "Relais-Url öffnen",
+"Open target" => "Ziel öffnen",
+"Open target url" => "Ziel-Url öffnen",
+"Preference saved." => "Präferenz gespeichert.",
+"Preference(s) retrieved." => "Präferenz(en) abgefragt.",
+"Relay url" => "Relais-Url",
+"Reload list" => "Liste neu laden",
+"Save" => "Speichern",
+"Send by email" => "Als E-Mail verschicken",
+"Send by SMS" => "Als SMS verschicken",
+"Setting saved." => "Einstellung gespeichert.",
+"Shortlet" => "Shortlet",
+"Shorty with id '%s' deleted" => "Shorty mit Kennung '%s' gelöscht",
+"Shorty source url" => "Shorty Quell-Url",
+"Shorty title" => "Shorty-Titel",
+"Show details" => "Details zeigen",
+"Show as QRCode" => "Als QRCode anzeigen",
+"Sort ascending" => "aufsteigend sortieren",
+"Sort descending" => "absteigend sortieren",
+"Source url" => "Quell-Url",
+"Specify a backend base url…" => "Lege eine Basis-Url für das Backend fest…",
+"Status" => "Status",
+"Status change for shorty with id '%s' saved" => "Statusänderung für Shorty mit Kennung '%s' gespeichert",
+"Target" => "Ziel",
+"Target url" => "Ziel-Url",
+"Target url '%s' is valid" => "Ziel-Url '%s' ist valide",
+"Test and use" => "Testen und nutzen",
+"Title" => "Titel",
+"Total of clicks" => "Summe der Klicks",
+"Unexpected type of exception caught: %s" => "Unerwarteter Type einer Ausnahme aufgetreten: %s",
+"Unknown object of type caught: %s" => "Unbekanntes Objekttyp aufgetreten: %s",
+"Url shortened to: %s" => "Url gekürzt zu: %s",
+"Verification by click" => "Prüfung durch Klicken",
+
+"%s\nMessage(code): %s (%s)\nFile(line): %s (%s)\nInfo: %%s" => "%s\Nachricht(Code): %s (%s)\nDatei(Zeile): %s (%s)\nInfo: %%s",
+
+"API access key" => "API-Zugriffsschlüssel",
+"Account" => "Nutzerkonto",
+"API key" => "API-Schlüssel",
+"bit.ly user" => "bit.ly-Nutzer",
+"bit.ly user name" => "bit.ly-Nutzerkennung",
+"bit.ly key" => "bit.ly-Schlüssel",
+"bit.ly users key" => "bit.ly-Nutzerschlüssel",
+"Google API key" => "Google API-Schlüssel",
+"Google API account" => "Google API-Nutzerkonto",
+"tiny.cc user" => "tiny.cc-Nutzer",
+"tiny.cc user name" => "tiny.cc-Nutzerkennung",
+"tiny.cc key" => "tiny.cc-Schlüssel",
+"tiny.cc user key" => "tiny.cc-Nutzerschlüssel",
+
+"The external 'bitly.com' service is used to register a short url for each generated shorty."
+=> "Der externe 'bitly.com'-Dienst wird genutzt, um eine Kurz-Url für jeden generierten Shorty zu registrieren.",
+"This means you have to register an '%s' at their site first."
+=> "Das bedeutet, zunächst muss ein '%s' auf deren Seite registriert werden.",
+"The external 'turl' service is used to register a short url for each generated shorty."
+=> "Der externe 'turl'-Dienst wird genutzt, um eine Kurz-Url für jeden generierten Shorty zu registrieren.",
+"The external 'ti.ny' service is used to register a short url for each generated shorty."
+=> "Der externe 'ti.ny'-Dienst wird genutzt, um eine Kurz-Url für jeden generierten Shorty zu registrieren.",
+"The external 'cli.gs' service is used to register a short url for each generated shorty."
+=> "Der externe 'cli.gs'-Dienst wird genutzt, um eine Kurz-Url für jeden generierten Shorty zu registrieren.",
+"The external 'is.gd' service is used to register a short url for each generated shorty."
+=> "Der externe 'is.gd'-Dienst wird genutzt, um eine Kurz-Url für jeden generierten Shorty zu registrieren.",
+"The external 'tiny.cc' service is used to register a short url for each generated shorty."
+=>"Der externe 'tiny.cc'-Dienst wird genutzt, um eine Kurz-Url für jeden generierten Shorty zu registrieren.",
+"No backend is used, direct links pointing to your ownCloud are generated."
+=> "Kein Backend wird verwendet, einfache direkte Links zur ownCloud werden erzeugt.",
+"The service requires you to authenticate yourself by providing a valid bit.ly user name and an '%s'."
+=> "Dieser Dienst erfordert die Authentifizierung durch eine valide bit.ly-Nutzerkennung und einen '%s'",
+"Such links are most likely longer than those generated when using a backend."
+=> "Solche Links werden sicherlich länger sein, als wenn ein Backend eingesetzt wird.",
+"However this option does not rely on any third party service and keeps your shortys under your control."
+=> "Dafür stützt sich diese Option aber nicht auf fremde Angebote und belässt die Shorties unter eigener Kontrolle.",
+"A static, rule-based backend is used, shorty links are generated relative to a given base url."
+=> "Ein statisches, regelbasiertes Backend wird genutzt, Shorty-Links werden relativ zu einer gegebenen Basis-Url generiert.",
+"Since this setup depends on server based configuration rules the base url can only be specified in the 'Admin' section of the configuration."
+=> "Da diese Konfiguration von serverseitigen Konfigurationsregeln abhängt, kann die Basis-Url nur unter 'Verwaltung' in der Konfiguration festgelegt werden.",
+"You have to take care that any request to the url configured here is internally mapped to the 'shorty' module."
+=> "Es muss sicher gestellt werden, dass jede Anfrage an die hier konfigurierte URL intern umgesetzt wird auf das 'Shorty'-Modul",
+"Have a try with the example link provided, click it, it should result in a confirmation that your setup is working."
+=> "Einfach den verfügbaren Beispiel-Link durch Anklicken testen. Das sollte zu einer Bestätigung führen, dass die Konfiguration funktioniert.",
+"Leave empty if you can't provide a short base url that is mapped the described way."
+=> "Einfach leer lassen, wenn keine Basis-URL angegeben werden kann, die wir beschrieben umgesetzt wird.",
+"Only use this backend, if you can provide a short base url that is mapped the described way. Your shorties won't work otherwise."
+=> "Dieses Backend kann nur verwendet werden, wenn die Basis-Url in der beschriebenen Art umgesetzt wird. Die Shorties werden andernfalls nicht nutzbar sein.",
+"Googles external 'goo.gl service' is used to register a short url for each generated shorty."
+=> "Googles externer 'goo.gl'-Dienst wird genutzt, um eine Kurz-Url für jeden generierten Shorty zu registrieren.",
+"You must provide a valid '%s' to use this service."
+=> "Es muss eine valide '%s' angegeben werden um diesen Dienst nutzen zu können. ",
+"This means you require a 'Google API console account'."
+=> "Das bedeutet, dass ein 'Google API console account' benötigt wird",
+"Register a new '%s' at their pages."
+=> "Einen neuen '%s' auf deren Seite registrieren.",
+"Enabling the SMS option will offer sending a Shorty via SMS."
+=> "Aktivieren der SMS-Option bietet das Versenden von Shortys via SMS an.",
+"Unfortunately support for 'SMS url handling' is usually only found on mobile devices like smart phones."
+=> "Unglücklicherweise werden 'SMS-Urls' typischerweise nur auf mobilen Geräten wie etwa Smartphones unterstützt.",
+"In addition, the implementations found in Android or iOS are minimalistic, buggy and differ from system to system."
+=> "Darüber hinaus sind die Implementierungen, etwa die in Adroid oder auch iOS minimalistisch, fehlerhaft und unterschiedlich von System zu System.",
+"In short: this might not work for you, therefore you can disable it…"
+=> "Kurz gesagt: die mag nicht funktionieren, daher can die Option deaktiviert werden…",
+); \ No newline at end of file
diff --git a/apps/shorty/lib/backend.php b/apps/shorty/lib/backend.php
new file mode 100644
index 00000000000..aa6c58b4490
--- /dev/null
+++ b/apps/shorty/lib/backend.php
@@ -0,0 +1,286 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file lib/backend.php
+ * Routines to use remote (online) shortening services as backends in a local workflow
+ * @author Christian Reiner
+ */
+
+/**
+ * @class OC_Shorty_Backend
+ * @brief Library to register urls using backends, typically remote (online) url shortening services
+ * @access public
+ * @author Christian Reiner
+ */
+class OC_Shorty_Backend
+{
+ /**
+ * @method OC_Shorty_Backend::registerUrl
+ * @brief Wrapper function around the specific backend routines
+ * @param id (string) Internal shorty id used to reference a shorty upon usage.
+ * @returns (string) The shortened url as generated by a specific backend.
+ * @throws OC_Shorty_Exception taking over the explaining of the failure from the specific backend
+ * @access public
+ * @author Christian Reiner
+ */
+ static function registerUrl ( $id )
+ {
+ try
+ {
+ // construct the $relay, the url to be called to reach THIS service (ownclouds shorty plugin)
+ $relay = OC_Shorty_Tools::relayUrl ( $id );
+ // call backend specific work horse
+ switch ( $type=OCP\Config::getUserValue(OCP\User::getUser(),'shorty','backend-type','none') )
+ {
+ default: return OC_Shorty_Backend::registerUrl_default ( $id, $relay );
+ case 'static': return OC_Shorty_Backend::registerUrl_static ( $id, $relay );
+ case 'bitly': return OC_Shorty_Backend::registerUrl_bitly ( $id, $relay );
+ case 'cligs': return OC_Shorty_Backend::registerUrl_cligs ( $id, $relay );
+ case 'google': return OC_Shorty_Backend::registerUrl_google ( $id, $relay );
+ case 'isgd': return OC_Shorty_Backend::registerUrl_isgd ( $id, $relay );
+ case 'tinyurl': return OC_Shorty_Backend::registerUrl_tinyurl ( $id, $relay );
+ case 'tinycc': return OC_Shorty_Backend::registerUrl_tinycc ( $id, $relay );
+ } // switch
+ } // try
+ catch (OC_Shorty_Exception $e)
+ {
+ throw $e;
+ } // catch
+ catch (Exception $e)
+ {
+ throw new OC_Shorty_Exception ( "Failed to register url '%s' at '%s' backend", array($relay,$type) );
+ } // catch
+ } // OC_Shorty_Backend::registerUrl
+
+ /**
+ * @method OC_Shorty_Backend::registerUrl_default
+ * @brief Pseudo-registers a given local relay url
+ * @param id (string)
+ * @param relay (url)
+ * @returns validated and pseudo-registered relay
+ * @access public
+ * @author Chrisian Reiner
+ */
+ static function registerUrl_default ( $id, $relay )
+ {
+ return OC_Shorty_Type::validate ( $relay, OC_Shorty_Type::URL );
+ } // OC_Shorty_Backend::registerUrl_default
+
+ /**
+ * @method OC_Shorty_Backend::registerUrl_static
+ * @brief Registers a given local relay url as local static shorty
+ * @param id (string)
+ * @param relay (url)
+ * @returns registered and validated relay url
+ * @access public
+ * @author Chrisian Reiner
+ */
+ static function registerUrl_static ( $id, $relay )
+ {
+ if ( (FALSE===($base=trim ( OCP\Config::getAppValue('shorty','backend-static-base',FALSE))))
+ ||(empty($base)) )
+ throw new OC_Shorty_Exception ( 'No base url defined for the static backend' );
+ return OC_Shorty_Type::validate ( $base.$id, OC_Shorty_Type::URL );
+ } // OC_Shorty_Backend::registerUrl_static
+
+ /**
+ * @method OC_Shorty_Backend::registerUrl_bitly
+ * @brief Registers a given local relay url at the bit.ly shortening service
+ * @param id (string)
+ * @param relay (url)
+ * @returns registered and validated relay url
+ * @access public
+ * @author Chrisian Reiner
+ */
+ static function registerUrl_bitly ( $id, $relay )
+ {
+ $bitly_api_user = OCP\Config::getUserValue(OCP\User::getUser(),'shorty','backend-bitly-user','');
+ $bitly_api_key = OCP\Config::getUserValue(OCP\User::getUser(),'shorty','backend-bitly-key', '');
+ if ( ! $bitly_api_key || ! $bitly_api_user )
+ throw new OC_Shorty_Exception ( 'No API user or key configured' );
+ $curl = curl_init ( );
+ curl_setopt ( $curl, CURLOPT_URL, 'https://api-ssl.bit.ly/shorten' );
+ curl_setopt ( $curl, CURLOPT_SSL_VERIFYHOST, TRUE );
+ curl_setopt ( $curl, CURLOPT_POST, TRUE );
+ curl_setopt ( $curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json') );
+ curl_setopt ( $curl, CURLOPT_POSTFIELDS, json_encode(array('version'=>'2.0.1',
+ 'longUrl'=>$relay,
+ 'format'=>'json',
+ 'login'=>$bitly_api_user,
+ 'apiKey'=>$bitly_api_key) ) );
+ curl_setopt ( $curl, CURLOPT_RETURNTRANSFER, TRUE );
+ if ( (FALSE===($reply=curl_exec($curl)))
+ ||(NULL===($payload=json_decode($reply)))
+ ||(!is_object($payload))
+ ||(!property_exists($payload,'id')) )
+ {
+ throw new OC_Shorty_Exception ( "Failed to register url at backend 'static'" );
+ }
+ curl_close ( $curl );
+ return OC_Shorty_Type::validate ( $payload->id, OC_Shorty_Type::URL );
+ } // OC_Shorty_Backend::registerUrl_bitly
+
+ /**
+ * @method OC_Shorty_Backend::registerUrl_cligs
+ * @brief Registers a given local relay url at the cli.gs shortening service
+ * @param id (string)
+ * @param relay (url)
+ * @returns registered and validated relay url
+ * @access public
+ * @author Chrisian Reiner
+ */
+ static function registerUrl_cligs ( $id, $relay )
+ {
+ $curl = curl_init ( );
+ curl_setopt ( $curl, CURLOPT_URL, sprintf('http://cli.gs/api/v2/cligs/create?url=%s&appid=owncloud_shorty&test=1', urlencode(trim($relay))) );
+ curl_setopt ( $curl, CURLOPT_RETURNTRANSFER, TRUE );
+ if ( (FALSE===($reply=curl_exec($curl)))
+ ||( ! preg_match( '/^(.+)$/', $reply, $match )) )
+ {
+ throw new OC_Shorty_Exception ( "Failed to register url at backend 'cli.gs'" );
+ }
+ curl_close ( $curl );
+ return OC_Shorty_Type::validate ( $match[1], OC_Shorty_Type::URL );
+ } // OC_Shorty_Backend::registerUrl_cligs
+
+ /**
+ * @method OC_Shorty_Backend::registerUrl_isgd
+ * @brief Registers a given local relay url at the is.gd shortening service
+ * @param id (string)
+ * @param relay (url)
+ * @returns registered and validated relay url
+ * @access public
+ * @author Chrisian Reiner
+ */
+ static function registerUrl_isgd ( $id, $relay )
+ {
+ $curl = curl_init ( );
+ curl_setopt ( $curl, CURLOPT_URL, sprintf('http://is.gd/create.php?format=simple&url=%s', urlencode(trim($relay))) );
+ curl_setopt ( $curl, CURLOPT_RETURNTRANSFER, TRUE );
+ if ( (FALSE===($reply=curl_exec($curl)))
+ ||( ! preg_match( '/^(.+)$/', $reply, $match )) )
+ {
+ throw new OC_Shorty_Exception ( "Failed to register url at backend 'is.gd'" );
+ }
+ curl_close ( $curl );
+ return OC_Shorty_Type::validate ( $match[1], OC_Shorty_Type::URL );
+ } // OC_Shorty_Backend::registerUrl_isgd
+
+ /**
+ * @method OC_Shorty_Backend::registerUrl_google
+ * @brief Registers a given local relay url at the google shortening service
+ * @param id (string)
+ * @param relay (url)
+ * @returns registered and validated relay url
+ * @access public
+ * @author Chrisian Reiner
+ */
+ static function registerUrl_google ( $id, $relay )
+ {
+ $api_key = OCP\Config::getUserValue(OCP\User::getUser(),'shorty','backend-google-key','');
+ if ( ! $api_key )
+ throw new OC_Shorty_Exception ( 'No goo.gl API key configured' );
+ $curl = curl_init ( );
+ curl_setopt ( $curl, CURLOPT_URL, 'https://www.googleapis.com/urlshortener/v1/url' );
+ curl_setopt ( $curl, CURLOPT_SSL_VERIFYHOST, TRUE );
+ curl_setopt ( $curl, CURLOPT_POST, TRUE );
+ curl_setopt ( $curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json') );
+ curl_setopt ( $curl, CURLOPT_POSTFIELDS, json_encode(array('longUrl'=>$relay,
+ 'key'=>$api_key) ) );
+ curl_setopt ( $curl, CURLOPT_RETURNTRANSFER, TRUE );
+ if ( (FALSE===($reply=curl_exec($curl)))
+ ||(NULL===($payload=json_decode($reply)))
+ ||(!is_object($payload))
+ ||(!property_exists($payload,'id')) )
+ {
+ throw new OC_Shorty_Exception ( "Failed to register url at backend 'goo.gl'" );
+ }
+ curl_close ( $curl );
+ return OC_Shorty_Type::validate ( $payload->id, OC_Shorty_Type::URL );
+ } // OC_Shorty_Backend::registerUrl_google
+
+ /**
+ * @method OC_Shorty_Backend::registerUrl_tinycc
+ * @brief Registers a given local relay url at the tiny.cc shortening service
+ * @param id (string)
+ * @param relay (url)
+ * @returns registered and validated relay url
+ * @access public
+ * @author Chrisian Reiner
+ */
+ static function registerUrl_tinycc ( $id, $relay )
+ {
+ $api_user = OCP\Config::getUserValue(OCP\User::getUser(),'shorty','backend-tinycc-user','');
+ $api_key = OCP\Config::getUserValue(OCP\User::getUser(),'shorty','backend-tinycc-key','');
+ if ( ! $api_key || ! $api_user )
+ throw new OC_Shorty_Exception ( 'No goo.gl API key configured' );
+ $curl = curl_init ( );
+ curl_setopt ( $curl, CURLOPT_URL, 'http://tiny.cc/?c=shorten' );
+ curl_setopt ( $curl, CURLOPT_SSL_VERIFYHOST, TRUE );
+ curl_setopt ( $curl, CURLOPT_POST, TRUE );
+ curl_setopt ( $curl, CURLOPT_HEADER, TRUE );
+ curl_setopt ( $curl, CURLOPT_POSTFIELDS, array('longUrl'=>$relay,
+ 'version'=>'2.0.3',
+ 'format'=>'json',
+ 'login'=>$api_user,
+ 'apiKey'=>$api_key) );
+ curl_setopt ( $curl, CURLOPT_RETURNTRANSFER, TRUE );
+ if ( (FALSE===($reply=curl_exec($curl)))
+ ||(NULL===($payload=json_decode($reply)))
+ ||(!is_object($payload))
+ ||(!property_exists($payload,'id')) )
+ {
+ throw new OC_Shorty_Exception ( "Failed to register url at backend 'tiny.cc'" );
+ }
+ curl_close ( $curl );
+ return OC_Shorty_Type::validate ( $payload->id, OC_Shorty_Type::URL );
+ } // OC_Shorty_Backend::registerUrl_google
+
+ /**
+ * @method OC_Shorty_Backend::registerUrl_tinyurl
+ * @brief Registers a given local relay url at the tinyURL shortening service
+ * @param id (string)
+ * @param relay (url)
+ * @returns registered and validated relay url
+ * @access public
+ * @author Chrisian Reiner
+ */
+ static function registerUrl_tinyurl ( $id, $relay )
+ {
+ $curl = curl_init ( );
+ curl_setopt ( $curl, CURLOPT_URL, sprintf('http://tinyurl.com/api-create.php?url=%s', urlencode(trim($relay))) );
+ curl_setopt ( $curl, CURLOPT_RETURNTRANSFER, TRUE );
+ if ( (FALSE===($reply=curl_exec($curl)))
+ ||( ! preg_match( '/^(.+)$/', $reply, $match )) )
+ {
+ throw new OC_Shorty_Exception ( "Failed to register url at backend 'tinyUrl'" );
+ }
+ curl_close ( $curl );
+ return OC_Shorty_Type::validate ( $match[1], OC_Shorty_Type::URL );
+ } // OC_Shorty_Backend::registerUrl_tinyurl
+
+} // class OC_Shorty_Backend
diff --git a/apps/shorty/lib/exception.php b/apps/shorty/lib/exception.php
new file mode 100644
index 00000000000..daeb9d10a9c
--- /dev/null
+++ b/apps/shorty/lib/exception.php
@@ -0,0 +1,162 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file lib/exception.php
+ * Application specific exception class
+ * @author Christian Reiner
+ */
+
+/**
+ * @class OC_Shorty_Exception
+ * @brief Application specific exception class
+ * @access public
+ * @author Christian Reiner
+ */
+class OC_Shorty_Exception extends Exception
+{
+ protected $phrase = '';
+ protected $param = array ( );
+
+ /**
+ * @method OC_Shorty_Exception::__construct
+ * @brief: Constructs an exception based on a phrase and a set of parameters
+ * @param phrase (string) Human readable message that should be translatable
+ * @param param (array) Set of parameters to be used as sprintf arguments to fill the phrase
+ * @access public
+ * @author Christian Reiner
+ */
+ public function __construct ( $phrase, $param=array() )
+ {
+ if ( is_array($param) )
+ $this->param = $param;
+ else $this->param = array($param);
+ $this->phrase = $phrase;
+// $this->message = vsprintf ( $phrase, $this->params );
+ Exception::__construct ( vsprintf($phrase,$this->param), 1 );
+ }
+
+ /**
+ * @method OC_Shorty_Exception::getTranslation
+ * @brief: Returns the translated message of the exception
+ * @returns (string) Translated message including the filled in set of arguments
+ * @access public
+ * @author Christian Reiner
+ */
+ public function getTranslation ( )
+ {
+ return OC_Shorty_L10n::t ( $this->phrase, $this->param );
+ }
+
+ /**
+ * @method OC_Shorty_Exception::JSONerror
+ * @brief Calls OCP\JSON::error with a pretty formated version of an exception
+ * @param e (exception) an exception object holding information
+ * @returns (json) OCP\JSON::error
+ * @access public
+ * @author Christian Reiner
+ */
+ static function JSONerror ( $e )
+ {
+ $title = OC_Shorty_L10n::t("Exception");
+ switch ( get_class($e) )
+ {
+ case 'OC_Shorty_Exception':
+ $message = $e->getTranslation();
+ break;
+ case 'PDOException':
+ $message = sprintf ( OC_Shorty_L10n::t( "%s\nMessage(code): %s (%s)\nFile(line): %s (%s)\nInfo: %%s",
+ OC_Shorty_L10n::t("Exception (%s)", get_class($e)),
+ htmlspecialchars($e->getMessage()),
+ htmlspecialchars($e->getCode()),
+ htmlspecialchars($e->getFile()),
+ htmlspecialchars($e->getLine()) ),
+ (method_exists($e,'errorInfo') ? trim($e->errorInfo()) : '-/-') );
+ break;
+ default:
+ if ( is_a($e,'Exception') )
+ $message = OC_Shorty_L10n::t("Unexpected type of exception caught: %s", get_class($e));
+ else $message = OC_Shorty_L10n::t("Unknown object of type caught: %s", get_class($e));
+ } // switch
+ // swallow any accidential output generated by php notices and stuff to preserve a clean JSON reply structure
+ $output = trim ( OC_Shorty_Tools::ob_control(FALSE) );
+ if ( $output )
+ {
+ $message = "! Swallowing accidential output from ajax routines ! \n"
+ ."Please fix this ! Here is the first line: \n"
+ .substr ( $output, 0, strpos($output,"\n") );
+ OCP\Util::writeLog( 'shorty', $message, OC_Log::WARN );
+ } // output
+ // return a clean JSON error
+ return OCP\JSON::error ( array ( 'title' => $title,
+ 'message' => sprintf("%s: %s", $title, $message) ) );
+ } // function error
+} // class OC_Shorty_Exception
+
+/**
+ * @class OC_Shorty_HttpException
+ * @brief Application specific exception class: protocol layer
+ * @access public
+ * @author Christian Reiner
+ */
+class OC_Shorty_HttpException extends OC_Shorty_Exception
+{
+
+ /**
+ * @method OC_Shorty_HttpException::__construct
+ * @brief: Constructs an exception based on a phrase and a set of parameters
+ * @param status (integer) Http status code
+ * @access public
+ * @author Christian Reiner
+ */
+ public function __construct ( $status )
+ {
+ if ( is_numeric($status)
+ && array_key_exists($status,OC_Shorty_Type::$HTTPCODE) )
+ {
+ $status = intval($status);
+ $phrase = OC_Shorty_Type::$HTTPCODE[$status];
+ }
+ else
+ {
+ $status = 400;
+ $phrase = OC_Shorty_Type::$HTTPCODE[400]; // "Bad Request"
+ } // switch
+
+ // return http status code to client (browser)
+ if ( ! headers_sent() )
+ {
+ header ( sprintf("HTTP/1.0 %s %s",$status,$phrase) );
+ }
+ $tmpl = new OCP\Template("shorty", "tmpl_http_status", "guest");
+ $tmpl->assign("explanation", OC_Shorty_L10n::t($phrase));
+ $tmpl->printPage();
+ exit;
+ } // function __construct
+
+} // class OC_Shorty_HttpException
+
+?>
diff --git a/apps/shorty/lib/hooks.php b/apps/shorty/lib/hooks.php
new file mode 100644
index 00000000000..6ba59eccbda
--- /dev/null
+++ b/apps/shorty/lib/hooks.php
@@ -0,0 +1,64 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file lib/hooks.php
+ * Static class providing routines to populate hooks called by other parts of ownCloud
+ * @author Christian Reiner
+ */
+
+/**
+ * @class OC_Shorty_Hooks
+ * @brief Static 'namespace' class for api hook population
+ * ownCloud propagates to use static classes as namespaces instead of OOP.
+ * This 'namespace' defines routines to populate hooks called by other parts of ownCloud
+ * @access public
+ * @author Christian Reiner
+ */
+class OC_Shorty_Hooks
+{
+ /**
+ * @brief Deletes all Shortys and preferences of a certain user
+ * @param paramters (array) parameters from postDeleteUser-Hook
+ * @return bool
+ */
+ public static function deleteUser ( $parameters )
+ {
+ OCP\Util::writeLog ( 'user post delete','wiping all users Shortys', OCP\Util::INFO );
+ $result = TRUE;
+ $param = array ( 'user' => OCP\User::getUser() );
+ // wipe shortys
+ $query = OCP\DB::prepare ( OC_Shorty_Query::WIPE_SHORTYS );
+ if ( FALSE===$query->execute($param) )
+ $result = FALSE;
+ // wipe preferences
+ $query = OCP\DB::prepare ( OC_Shorty_Query::WIPE_PREFERENCES );
+ if ( FALSE===$query->execute($param) )
+ $result = FALSE;
+ // report completion success
+ return $result;
+ }
+}
diff --git a/apps/shorty/lib/l10n.php b/apps/shorty/lib/l10n.php
new file mode 100644
index 00000000000..648130645a0
--- /dev/null
+++ b/apps/shorty/lib/l10n.php
@@ -0,0 +1,93 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file lib/l10n.php
+ * Translation singleton
+ * @author Christian Reiner
+ */
+
+/**
+ * @class OC_Shorty_L10n
+ * @brief Convenient translation singleton
+ * @access public
+ * @author Christian Reiner
+ */
+class OC_Shorty_L10n
+{
+ /**
+ * @var OC_Shorty_L10n::dictionary
+ * @brief An internal dictionary file filled from the translation files provided.
+ * @access private
+ * @author Christian Reiner
+ */
+ private $dictionary;
+
+ /**
+ * @var OC_Shorty_L10n::instance
+ * @brief Internal singleton object
+ * @access private
+ * @author Christian Reiner
+ */
+ static private $instance=NULL;
+
+ /**
+ * @method OC_Shorty_L10n::__construct
+ * @brief
+ * @access private
+ * @author Christian Reiner
+ */
+ private function __construct ( ) { $this->dictionary = new OC_L10n('shorty'); }
+
+ /**
+ * @method OC_Shorty_L10n::t
+ * @brief Translates a given string into the users session language and fills any placeolders
+ * @param phrase to be translated
+ * @param … further arguments used as filling tokens in the tradition of printf strategies
+ * @returns translated phrase or the original phrase incase no translation could be found
+ * @access public
+ * @author Christian Reiner
+ */
+ static public function t ( $phrase )
+ {
+ // create singleton instance, if required
+ if ( ! self::$instance )
+ self::$instance = new OC_Shorty_L10n ( );
+ // handle different styles of how arguments can be handed over to this method
+ switch ( func_num_args() )
+ {
+ case 1: return self::$instance->dictionary->t ( $phrase, array() );
+ case 2: $arg = func_get_arg(1);
+ if ( is_array($arg) )
+ return self::$instance->dictionary->t ( $phrase, $arg );
+ else return self::$instance->dictionary->t ( $phrase, array($arg) );
+ default: $args = func_get_args();
+ array_shift ( $args );
+ return self::$instance->dictionary->t ( $phrase, $args );
+ }
+ }
+} // class OC_Shorty_L10n
+?>
diff --git a/apps/shorty/lib/meta.php b/apps/shorty/lib/meta.php
new file mode 100644
index 00000000000..91937bd3c8a
--- /dev/null
+++ b/apps/shorty/lib/meta.php
@@ -0,0 +1,213 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file lib/meta.php
+ * Routines to retrieve meta information about a remote url
+ * @author Christian Reiner
+ */
+
+/**
+ * @class OC_Shorty_Meta
+ * @brief Static 'namespace' class for url meta information retrieval
+ * ownCloud propagates to use static classes as namespaces instead of OOP.
+ * This 'namespace' defines routines for the retrieval of meta information about remote urls.
+ * @access public
+ * @author Christian Reiner
+ */
+class OC_Shorty_Meta
+{
+
+ /**
+ * @method OC_Shorty_Meta::fetchMetaData
+ * @brief Retrieves the meta information to a given remote url
+ * @param url decoded target url for which meta information if requested
+ * @returns associative array holding the requested meta data
+ * @access public
+ * @author Christian Reiner
+ */
+ static function fetchMetaData ( $url )
+ {
+ $url_token = parse_url ( $url );
+ // some sane fallback values, in case we cannot get the meta data
+ $meta = array();
+ $meta['target'] = $url;
+ $meta['title'] = strtolower ( $url_token['host'] );
+ $meta['scheme'] = strtolower ( $url_token['scheme'] );
+ $meta['mimetype'] = 'application/octet-stream';
+ $meta['schemicon'] = self::selectIcon ( 'scheme', strtolower($url_token['scheme']) );
+ // we wont bother retrieving data about other protocols than http or ftp
+ if ( ! in_array(strtolower($url_token['scheme']),array('http','https','ftp','ftps')) )
+ return $meta;
+ // to fetch meta data we rely on curl being installed
+ if ( ! function_exists('curl_init') )
+ return $meta;
+ // try to retrieve the meta data
+ $handle = curl_init ( );
+ curl_setopt ( $handle, CURLOPT_URL, $url );
+ curl_setopt ( $handle, CURLOPT_RETURNTRANSFER, 1 );
+ curl_setopt ( $handle, CURLOPT_FOLLOWLOCATION, TRUE );
+ curl_setopt ( $handle, CURLOPT_MAXREDIRS, 10 );
+ if ( FALSE!==($page=curl_exec($handle)) )
+ {
+ // try to extract title from page
+ preg_match ( "/<head>.*<title>(.*)<\/title>.*<\/head>/si", $page, $match );
+ $meta['title'] = htmlspecialchars_decode ( $match[1] );
+ $meta['staticon'] = self::selectIcon ( 'state', TRUE );
+ // final url after a possible redirection
+ $meta['final'] = curl_getinfo ( $handle, CURLINFO_EFFECTIVE_URL );
+ // try to extract favicon from page
+ preg_match ( '/<[^>]*link[^>]*(rel=["\']icon["\']|rel=["\']shortcut icon["\']) .*href=["\']([^>]*)["\'].*>/iU', $page, $match );
+ if (1<sizeof($match))
+ {
+ // the specified uri might be an url, an absolute or a relative path
+ // we have to turn it into an url to be able to display it out of context
+ $favicon = htmlspecialchars_decode ( $match[2] );
+ // test for an url
+ if (parse_url($favicon,PHP_URL_SCHEME))
+ {
+ $meta['favicon'] = $favicon;
+ }
+ // test for an absolute path
+ elseif ( 0===strpos(parse_url($favicon,PHP_URL_PATH),'/') )
+ {
+ $url_token = parse_url($meta['final']);
+ $meta['favicon'] = sprintf( '%s://%s/%s', $url_token['scheme'], $url_token['host'], $favicon );
+ }
+ // so it appears to be a relative path
+ else
+ {
+ $url_token = parse_url($meta['final']);
+ $meta['favicon'] = sprintf( '%s://%s%s%s', $url_token['scheme'], $url_token['host'], dirname($url_token['path']), $favicon );
+ }
+ }
+ $meta['mimetype'] = preg_replace ( '/^([^;]+);.*/i', '$1', curl_getinfo($handle,CURLINFO_CONTENT_TYPE) );
+ $meta['mimicon'] = self::selectIcon ( 'mimetype', $meta['mimetype'] );
+ $meta['code'] = curl_getinfo ( $handle, CURLINFO_HTTP_CODE );
+ $meta['status'] = OC_Shorty_L10n::t ( self::selectCode('status',$meta['code']) );
+ $meta['explanation'] = OC_Shorty_L10n::t ( self::selectCode('explanation',$meta['code']) );
+ }
+ curl_close ( $handle );
+ // that's it !
+ return $meta;
+ } // function fetchMetaData
+
+ /**
+ * @method OC_Shorty_Meta::selectCode
+ * @brief Some helper utility used to resolve numeric http status codes into human readable strings
+ * @param aspect a string indicating a section/pool a code is to be resolved in
+ * @param identifier a string indicating a specific code to be resolved
+ * @returns a human readable string resolving the specified numeric status code
+ * @throws OC_Shorty_Exception in case of an undefined code to be resolved
+ * @access public
+ * @author Christian Reiner
+ */
+ static function selectCode ( $aspect, $identifier )
+ {
+ // map of official http status codes
+ $_code_map = array
+ (
+ 'status' => OC_Shorty_Type::$HTTPCODE,
+ 'explanation' => array
+ (
+ 200 => 'Target url is valid and resolved.',
+ 201 => 'The request has been fulfilled and created a new ressource.',
+ 202 => 'The request has been accepted.',
+ 203 => 'The request yielded in non-authorative information.',
+ 204 => 'The request has been fulfilled but not produced any content.',
+ 205 => 'The request has been fulfilled and the view should be reset.',
+ 206 => 'The request has been fulfilled partially.',
+ )
+ );
+ // resolve specified code against map or provide some fallback content
+ if ( key_exists($aspect,$_code_map) && key_exists($identifier,$_code_map[$aspect]) )
+ return $_code_map[$aspect][$identifier];
+ else
+ {
+ switch ( $aspect )
+ {
+ case 'status': return sprintf("Status %s [unknown]",$identifier);
+ case 'explanation': return sprintf("[Undefined status code '%s']",$identifier);
+ default: throw new OC_Shorty_Exception ( "unknown aspect '%s' requested to resolve code '%s'",
+ array($aspect,$identifier) );
+ } // switch
+ }
+ } // function selectCode
+
+ /**
+ * @method OC_Shorty_Meta::selectIcon
+ * @brief Some helper utility for the easy integrate of icon references into templates and alike
+ * @param aspect a string indicating a section/pool an icon is to be chosen from
+ * @param identifier a string indicating a specific icon to be referenced
+ * @returns a hyper reference to an icon in form of a string
+ * @access public
+ * @author Christian Reiner
+ */
+ static function selectIcon ( $aspect, $identifier )
+ {
+ switch ( $aspect )
+ {
+ case 'state':
+ switch ($identifier)
+ {
+ case TRUE: return OCP\Util::imagePath('shorty', 'status/good.png');
+ case FALSE: return OCP\Util::imagePath('shorty', 'status/bad.png');
+ default: return OCP\Util::imagePath('shorty', 'status/neutral.png');
+ } // switch identifier
+ case 'scheme':
+ switch ($identifier)
+ {
+ case 'http':
+ case 'https': return OCP\Util::imagePath('shorty', 'scheme/H.png');
+ case 'ftp':
+ case 'ftps': return OCP\Util::imagePath('shorty', 'scheme/F.png');
+ case 'sftp': return OCP\Util::imagePath('shorty', 'scheme/S.png');
+ case 'mailto': return OCP\Util::imagePath('shorty', 'scheme/M.png');
+ case 'gopher': return OCP\Util::imagePath('shorty', 'scheme/G.png');
+ case 'webdav':
+ case 'webdavs': return OCP\Util::imagePath('shorty', 'scheme/W.png');
+ default: return OCP\Util::imagePath('shorty', 'blank.png');
+ } // switch identifier
+ case 'mimetype':
+ $identifier = explode('/',$identifier);
+ switch ($identifier[0])
+ {
+ case 'audio': return OCP\Util::imagePath('core', 'filetypes/audio.png');
+ case 'text': return OCP\Util::imagePath('core', 'filetypes/text.png');
+ case 'video': return OCP\Util::imagePath('core', 'filetypes/video.png');
+ case 'application':
+ switch ($identifier[1])
+ {
+ case 'pdf': return OCP\Util::imagePath('core', 'filetypes/application-pdf.png');
+ default: return OCP\Util::imagePath('shorty', 'blank.png');
+ } // switch identifier[1]
+ default: return OCP\Util::imagePath('shorty', 'blank.png');
+ } // switch identifier[0]
+ } // switch aspect
+ } // function selectIcon
+
+} // class OC_Shorty_Meta
+?>
diff --git a/apps/shorty/lib/query.php b/apps/shorty/lib/query.php
new file mode 100644
index 00000000000..6501a54d927
--- /dev/null
+++ b/apps/shorty/lib/query.php
@@ -0,0 +1,57 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file lib/query.php
+ * Static catalog of sql queries
+ * @author Christian Reiner
+ */
+
+/**
+ * @class OC_Shorty_Query
+ * @brief Static catalog of sql queries
+ * These query templates are referenced by a OC_Shorty_Query::URL_...
+ * They have to be prapared by adding an array of parameters
+ * @access public
+ * @author Christian Reiner
+ */
+class OC_Shorty_Query
+{
+ const URL_INSERT = "INSERT INTO *PREFIX*shorty (id,status,favicon,title,source,target,user,until,created,notes) VALUES (:id,:status,:favicon,:title,:source,:target,:user,:until,CURRENT_DATE,:notes)";
+ const URL_DELETE = "DELETE FROM *PREFIX*shorty WHERE user=:user AND id=:id";
+ const URL_REMOVE = "DELETE FROM *PREFIX*shorty WHERE user=:user AND 'deleted'=status";
+ const URL_UPDATE = "UPDATE *PREFIX*shorty SET status=:status,title=:title,until=:until,notes=:notes WHERE user=:user AND id=:id";
+ const URL_STATUS = "UPDATE *PREFIX*shorty SET status=:status WHERE user=:user AND id=:id";
+ const URL_CLICK = "UPDATE *PREFIX*shorty SET accessed=CURRENT_TIMESTAMP, clicks=(clicks+1) WHERE id=:id";
+ const URL_FORWARD = "SELECT user,source,target,status,(until IS NOT NULL AND until!='' AND until<CURRENT_TIMESTAMP) AS expired FROM *PREFIX*shorty WHERE id=:id";
+ const URL_SOURCE = "SELECT id,source,target,status,(until IS NOT NULL AND until!='' AND until<CURRENT_TIMESTAMP) AS expired FROM *PREFIX*shorty WHERE source=:source";
+ const URL_VERIFY = "SELECT id,status,favicon,title,source,target,clicks,created,accessed,until,notes FROM *PREFIX*shorty WHERE user=:user AND id=:id LIMIT 1";
+ const URL_LIST = "SELECT id,status,favicon,title,source,target,clicks,created,accessed,until,notes FROM *PREFIX*shorty WHERE user=:user ORDER BY :sort";
+ const URL_COUNT = "SELECT count(*) AS sum_shortys,IFNULL(sum(clicks),0) AS sum_clicks FROM *PREFIX*shorty WHERE user=:user";
+ const WIPE_SHORTYS = "DELETE FROM *PREFIX*shorty WHERE user=:user";
+ const WIPE_PREFERENCES = "DELETE FROM *PREFIX*preferences WHERE user=:user";
+} // class OC_Shorty_Query
+?>
diff --git a/apps/shorty/lib/tools.php b/apps/shorty/lib/tools.php
new file mode 100644
index 00000000000..2b3bac70672
--- /dev/null
+++ b/apps/shorty/lib/tools.php
@@ -0,0 +1,235 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file lib/tools.php
+ * A collection of general utility routines
+ * @author Christian Reiner
+ */
+
+/**
+ * @class OC_Shorty_Tools
+ * @brief Collection of a few practical routines, a tool box
+ * @access public
+ * @author Christian Reiner
+ */
+class OC_Shorty_Tools
+{
+ // internal flag indicating if output buffering should be used to prevent accidentially output during ajax requests
+ static $ob_usage = TRUE;
+ // internal flag indicating if there is currently an output buffer active
+ static $ob_active = FALSE;
+
+ /**
+ * @method OC_Shorty_Tools::ob_control
+ * @param on (boolean) wether to activate or deactivate the buffer
+ * @access public
+ * @author Christian Reiner
+ */
+ static function ob_control ( $on=TRUE )
+ {
+ $output = NULL;
+ if ( self::$ob_usage )
+ {
+ // attempt to use outpout buffering
+ if ( $on )
+ {
+ // start buffering if possible and not yet started before
+ if ( function_exists('ob_start') // output buffers installed at all ?
+ && ! self::$ob_active ) // don't stack buffers (create buffer only, if not yet started)
+ {
+ ob_implicit_flush ( FALSE );
+ ob_start ( );
+ self::$ob_active = TRUE;
+ }
+ } // if $on==TRUE
+ else
+ {
+ // end buffering _if_ it has been started before
+ if ( self::$ob_active )
+ {
+ $output = ob_get_contents ( );
+ ob_end_clean ( );
+ self::$ob_active = FALSE;
+ }
+ } // if $on==FALSE
+ } // if ob_usage
+ return $output;
+ } // function ob_control
+
+ /**
+ * @method OC_Shorty_Tools::db_escape
+ * @brief escape a value for incusion in db statements
+ * @param value (string) value to be escaped
+ * @returns (string) escaped string value
+ * @throws OC_Shorty_Exception in case of an unknown database engine
+ * @access public
+ * @author Christian Reiner
+ * @todo use mdb2::quote() / mdb2:.escape() instead ?
+ */
+ static function db_escape ( $value )
+ {
+ $type = OCP\Config::getSystemValue ( 'dbtype', 'sqlite' );
+ switch ( $type )
+ {
+ case 'sqlite':
+ case 'sqlite3':
+ return sqlite_escape_string ( $value );
+ case 'pgsql':
+ return pg_escape_string ( $value );
+ case 'mysql':
+ if (get_magic_quotes_gpc())
+ return mysql_real_escape_string ( stripslashes($value) );
+ else return mysql_real_escape_string ( $value );
+ }
+ throw new OC_Shorty_Exception ( "unknown database backend type '%1'", array($type) );
+ } // function db_escape
+
+ /**
+ * @method OC_Shorty_Tools::db_timestamp
+ * @brief current timestamp as required by db engine
+ * @returns (string) current timestamp as required by db engine
+ * @throws OC_Shorty_Exception in case of an unknown database engine
+ * @access public
+ * @author Christian Reiner
+ * @todo not really required any more, we rely on CURRENT_TIMESTAMP instead
+ */
+ static function db_timestamp ( )
+ {
+ $type = OCP\Config::getSystemValue( "dbtype", "sqlite" );
+ switch ( $type )
+ {
+ case 'sqlite':
+ case 'sqlite3': return "strftime('%s','now')";
+ case 'mysql': return 'UNIX_TIMESTAMP()';
+ case 'pgsql': return "date_part('epoch',now())::integer";
+ }
+ throw new OC_Shorty_Exception ( "unknown database backend type '%1'", array($type) );
+ } // function db_timestamp
+
+ /**
+ * @method OC_Shorty_Tools::shorty_id
+ * @brief Creates a unique id to be used for a new shorty entry
+ * @returns (string) valid and unique id
+ * @access public
+ * @author Christian Reiner
+ */
+ static function shorty_id ( )
+ {
+ // each shorty installation uses a (once self generated) 62 char alphabet
+ $alphabet=OCP\Config::getAppValue('shorty','id-alphabet');
+ if ( empty($alphabet) )
+ {
+ $alphabet = self::randomAlphabet(62);
+ OCP\Config::setAppValue ( 'shorty', 'id-alphabet', $alphabet );
+ }
+ // use alphabet to generate a id being unique over time
+ return self::convertToAlphabet ( str_replace(array(' ','.'),'',microtime()), $alphabet );
+ } // function shorty_id
+
+ /**
+ *
+ */
+ static function randomAlphabet ($length)
+ {
+ if ( ! is_integer($length) )
+ return FALSE;
+ $c = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxwz0123456789";
+ for($l=0;$l<$length;$l++) $s .= $c{rand(0,strlen($c))};
+ return str_shuffle($s);
+ } // function randomAlphabet
+
+ /**
+ * @method OC_Shorty_Tools::convertToAlphabet
+ * @brief Converts a given decimal number into an arbitrary base (alphabet)
+ * @param number decimal value to be converted
+ * @returns (string) converted value in string notation
+ * @access public
+ * @author Christian Reiner
+ */
+ static function convertToAlphabet ( $number, $alphabet )
+ {
+ $alphabetLen = strlen($alphabet);
+ $decVal = (int) $number;
+ $number = FALSE;
+ $nslen = 0;
+ $pos = 1;
+ while ($decVal > 0)
+ {
+ $valPerChar = pow($alphabetLen, $pos);
+ $curChar = floor($decVal / $valPerChar);
+ if ($curChar >= $alphabetLen)
+ {
+ $pos++;
+ } else {
+ $decVal -= ($curChar * $valPerChar);
+ if ($number === FALSE)
+ {
+ $number = str_repeat($alphabet{1}, $pos);
+ $nslen = $pos;
+ }
+ $number = substr($number, 0, ($nslen - $pos)) . $alphabet{$curChar} . substr($number, (($nslen - $pos) + 1));
+ $pos--;
+ }
+ }
+ if ($number === FALSE) $number = $alphabet{1};
+ return $number;
+ }
+
+ /**
+ * @method OC_Shorty_Tools::relayUrl
+ * @brief Generates a relay url for a given id acting as a href target for all backends
+ * @param id (string) shorty id as shorty identification
+ * @returns (string) generated absolute relay url
+ * @access public
+ * @author Christian Reiner
+ */
+ static function relayUrl ($id)
+ {
+ return sprintf ( '%s?service=%s&id=%s', OCP\Util::linkToAbsolute("", "public.php"), 'shorty_relay', $id );
+ } // function relayUrl
+
+ /**
+ * @method OC_Shorty_Tools::countShortys
+ * @brief Returns the total number of entries and clicks from the database
+ * @returns (array) two elements sum_shortys & sum_clicks holding an integer each
+ * @access public
+ * @author Christian Reiner
+ */
+ static function countShorties ()
+ {
+ $param = array
+ (
+ ':user' => OCP\User::getUser ( ),
+ );
+ $query = OCP\DB::prepare ( OC_Shorty_Query::URL_COUNT );
+ $result = $query->execute($param);
+ $reply = $result->fetchAll();
+ return $reply[0];
+ } // function countShorties
+
+} // class OC_Shorty_Tools
+?>
diff --git a/apps/shorty/lib/type.php b/apps/shorty/lib/type.php
new file mode 100644
index 00000000000..97cbf04ddb4
--- /dev/null
+++ b/apps/shorty/lib/type.php
@@ -0,0 +1,291 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file lib/type.php
+ * Type handling, recognition and verification routines
+ * @author Christian Reiner
+ */
+
+/**
+ * @class OC_Shorty_Type
+ * @brief Static 'namespace' class offering routines and constants used to handle type recognition and value verification
+ * @access public
+ * @author Christian Reiner
+ */
+class OC_Shorty_Type
+{
+ // the 'types' of values we deal with, actually more something like flavours
+ const ID = 'id';
+ const STATUS = 'status';
+ const SORTKEY = 'sortkey';
+ const SORTVAL = 'sortval';
+ const STRING = 'string';
+ const URL = 'url';
+ const INTEGER = 'integer';
+ const FLOAT = 'float';
+ const DATE = 'date';
+ const TIMESTAMP = 'timestamp';
+ // a list of all valid list sorting codes
+ static $SORTING = array (
+ '' =>'created DESC', // default
+ 'aa'=>'accessed', 'ad'=>'accessed DESC',
+ 'ca'=>'created', 'cd'=>'created DESC',
+ 'da'=>'until', 'dd'=>'until DESC',
+ 'ha'=>'clicks', 'hd'=>'clicks DESC',
+ 'ka'=>'id', 'kd'=>'id DESC',
+ 'sa'=>'status', 'sd'=>'status DESC',
+ 'ta'=>'title', 'td'=>'title DESC',
+ 'ua'=>'target', 'ud'=>'target DESC' );
+ // a list of all valid user preferences
+ static $PREFERENCE = array (
+ 'backend-type' => OC_Shorty_Type::STRING,
+ 'backend-static-base' => OC_Shorty_Type::URL,
+ 'backend-bitly-user' => OC_Shorty_Type::STRING,
+ 'backend-bitly-key' => OC_Shorty_Type::STRING,
+ 'backend-google-key' => OC_Shorty_Type::STRING,
+ 'backend-tinycc-user' => OC_Shorty_Type::STRING,
+ 'backend-tinycc-key' => OC_Shorty_Type::STRING,
+ 'sms-control' => OC_Shorty_Type::STRING,
+ 'list-sort-code' => OC_Shorty_Type::SORTKEY,
+ );
+ // valid status for entries
+ static $STATUS = array (
+ 'blocked',
+ 'private',
+ 'shared',
+ 'public',
+ 'deleted',
+ );
+ // a list of implemented backends
+ static $BACKENDS = array (
+ 'none' => ' [ none ] ',
+ 'static' => 'static backend',
+// 'bitly' => 'bitly.com service',
+// 'cligs' => 'cli.gs service',
+ 'isgd' => 'is.gd service',
+ 'google' => 'goo.gl service',
+// 'tinycc' => 'tiny.cc service',
+ 'tinyurl' => 'ti.ny service',
+ );
+ // a list of all valid system settings
+ static $SETTING = array (
+ 'backend-static-base' => OC_Shorty_Type::URL,
+ );
+ static $HTTPCODE = array (
+ 200 => 'Ok',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 306 => '(unused)',
+ 307 => 'Temporary Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Timeout',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Long',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested Range Not Satisfiable',
+ 417 => 'Expectation Failed',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Timeout',
+ 505 => 'HTTP Version Not Supported',
+ );
+
+ /**
+ * @method OC_Shorty_Type::validate
+ * @brief Validates a given value against a type specific regular expression
+ * Validates a given value according to the claimed type of the value.
+ * Validation is done by matching the value against a type specific regular expression.
+ * @param value the value to be verified according to the specified type
+ * @param type the type the value is said to belong to, important for verification
+ * @param strict flag indicating if the verification should be done strict, that is if an exception should be thrown in case of a failure
+ * @returns the value itself in case of a positive validation, NULL or an exception in case of a failure, depending on the flag indication strict mode
+ * @throws error indicating a failed validation in case of strict mode
+ * @access public
+ * @author Christian Reiner
+ */
+ static function validate ( $value, $type, $strict=FALSE )
+ {
+ switch ( $type )
+ {
+ case self::ID:
+ if ( preg_match ( '/^[a-z0-9]{2,20}$/i', $value ) )
+ return $value;
+ elseif ( ! $strict)
+ return NULL;
+ throw new OC_Shorty_Exception ( "invalid value '%s' for type '%s'", array( ((24<sizeof($value))?$value:substr($value,0,21).'…'),$type) );
+ case self::STATUS:
+ if ( in_array($value,OC_Shorty_Type::$STATUS) )
+ return $value;
+ elseif ( ! $strict)
+ return NULL;
+ throw new OC_Shorty_Exception ( "invalid value '%s' for type '%s'", array( ((24<sizeof($value))?$value:substr($value,0,21).'…'),$type) );
+ case self::SORTKEY:
+ if ( array_key_exists ( trim($value), self::$SORTING ) )
+ return $value;
+ elseif ( ! $strict)
+ return NULL;
+ throw new OC_Shorty_Exception ( "invalid value '%s' for type '%s'", array( ((24<sizeof($value))?$value:substr($value,0,21).'…'),$type) );
+ case self::SORTVAL:
+ if ( in_array ( trim($value), self::$SORTING ) )
+ return $value;
+ elseif ( ! $strict)
+ return NULL;
+ throw new OC_Shorty_Exception ( "invalid value '%s' for type '%s'", array( ((24<sizeof($value))?$value:substr($value,0,21).'…'),$type) );
+ case self::STRING:
+ if ( preg_match ( '/^.*$/x', str_replace("\n","\\n",$value) ) )
+ return str_replace("\n","\\n",$value);
+ elseif ( ! $strict)
+ return NULL;
+ throw new OC_Shorty_Exception ( "invalid value '%s' for type '%s'", array( ((24<sizeof($value))?$value:substr($value,0,21).'…'),$type) );
+ case self::URL:
+// $pattern = '/^([a-zA-Z][a-zA-Z][a-zA-Z0-9]+)\:\/\/([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&amp;%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\:[0-9]+)*(\/($|[a-zA-Z0-9\.\;\:\,\@\?\'\\\+&amp;%\$#\=~_\-]+)?)*$/';
+ $pattern = '/^([a-zA-Z][a-zA-Z][a-zA-Z0-9]+)\:\/\/([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&amp;%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\:[0-9]+)*(\/($|.+)?)*$/';
+ if ( preg_match ( $pattern, $value ) )
+ return $value;
+ elseif ( ! $strict)
+ return NULL;
+ throw new OC_Shorty_Exception ( "invalid value '%s' for type '%s'", array( ((24<sizeof($value))?$value:substr($value,0,21).'…'),$type) );
+ case self::INTEGER:
+ if ( preg_match ( '/^[0-9]+$/', $value ) )
+ return $value;
+ elseif ( ! $strict)
+ return NULL;
+ throw new OC_Shorty_Exception ( "invalid value '%s' for type '%s'", array( ((24<sizeof($value))?$value:substr($value,0,21).'…'),$type) );
+ case self::FLOAT:
+ if ( preg_match ( '/^[0-9]+(\.[0-9]+)?$/', $value ) )
+ return $value;
+ throw new OC_Shorty_Exception ( "invalid value '%s' for type '%s'", array( ((24<sizeof($value))?$value:substr($value,0,21).'…'),$type) );
+ case self::TIMESTAMP:
+ if ( preg_match ( '/^[0-9]{10}$/', $value ) )
+ return $value;
+ elseif ( ! $strict)
+ return NULL;
+ throw new OC_Shorty_Exception ( "invalid value '%s' for type '%s'", array( ((24<sizeof($value))?$value:substr($value,0,21).'…'),$type) );
+ case self::DATE:
+ if (FALSE!==($time=strtotime($value)))
+ return $time;
+ elseif ( ! $strict)
+ return NULL;
+ throw new OC_Shorty_Exception ( "invalid value '%s' for type '%s'", array( ((24<sizeof($value))?$value:substr($value,0,21).'…'),$type) );
+ } // switch $type
+ throw new OC_Shorty_Exception ( "unknown request argument type '%s'", array($type) );
+ } // function is_valid
+
+ /**
+ * @method OC_Shorty_Type::normalize
+ * @brief cleanup and formal normalization of a given value according to its type
+ * Normalizes a given value according to its claimed type.
+ * This typically means trimming of string values, but sometimes also more specific actions.
+ * @param value the value to be normalized
+ * @param type the supposed type of the value
+ * @param strict boolean flag indicating if the normalization should be done in a strict way
+ * @returns the normalized value
+ * @throws error indicating a parameter violation
+ * @access public
+ * @author Christian Reiner
+ */
+ static function normalize ( $value, $type, $strict=FALSE )
+ {
+ if (NULL===(self::validate($value,$type,$strict)))
+ {
+ if ( ! $strict)
+ return NULL;
+ else
+ throw new OC_Shorty_Exception ( "invalid value '%1\$s' for type '%2\$s'", array($value,$type) );
+ } // if
+ switch ( $type )
+ {
+ case self::ID: return trim ( $value );
+ case self::STATUS: return trim ( $value );
+ case self::SORTKEY: return trim ( $value );
+ case self::SORTVAL: return trim ( $value );
+ case self::STRING: return trim ( $value );
+ case self::URL: return trim ( $value );
+ case self::INTEGER: return sprintf ( '%d', $value );
+ case self::FLOAT: return sprintf ( '%f', $value );
+ case self::TIMESTAMP: return trim ( $value );
+ case self::DATE: return date ( 'Y-m-d', self::validate($value,OC_Shorty_Type::DATE) );
+ } // switch $type
+ throw new OC_Shorty_Exception ( "unknown request argument type '%s'", array($type) );
+ } // function normalize
+
+ /**
+ * @method OC_Shorty_Type::req_argument
+ * @brief returns checked request argument or throws an error
+ * @param arg (string) name of the request argument to get_argument
+ * @param strict (bool) controls if an exception will be thrown upon a missing argument
+ * @returns (string) checked and prepared value of request argument
+ * @throws error indicating a parameter violation
+ * @access public
+ * @author Christian Reiner
+ */
+ static function req_argument ( $arg, $type, $strict=FALSE )
+ {
+ switch ( $_SERVER['REQUEST_METHOD'] )
+ {
+ case 'POST':
+ if ( isset($_POST[$arg]) && !empty($_POST[$arg]) )
+ return self::normalize ( urldecode($_POST[$arg]), $type ) ;
+ elseif ( ! $strict)
+ return NULL;
+ throw new OC_Shorty_Exception ( "missing mandatory argument '%1s'", array($arg) );
+ case 'GET':
+ if ( isset($_GET[$arg]) && !empty($_GET[$arg]) )
+ return self::normalize ( urldecode(trim($_GET[$arg])), $type, $strict );
+ elseif ( ! $strict)
+ return NULL;
+ throw new OC_Shorty_Exception ( "missing mandatory argument '%1s'", array($arg) );
+ default:
+ throw new OC_Shorty_Exception ( "unexpected http request method '%1s'", array($_SERVER['REQUEST_METHOD']) );
+ }
+ } // function req_argument
+
+} // class OC_Shorty_Query
+?>
diff --git a/apps/shorty/preferences.php b/apps/shorty/preferences.php
new file mode 100644
index 00000000000..6e122fe410b
--- /dev/null
+++ b/apps/shorty/preferences.php
@@ -0,0 +1,68 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file settings.php
+ * This plugins user preferences dialog
+ * The dialog will be included in the general framework of the user preferences page
+ * @access public
+ * @author Christian Reiner
+ */
+
+OCP\Util::addStyle ( '3rdparty', 'chosen/chosen' );
+OCP\Util::addStyle ( 'shorty', 'shorty' );
+OCP\Util::addStyle ( 'shorty', 'preferences' );
+
+OCP\Util::addScript ( '3rdparty', 'chosen/chosen.jquery.min' );
+OCP\Util::addScript ( 'shorty', 'shorty' );
+OCP\Util::addScript ( 'shorty', 'preferences' );
+if ( OC_Log::DEBUG==OC_Config::getValue( "loglevel", OC_Log::WARN ) )
+ OCP\Util::addScript ( 'shorty', 'debug' );
+
+
+// fetch template
+$tmpl = new OCP\Template ( 'shorty', 'tmpl_preferences' );
+// inflate template
+$backend_types = OC_Shorty_Type::$BACKENDS;
+// kick out static option again if no global backend base has been specified in the system settings
+$backend_static_base = OCP\Config::getAppValue('shorty','backend-static-base','');
+if ( empty($backend_static_base)
+ || !parse_url($backend_static_base,PHP_URL_SCHEME)
+ || !parse_url($backend_static_base,PHP_URL_HOST) )
+ unset($backend_types['static']);
+// feed template engine
+$tmpl->assign ( 'backend-types', $backend_types );
+$tmpl->assign ( 'backend-static-base', $backend_static_base );
+$tmpl->assign ( 'backend-bitly-user', OCP\Config::getUserValue(OCP\User::getUser(),'shorty','backend-bitly-user','') );
+$tmpl->assign ( 'backend-bitly-key', OCP\Config::getUserValue(OCP\User::getUser(),'shorty','backend-bitly-key','') );
+$tmpl->assign ( 'backend-google-key', OCP\Config::getUserValue(OCP\User::getUser(),'shorty','backend-google-key','') );
+$tmpl->assign ( 'backend-tinycc-user', OCP\Config::getUserValue(OCP\User::getUser(),'shorty','backend-tinycc-user','') );
+$tmpl->assign ( 'backend-tinycc-key', OCP\Config::getUserValue(OCP\User::getUser(),'shorty','backend-tinycc-key','') );
+$tmpl->assign ( 'backend-type', OCP\Config::getUserValue(OCP\User::getUser(),'shorty','backend-type','') );
+$tmpl->assign ( 'sms-control', OCP\Config::getUserValue(OCP\User::getUser(),'shorty','sms-control','disabled') );
+// render template
+return $tmpl->fetchPage ( );
+?>
diff --git a/apps/shorty/qrcode.php b/apps/shorty/qrcode.php
new file mode 100644
index 00000000000..43b91b22afb
--- /dev/null
+++ b/apps/shorty/qrcode.php
@@ -0,0 +1,100 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file qrcode.php
+ * Generates qr code barcodes cading a specified url
+ * @access public
+ * @author Christian Reiner
+ */
+
+require_once ( '3rdparty/php/phpqrcode.php' );
+
+$source = NULL;
+// we try to guess what the request indicates:
+// - a (source) url to be looked up in the database
+foreach ($_GET as $key=>$val) // in case there are unexpected, additional arguments like a timestamp added by some stupid proxy
+{
+ switch ($key)
+ {
+ default:
+ // unrecognized key, we ignore it
+ break;
+ case 'url':
+ case 'uri':
+ case 'ref':
+ case 'source':
+ case 'target':
+ // a recognized argument key indicating an id to be looked up
+ $source = OC_Shorty_Type::req_argument($key,OC_Shorty_Type::URL,FALSE);
+ break 2; // skip switch AND foreach
+ } // switch
+} // foreach
+
+// generate qr code for the specified url, IF it exists and is usable in the database
+try
+{
+ if ( $source )
+ {
+ $param = array ( 'source' => OC_Shorty_Type::normalize($source,OC_Shorty_Type::URL) );
+ $query = OCP\DB::prepare ( OC_Shorty_Query::URL_SOURCE );
+ $result = $query->execute($param)->FetchAll();
+
+ if ( FALSE===$result )
+ throw new OC_Shorty_HttpException ( 500 );
+ elseif ( ! is_array($result) )
+ throw new OC_Shorty_HttpException ( 500 );
+ elseif ( 0==sizeof($result) )
+ {
+ // no entry found => 404: Not Found
+ throw new OC_Shorty_HttpException ( 404 );
+ }
+ elseif ( 1<sizeof($result) )
+ {
+ // multiple matches => 409: Conflict
+ throw new OC_Shorty_HttpException ( 409 );
+ }
+ elseif ( (!array_key_exists(0,$result)) || (!is_array($result[0])) || (!array_key_exists('source',$result[0])) )
+ {
+ // invalid entry => 500: Internal Server Error
+ throw new OC_Shorty_HttpException ( 500 );
+ }
+ elseif ( (!array_key_exists('source',$result[0])) || ('1'==$result[0]['expired']) )
+ {
+ // entry expired => 410: Gone
+ throw new OC_Shorty_HttpException ( 410 );
+ }
+ // generate qrcode, regardless of who sends the request
+ QRcode::png ( $source );
+ } // if $source
+ else
+ {
+ // refuse forwarding => 403: Forbidden
+ throw new OC_Shorty_HttpException ( 403 );
+ }
+} catch ( OC_Shorty_Exception $e ) { header($e->getMessage()); }
+
+?>
diff --git a/apps/shorty/relay.php b/apps/shorty/relay.php
new file mode 100644
index 00000000000..077a3625afb
--- /dev/null
+++ b/apps/shorty/relay.php
@@ -0,0 +1,140 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file relay.php
+ * This is the plugins central relaying feature
+ * All relay requests are handled by this file.
+ * @access public
+ * @author Christian Reiner
+ */
+
+OCP\App::setActiveNavigationEntry ( 'shorty_index' );
+
+$arg = NULL;
+// we try to guess what the request indicates:
+// - a (shorty) id to be looked up in the database resulting in a forwarding to the stored target
+// - a (target) url to be added as a new shorty
+// - none of the two, so just a plain list of existing shortys
+foreach ($_GET as $key=>$val) // in case there are unexpected, additional arguments like a timestamp added by some stupid proxy
+{
+ switch ($key)
+ {
+ default:
+ // unrecognized key, we ignore it
+ break;
+ case 'id':
+ case 'shorty':
+ case 'ref':
+ case 'entry':
+ // a recognized argument key indicating an id to be looked up
+ $arg = OC_Shorty_Type::req_argument($key,OC_Shorty_Type::ID,FALSE);
+ break 2; // skip switch AND foreach
+ } // switch
+} // foreach
+
+// an id was specified, ordinary or special meaning ?
+if ( '0000000000'==$arg )
+{
+ // this is a pseudo id, used to test the setup, so just return a positive message.
+ // this is used to test the setup of the static backend, shorty calls itself from there
+ OCP\Util::writeLog( 'shorty', "Positiv validation of static backend base url", OC_Log::INFO );
+ OCP\JSON::success ( array ( ) );
+ exit();
+}
+
+// now construct the target url and relay to it (if applicable)
+try
+{
+ // detect requested shorty id from request
+ $p_id = trim ( OC_Shorty_Type::normalize($arg,OC_Shorty_Type::ID) ) ;
+ if ( $p_id )
+ {
+ $param = array
+ (
+ 'id' => $p_id,
+ );
+ $query = OCP\DB::prepare ( OC_Shorty_Query::URL_FORWARD );
+ $result = $query->execute($param)->FetchAll();
+ if ( FALSE===$result )
+ throw new OC_Shorty_HttpException ( 500 );
+ elseif ( ! is_array($result) )
+ throw new OC_Shorty_HttpException ( 500 );
+ elseif ( 0==sizeof($result) )
+ {
+ // no entry found => 404: Not Found
+ throw new OC_Shorty_HttpException ( 404 );
+ }
+ elseif ( 1<sizeof($result) )
+ {
+ // multiple matches => 409: Conflict
+ throw new OC_Shorty_HttpException ( 409 );
+ }
+ elseif ( (!array_key_exists(0,$result)) || (!is_array($result[0])) || (!array_key_exists('target',$result[0])) )
+ {
+ // invalid entry => 500: Internal Server Error
+ throw new OC_Shorty_HttpException ( 500 );
+ }
+ elseif ( (!array_key_exists('target',$result[0])) || ('1'==$result[0]['expired']) )
+ {
+ // entry expired => 410: Gone
+ throw new OC_Shorty_HttpException ( 410 );
+ }
+ // an usable target !
+ $target = trim($result[0]['target']);
+ // check status of matched entry
+ switch (trim($result[0]['status']))
+ {
+ default:
+ case 'blocked':
+ // refuse forwarding => 403: Forbidden
+ throw new OC_Shorty_HttpException ( 403 );
+ case 'private':
+ // check if user owns the Shorty, deny access if not
+ if ( $result[0]['user']!=OCP\User::getUser() )
+ // refuse forwarding => 403: Forbidden
+ throw new OC_Shorty_HttpException ( 403 );
+ // NO break; but fall through to the action in 'case public:'
+ case 'shared':
+ // check if we are a user, deny access if not
+ if ( ! OCP\User::isLoggedIn() )
+ // refuse forwarding => 403: Forbidden
+ throw new OC_Shorty_HttpException ( 403 );
+ // NO break; but fall through to the action in 'case public:'
+ case 'public':
+ // forward to target, regardless of who sends the request
+ header("HTTP/1.0 301 Moved Permanently");
+ // http forwarding header
+ header ( sprintf('Location: %s', $target) );
+ } // switch status
+ // register click in shorty
+ $query = OCP\DB::prepare ( OC_Shorty_Query::URL_CLICK );
+ $query->execute ( $param );
+ exit();
+ } // if id
+} catch ( OC_Shorty_Exception $e ) { header($e->getMessage()); }
+
+?>
diff --git a/apps/shorty/settings.php b/apps/shorty/settings.php
new file mode 100644
index 00000000000..f10e354a7be
--- /dev/null
+++ b/apps/shorty/settings.php
@@ -0,0 +1,52 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+
+/**
+ * @file settings.php
+ * This plugins system settings dialog
+ * The dialog will be included in the general framework of the system settings page
+ * @access public
+ * @author Christian Reiner
+ */
+
+OCP\Util::addStyle ( '3rdparty', 'chosen/chosen' );
+OCP\Util::addStyle ( 'shorty', 'shorty' );
+OCP\Util::addStyle ( 'shorty', 'settings' );
+
+OCP\Util::addScript ( '3rdparty', 'chosen/chosen.jquery.min' );
+OCP\Util::addScript ( 'shorty', 'shorty' );
+OCP\Util::addScript ( 'shorty', 'settings' );
+if ( OC_Log::DEBUG==OC_Config::getValue( "loglevel", OC_Log::WARN ) )
+ OCP\Util::addScript ( 'shorty', 'debug' );
+
+
+// fetch template
+$tmpl = new OCP\Template ( 'shorty', 'tmpl_settings' );
+// inflate template
+$tmpl->assign ( 'backend-static-base', OCP\Config::getAppValue('shorty','backend-static-base','') );
+// render template
+return $tmpl->fetchPage ( );
+?>
diff --git a/apps/shorty/templates/tmpl_dlg_qrcode.php b/apps/shorty/templates/tmpl_dlg_qrcode.php
new file mode 100644
index 00000000000..15b0eb2c366
--- /dev/null
+++ b/apps/shorty/templates/tmpl_dlg_qrcode.php
@@ -0,0 +1,52 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+?>
+
+<?php
+/**
+ * @file templates/tmpl_dlg_qrcode.php
+ * Dialog popup to visualize and offer an url as a QRCode (2D barcode)
+ * @access public
+ * @author Christian Reiner
+ */
+?>
+
+<!-- additional (hidden) popup dialogs for specific usage actions -->
+<fieldset id="dialog-qrcode" style="display:none;" class="" style="align:center;">
+ <input id="qrcode-url" type="hidden" value="<?php echo $_['qrcode-url']; ?>">
+ <span id='qrcode-img'>
+ <?php echo $l->t("Click for qrcode url").":"; ?>
+ <br>
+ <img width="100%" class="shorty-status" border="1" alt="<?php echo $l->t("QRCode"); ?>"
+ src="<?php echo OCP\Util::imagePath('shorty','loading-disk.gif'); ?>" >
+ </span>
+ <span id='qrcode-val' style="display:none;">
+ <?php echo $l->t("Click for qrcode image").":"; ?>
+ <br>
+ <span class="shorty-framed"><a title="<?php echo $l->t("QRCode url").":"; ?>"></a></span>
+ </span>
+</fieldset>
+<!-- end of qrcode dialog -->
diff --git a/apps/shorty/templates/tmpl_dlg_verify.php b/apps/shorty/templates/tmpl_dlg_verify.php
new file mode 100644
index 00000000000..14b10d84174
--- /dev/null
+++ b/apps/shorty/templates/tmpl_dlg_verify.php
@@ -0,0 +1,70 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+?>
+
+<?php
+/**
+ * @file templates/tmpl_dlg_verify.php
+ * Dialog popup to validate a configured static backend base
+ * @access public
+ * @author Christian Reiner
+ */
+?>
+
+<!-- a (usually hidden) dialog used for verification of the correct setup of the 'static' backend -->
+<div id="dialog-verification" style="display:none;" title="<?php echo $l->t("'Static' backend: base url verification"); ?>">
+ <!-- verification-in-progress -->
+ <div id="hourglass">
+ <img src="<?php echo OCP\Util::imagePath('shorty', 'loading-disk.gif'); ?>">
+ </div>
+ <!-- success -->
+ <div id="success" style="display:none;">
+ <fieldset>
+ <legend>
+ <img class="shorty-status" src="<?php echo OCP\Util::imagePath('shorty','status/good.png'); ?>" alt="<?php $l->t('Success') ?>" title="<?php $l->t('Verification successful') ?>">
+ <span id="title" class="title"><strong>Verification successful !</strong></span>
+ </legend>
+ <?php echo $l->t("<p>Great, your setup appears to be working fine ! </p>".
+ "<p>Requests to the configured base url '%s' are mapped to this ownClouds shorty module at '%1\$s'</p>".
+ "<p>Usage of that static backend is fine and safe as long as this setup is not altered.</p>",
+ array('<a id="verification-target" style="font-family:Monospace;"></a>',OCP\Util::linkToAbsolute('shorty','index.php')) );?>
+ </fieldset>
+ </div>
+ <!-- failure -->
+ <div id="failure" style="display:none;">
+ <fieldset>
+ <legend>
+ <img class="shorty-status" src="<?php echo OCP\Util::imagePath('shorty','status/bad.png'); ?>" alt="<?php $l->t('Success') ?>" title="<?php $l->t('Verification successful') ?>">
+ <span id="title" class="title"><strong>Verification failed !</strong></span>
+ </legend>
+ <?php echo $l->t("Sorry, but your setup appears not be be working correctly yet.<p>".
+ "Please check your setup and make sure that the configured base url '%1\$s' is indeed correct ".
+ "and that all requests to it are somehow mapped to ownClouds shorty module at '%2\$s'.",
+ array('<a id="verification-target" style="font-family:Monospace;"></a>',OCP\Util::linkToAbsolute('shorty','index.php')) );?>
+ </fieldset>
+ </div>
+</div>
+<!-- end of verification dialog -->
diff --git a/apps/shorty/templates/tmpl_http_status.php b/apps/shorty/templates/tmpl_http_status.php
new file mode 100644
index 00000000000..a43b4e3c328
--- /dev/null
+++ b/apps/shorty/templates/tmpl_http_status.php
@@ -0,0 +1,41 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+?>
+
+<?php
+/**
+ * @file templates/tmpl_http_status.php
+ * Displays a human readable version of a classical http status error
+ * @access public
+ * @author Christian Reiner
+ */
+?>
+
+<ul>
+ <li class='error'>
+ <?php echo $_['explanation']; ?>
+ </li>
+</ul>
diff --git a/apps/shorty/templates/tmpl_index.php b/apps/shorty/templates/tmpl_index.php
new file mode 100644
index 00000000000..fc4c4f91616
--- /dev/null
+++ b/apps/shorty/templates/tmpl_index.php
@@ -0,0 +1,66 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+?>
+
+<?php
+/**
+ * @file templates/tmpl_index.php
+ * The general html environment where specific templates are bedded into.
+ * @access public
+ * @author Christian Reiner
+ */
+?>
+
+<!-- central notification area -->
+<div id='notification'></div>
+
+<!-- top control bar -->
+<div id="controls" class="controls shorty-controls" data-referrer="<?php if (array_key_exists('shorty-referrer',$_)) echo $_['shorty-referrer']; ?>">
+ <!-- button to add a new entry to list -->
+ <input type="button" id="add" value="<?php echo OC_Shorty_L10n::t('New Shorty'); ?>"/>
+ <!-- display label: number of entries in list -->
+ <span>
+ <a class="shorty-prompt"><?php echo OC_Shorty_L10n::t('Number of entries') ?>:</a>
+ <a id="sum_shortys" class="shorty-value">
+ <img src="<?php echo OCP\Util::imagePath('core', 'loading.gif'); ?>" /></a>
+ </span>
+ <!-- display label: total of clicks in list -->
+ <span>
+ <a class="shorty-prompt"><?php echo OC_Shorty_L10n::t('Total of clicks') ?>:</a>
+ <a id="sum_clicks" class="shorty-value">
+ <img src="<?php echo OCP\Util::imagePath('core', 'loading.gif'); ?>" /></a>
+ </span>
+ <!-- the dialogs, hidden by default -->
+<?php require_once('tmpl_url_add.php'); ?>
+<?php require_once('tmpl_url_edit.php'); ?>
+<?php require_once('tmpl_url_show.php'); ?>
+<?php require_once('tmpl_url_share.php'); ?>
+</div>
+
+<!-- the "desktop where the action takes place -->
+<div id="desktop" class="right-content shorty-desktop">
+<?php require_once('tmpl_url_list.php'); ?>
+</div>
diff --git a/apps/shorty/templates/tmpl_preferences.php b/apps/shorty/templates/tmpl_preferences.php
new file mode 100644
index 00000000000..61294c1914c
--- /dev/null
+++ b/apps/shorty/templates/tmpl_preferences.php
@@ -0,0 +1,257 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+?>
+
+<?php
+/**
+ * @file templates/tmpl_preferences.php
+ * Dialog to change user preferences, to be included in the clouds preferences page.
+ * @access public
+ * @author Christian Reiner
+ */
+?>
+
+<form id="shorty">
+ <fieldset class="personalblock">
+ <div id="title" class="title">
+ <img class="" src="<?php echo OCP\Util::imagePath("shorty","shorty.png"); ?> ">
+ <strong>Shorty</strong>
+ </div>
+ <div id="settings">
+ <!-- shortlet -->
+ <label for="shortlet" class="aspect"><?php echo $l->t("Shortlet").":";?></label>
+ <span id="shortlet">
+ <a class="shortlet"
+ href="javascript:(function(){url=encodeURIComponent(location.href);window.open('<?php echo OCP\Util::linkToAbsolute('shorty', 'index.php'); ?>&url='+url, 'owncloud-shorty')%20})()">
+ <?php echo $l->t("Add page as 'Shorty' to ownCloud"); ?>
+ </a>
+ </span>
+ <p>
+ <span class="explain"><em><?php echo $l->t("Drag this to your browser bookmarks.");
+ echo $l->t("Click it, for whatever site you want to create a Shorty."); ?></em></span>
+ </p>
+ <p>
+ <!-- backend selection -->
+ <label for="backend-type" class="aspect"><?php echo $l->t("Backend").":";?></label>
+ <!-- list of available backend types -->
+ <span style="margin-right:1em;">
+ <select id="backend-type" name="backend-type" style="width:11em;" class="chosen"
+ placeholder="<?php echo $l->t("Choose service…"); ?>" >
+ <?php
+ foreach ( $_['backend-types'] as $value=>$display )
+ echo sprintf ( " <option value=\"%s\" %s>%s</option>\n",
+ $value,
+ ($value==$_['backend-type']?'selected':''),
+ $l->t($display) );
+ ?>
+ </select>
+ </span>
+ <!-- some additional fields: input, explanation and example -->
+ <!-- depending on the chosen backend-type above only one of the following span tags will be displayed -->
+ <span id="backend-none" class="backend-supplement" style="display:none;">
+ <br/>
+ <label for="backend-example" class="aspect"> </label>
+ <span id="backend-example">
+ <label for="example" class="aspect"><?php echo $l->t("Example").':';?></label>
+ <span id="example" class="example"><?php echo sprintf('http://%s%s<em>&lt;shorty id&gt;</em>',$_SERVER['SERVER_NAME'],OCP\Util::linkTo('shorty','',false)) ?></span>
+ </span>
+ <br/>
+ <span id="explain" class="explain">
+ <?php echo sprintf('%s<br />%s<br />%s',
+ $l->t("No backend is used, direct links pointing to your ownCloud are generated."),
+ $l->t("Such links are most likely longer than those generated when using a backend."),
+ $l->t("However this option does not rely on any third party service and keeps your shortys under your control.") ); ?>
+ </span>
+ </span>
+ <!-- backend -static- -->
+ <span id="backend-static" class="backend-supplement" style="display:none;">
+ <label for="backend-static-base" class="aspect"><?php echo $l->t("Base url").':';?></label>
+ <input id="backend-static-base" type="text" name="backend-static-base"
+ readonly disabled value="<?php echo $_['backend-static-base']; ?>"
+ placeholder="<?php echo $l->t("Specify a backend base url…"); ?>" style="width:25em;">
+ <br/>
+ <label for="backend-example" class="aspect"> </label>
+ <span id="backend-example">
+ <label for="example" class="aspect"><?php echo $l->t("Example").':';?></label>
+ <a id="example" class="example" title="<?php echo $l->t("Verification by click");?>">
+ <?php echo sprintf('http://%s/<em>&lt;service&gt;</em>/<em>&lt;shorty id&gt;</em>',$_SERVER['SERVER_NAME']) ?>
+ </a>
+ </span>
+ <br/>
+ <span id="explain" class="explain">
+ <?php echo sprintf("%s<br />\n%s<br />\n%s<br />\n%s",
+ $l->t("A static, rule-based backend is used, shorty links are generated relative to a given base url."),
+ $l->t("Since this setup depends on server based configuration rules the base url can only be specified in the 'Admin' section of the configuration."),
+ $l->t("Have a try with the example link provided, click it, it should result in a confirmation that your setup is working."),
+ $l->t("Only use this backend, if you can provide a short base url that is mapped the described way. Your shorties won't work otherwise.") ); ?>
+ </span>
+ </span>
+ <!-- backend bit.ly -->
+ <span id="backend-bitly" class="backend-supplement" style="display:none;">
+ <label for="backend-bitly-user" class="aspect"><?php echo $l->t("bit.ly user").':';?></label>
+ <input id="backend-bitly-user" type="text" name="backend-bitly-user" value="<?php echo $_['backend-bitly-user']; ?>"
+ maxlength="256" placeholder="<?php echo $l->t("bit.ly user name");?>" style="width:10em;">
+ <label for="backend-bitly-key" class="aspect"><?php echo $l->t("bit.ly key").':';?></label>
+ <input id="backend-bitly-key" type="text" name="backend-bitly-key" value="<?php echo $_['backend-bitly-key']; ?>"
+ maxlength="256" placeholder="<?php echo $l->t("bit.ly users key");?>" style="width:18em;">
+ <br/>
+ <label for="backend-example" class="aspect"> </label>
+ <span id="backend-example">
+ <label for="example" class="aspect"><?php echo $l->t("Example").':';?></label>
+ <span id="example" class="example"><?php echo sprintf('http://bitly.com/<em>&lt;shorty id&gt;</em>') ?></span>
+ </span>
+ <br/>
+ <span id="explain" class="explain">
+ <?php echo sprintf("%s<br />\n%s<br />\n%s",
+ $l->t("The external 'bitly.com' service is used to register a short url for each generated shorty."),
+ $l->t("The service requires you to authenticate yourself by providing a valid bit.ly user name and an '%s'.",
+ sprintf('<a class="external" href="http://bitly.com/a/your_api_key" target="_blank">%s</a>',$l->t("API access key")) ),
+ $l->t("This means you have to '%s' at their site first.",
+ sprintf('<a class="external" href="http://bitly.com/a/sign_up" target="_blank">%s</a>',$l->t("register an account")) ) ); ?>
+ </span>
+ </span>
+ <!-- backend cligs -->
+ <span id="backend-cligs" class="backend-supplement" style="display:none;">
+ <br/>
+ <label for="backend-example" class="aspect"> </label>
+ <span id="backend-example">
+ <label for="example" class="aspect"><?php echo $l->t("Example").':';?></label>
+ <span id="example" class="example"><?php echo sprintf('http://cli.gs/<em>&lt;shorty id&gt;</em>') ?></span>
+ </span>
+ <br/>
+ <span id="explain" class="explain">
+ <?php echo $l->t("The external 'cli.gs' service is used to register a short url for each generated shorty.");?>
+ </span>
+ </span>
+ <!-- backend is.gd -->
+ <span id="backend-isgd" class="backend-supplement" style="display:none;">
+ <br/>
+ <label for="backend-example" class="aspect"> </label>
+ <span id="backend-example">
+ <label for="example" class="aspect"><?php echo $l->t("Example").':';?></label>
+ <span id="example" class="example"><?php echo sprintf('http://is.gd/<em>&lt;shorty id&gt;</em>') ?></span>
+ </span>
+ <br/>
+ <span id="explain" class="explain">
+ <?php echo $l->t("The external 'is.gd' service is used to register a short url for each generated shorty.");?>
+ </span>
+ </span>
+ <!-- backend google -->
+ <span id="backend-google" class="backend-supplement" style="display:none;">
+ <label for="backend-google-key" class="aspect"><?php echo $l->t("API key").':';?></label>
+ <input id="backend-google-key" type="text" name="backend-google-key" value="<?php echo $_['backend-google-key']; ?>"
+ maxlength="256" placeholder="<?php echo $l->t("Google API key");?>" style="width:24em;">
+ <br/>
+ <label for="backend-example" class="aspect"> </label>
+ <span id="backend-example">
+ <label for="example" class="aspect"><?php echo $l->t("Example").':';?></label>
+ <span id="example" class="example"><?php echo sprintf('http://goo.gl/<em>&lt;shorty id&gt;</em>') ?></span>
+ </span>
+ <br/>
+ <span id="explain" class="explain">
+ <?php echo sprintf("%s<br />\n%s<br />\n%s<br />\n%s",
+ $l->t("Googles external 'goo.gl service' is used to register a short url for each generated shorty."),
+ $l->t("You must provide a valid '%s' to use this service.",
+ sprintf('<a class="external" href="https://code.google.com/apis/console/" target="_blank">%s</a>',$l->t("Google API key")) ),
+ $l->t("This means you require a 'Google API console account'."),
+ $l->t("Register a new '%s' at their pages.", array(
+ sprintf('<a class="external" href="https://code.google.com/apis/console/" target="_blank">%s</a>',$l->t("Google API account")) ) ) );?>
+ </span>
+ </span>
+ <!-- backend tinycc -->
+ <span id="backend-tinycc" class="backend-supplement" style="display:none;">
+ <label for="backend-tinycc-user" class="aspect"><?php echo $l->t("tiny.cc user").':'; ?></label>
+ <input id="backend-tinycc-user" type="text" name="backend-tinycc-user" value="<?php echo $_['backend-tinycc-user']; ?>"
+ maxlength="256" placeholder="<?php echo $l->t("tiny.cc user name"); ?>" style="width:10em;">
+ <label for="backend-tinycc-key" class="aspect"><?php echo $l->t("tiny.cc key").':'; ?></label>
+ <input id="backend-tinycc-key" type="text" name="backend-tinycc-key" value="<?php echo $_['backend-tinycc-key']; ?>"
+ maxlength="256" placeholder="<?php echo $l->t("tiny.cc user key"); ?>" style="width:19em;">
+ <br/>
+ <label for="backend-example" class="aspect"> </label>
+ <span id="backend-example">
+ <label for="example" class="aspect"><?php echo $l->t("Example").':';?></label>
+ <span id="example" class="example"><?php echo sprintf('http://tiny.cc/<em>&lt;shorty id&gt;</em>') ?></span>
+ </span>
+ <br/>
+ <span id="explain" class="explain">
+ <?php echo sprintf ( "%s<br />\n%s<br />\n%s",
+ $l->t("The external 'tiny.cc' service is used to register a short url for each generated shorty."),
+ $l->t("The service requires you to authenticate yourself by providing a valid tiny.cc user name and an api access key."),
+ $l->t("This means you have to register an '%s' at their site first.", array (
+ sprintf('<a class="external" href="http://tiny.ccc/" target="_blank">%s</a>', $l->t("account") ) ) ) ); ?>
+ </span>
+ </span>
+ <!-- backend tinyURL -->
+ <span id="backend-tinyurl" class="backend-supplement" style="display:none;">
+ <br/>
+ <label for="backend-example" class="aspect"> </label>
+ <span id="backend-example">
+ <label for="example" class="aspect"><?php echo $l->t("Example").':'; ?></label>
+ <span id="example" class="example"><?php echo sprintf('http://ti.ny/<em>&lt;shorty id&gt;</em>') ?></span>
+ </span>
+ <br/>
+ <span id="explain" class="explain">
+ <?php echo $l->t("The external 'ti.ny' service is used to register a short url for each generated shorty.");?>
+ </span>
+ </span>
+ <!-- backend turl -->
+ <span id="backend-turl" class="backend-supplement" style="display:none;">
+ <br/>
+ <label for="backend-example" class="aspect"> </label>
+ <span id="backend-example">
+ <label for="example" class="aspect"><?php echo $l->t("Example").':'; ?></label>
+ <span id="example" class="example"><?php echo sprintf('http://turl.ca/<em>&lt;shorty id&gt;</em>') ?></span>
+ </span>
+ <br/>
+ <span id="explain" class="explain">
+ <?php echo $l->t("The external 'turl' service is used to register a short url for each generated shorty."); ?>
+ </span>
+ </span>
+ </p>
+ <p>
+ <!-- sms -->
+ <label for="sms" class="aspect"><?php echo $l->t("SMS").":";?></label>
+ <span id="sms" style="margin-right:1em;">
+ <select id="sms-control" name="sms-control" style="width:11em;" class="chosen">
+ <?php echo sprintf("<option value=\"disabled\" %s>%s</option>\n",
+ ('enabled'!=$_['sms-control']?'selected':''),
+ $l->t('disabled') ); ?>
+ <?php echo sprintf("<option value=\"enabled\" %s>%s</option>\n",
+ ('enabled'==$_['sms-control']?'selected':''),
+ $l->t('enabled')); ?>
+ </select>
+ <em><?php echo $l->t("Enabling the SMS option will offer sending a Shorty via SMS."); ?></em>
+ </span>
+ <p>
+ <span class="explain"><em><?php echo $l->t("Unfortunately support for 'SMS url handling' is usually only found on mobile devices like smart phones.")."<br>\n";
+ echo $l->t("In addition, the implementations found in Android or iOS are minimalistic, buggy and differ from system to system.")."<br>\n";
+ echo $l->t("In short: this might not work for you, therefore you can disable it…")."<br>\n";?></em></span>
+ </p>
+ </div>
+ <!-- a (usually hidden) dialog used for verification of the correct setup of the 'static' backend -->
+ <?php require_once('tmpl_dlg_verify.php'); ?>
+ </fieldset>
+</form>
diff --git a/apps/shorty/templates/tmpl_settings.php b/apps/shorty/templates/tmpl_settings.php
new file mode 100644
index 00000000000..bcbab5ae014
--- /dev/null
+++ b/apps/shorty/templates/tmpl_settings.php
@@ -0,0 +1,70 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+?>
+
+<?php
+/**
+ * @file templates/tmpl_settings.php
+ * Dialog to change plugin settings, to be included in the clouds settings page.
+ * @access public
+ * @author Christian Reiner
+ */
+?>
+
+<!-- settings of app 'shorty' -->
+<form id="shorty">
+ <fieldset class="personalblock">
+ <legend>
+ <span id="title" class="title">
+ <img class="" src="<?php echo OCP\Util::imagePath("shorty","shorty.png"); ?> ">
+ <strong>Shorty</strong>
+ </span>
+ </legend>
+ <div id="backend-static" class="backend-supplement">
+ <label for="backend-static-base" class="aspect"><?php echo $l->t("Base url").':';?></label>
+ <input id="backend-static-base" type="text" name="backend-static-base"
+ value="<?php echo $_['backend-static-base']; ?>"
+ maxlength="256" placeholder="<?php echo $l->t('Specify a static base url…');?>" style="width:25em;">
+ <br/>
+ <label for="backend-example" class="aspect"> </label>
+ <span id="backend-example">
+ <label for="example" class="aspect"><?php echo $l->t("Example").':';?></label>
+ <a id="example" class="example" title="<?php echo $l->t("Verification by click");?>">
+ <?php echo sprintf('http://%s/<em>&lt;service&gt;</em>/<em>&lt;shorty id&gt;</em>',$_SERVER['SERVER_NAME']) ?>
+ </a>
+ </span>
+ <br/>
+ <span id="explain" class="explain"><?php echo sprintf("%s<br />\n%s<br />\n%s<br />\n%s",
+ $l->t("Static, rule-based backend, generates shorty links relative to a given base url."),
+ $l->t("You have to take care that any request to the url configured here is internally mapped to the 'shorty' module."),
+ $l->t("Have a try with the example link provided, click it, it should result in a confirmation that your setup is working."),
+ $l->t("Leave empty if you can't provide a short base url that is mapped the described way.") ); ?>
+ </span>
+ </div>
+
+ <?php require_once('tmpl_dlg_verify.php'); ?>
+ </fieldset>
+</form>
diff --git a/apps/shorty/templates/tmpl_url_add.php b/apps/shorty/templates/tmpl_url_add.php
new file mode 100644
index 00000000000..abf51bdab73
--- /dev/null
+++ b/apps/shorty/templates/tmpl_url_add.php
@@ -0,0 +1,86 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+?>
+
+<?php
+/**
+ * @file templates/tmpl_url_add.php
+ * A dialog to add a remote target url as a new shorty.
+ * @access public
+ * @author Christian Reiner
+ */
+?>
+
+<!-- (hidden) dialog to add a new shorty -->
+<form id="dialog-add" class="shorty-dialog shorty-standalone">
+ <fieldset>
+ <legend class="">
+ <a id="close" class="shorty-close-button"
+ title="<?php echo OC_Shorty_L10n::t('Close'); ?>">
+ <img alt="<?php echo OC_Shorty_L10n::t('Close'); ?>"
+ src="<?php echo OCP\Util::imagePath('shorty','actions/shade.png'); ?>">
+ </a>
+ <?php echo OC_Shorty_L10n::t('Add a new shorty').':'; ?>
+ </legend>
+ <label for="target"><?php echo OC_Shorty_L10n::t('Target url').':'; ?></label>
+ <input id="target" name="target" type="text" maxlength="4096" data="" class=""/>
+ <br />
+ <label for="meta">&nbsp;<img id="busy" height="12px" src="<?php echo OCP\Util::imagePath('shorty', 'loading-led.gif'); ?>"></label>
+ <span id="meta" class="shorty-meta">
+ <img id="staticon" class="shorty-icon" src="" width="16" data="<?php echo OCP\Util::imagePath('shorty', 'status/neutral.png'); ?>">
+ <img id="schemicon" class="shorty-icon" src="" width="16" data="<?php echo OCP\Util::imagePath('shorty', 'blank.png'); ?>">
+ <img id="favicon" class="shorty-icon" src="" width="16" data="<?php echo OCP\Util::imagePath('shorty', 'blank.png'); ?>">
+ <img id="mimicon" class="shorty-icon" src="" width="16" data="<?php echo OCP\Util::imagePath('shorty', 'blank.png'); ?>">
+ <a id="explanation" maxlength="80" data="" class="shorty-value"></a>
+ </span>
+ <br />
+ <label for="title"><?php echo OC_Shorty_L10n::t('Shorty title').':'; ?></label>
+ <input id="title" name="title" type="text" maxlength="80" data="" class="" placeholder=""/>
+ <br />
+ <label for="status"><?php echo OC_Shorty_L10n::t('Status').':'; ?></label>
+ <select id="status" name="status" data="shared" value="shared" class="">
+ <?php
+ foreach ( OC_Shorty_Type::$STATUS as $status )
+ if ( 'deleted'!=$status )
+ echo sprintf ( "<option value=\"%s\">%s</option>\n", $status, OC_Shorty_L10n::t($status) );
+ ?>
+ </select>
+ <span style="display:inline-block;">
+ <label for="until"><?php echo OC_Shorty_L10n::t('Expiration').':'; ?></label>
+ <input id="until" name="until" type="text" maxlength="10" value=""
+ data="" class="" style="width:8em;"
+ placeholder="-<?php echo OC_Shorty_L10n::t('never'); ?>-"
+ icon="<?php echo OCP\Util::imagePath('shorty', 'calendar.png'); ?>"/>
+ </span>
+ <br />
+ <label for="notes"><?php echo OC_Shorty_L10n::t('Notes').':'; ?></label>
+ <textarea id="notes" name="notes" maxlength="4096" data="" class=""
+ placeholder="<?php echo OC_Shorty_L10n::t('Anything that appears helpful …'); ?>"></textarea>
+ <br />
+ <label for="confirm"></label>
+ <button id="confirm" class="shorty-button-submit"><?php echo OC_Shorty_L10n::t('Add as new'); ?></button>
+ </fieldset>
+</form>
diff --git a/apps/shorty/templates/tmpl_url_edit.php b/apps/shorty/templates/tmpl_url_edit.php
new file mode 100644
index 00000000000..395b75ecab5
--- /dev/null
+++ b/apps/shorty/templates/tmpl_url_edit.php
@@ -0,0 +1,100 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+?>
+
+<?php
+/**
+ * @file templates/tmpl_url_edit.php
+ * A dialog to modify some aspects of a selected shorty.
+ * @access public
+ * @author Christian Reiner
+ */
+?>
+
+<!-- (hidden) dialog to modify a stored shorty -->
+<form id="dialog-edit" class="shorty-dialog shorty-standalone">
+ <fieldset>
+ <legend class="">
+ <a id="close" class="shorty-close-button"
+ title="<?php echo OC_Shorty_L10n::t('Close'); ?>">
+ <img alt="<?php echo OC_Shorty_L10n::t('Close'); ?>"
+ src="<?php echo OCP\Util::imagePath('shorty','actions/shade.png'); ?>">
+ </a>
+ <?php echo OC_Shorty_L10n::t('Modify shorty').':'; ?>
+ </legend>
+ <input id="id" name="id" type="hidden" readonly data="" class="" readonly disabled />
+ <label for="source"><?php echo OC_Shorty_L10n::t('Source url').':'; ?></label>
+ <input id="source" name="source" type="text" data="" class="" readonly disabled />
+ <br />
+ <label for="target"><?php echo OC_Shorty_L10n::t('Target url').':'; ?></label>
+ <input id="target" name="target" type="text" data="" class="" readonly />
+ <br />
+ <label for="meta">&nbsp;</label>
+ <span id="meta">
+ <img id="staticon" class="shorty-icon" src="" data="<?php echo OCP\Util::imagePath('shorty', 'status/neutral.png'); ?>">
+ <img id="schemicon" class="shorty-icon" src="" data="<?php echo OCP\Util::imagePath('shorty', 'blank.png'); ?>">
+ <img id="favicon" class="shorty-icon" src="" data="<?php echo OCP\Util::imagePath('shorty', 'blank.png'); ?>">
+ <img id="mimicon" class="shorty-icon" src="" data="<?php echo OCP\Util::imagePath('shorty', 'blank.png'); ?>">
+ <a id="explanation" maxlength="80" data="" class="shorty-value"></a>
+ </span>
+ <br />
+ <label for="title"><?php echo OC_Shorty_L10n::t('Shorty title').':'; ?></label>
+ <input id="title" name="title" type="text" maxlength="80" data="" class="" />
+ <br />
+ <label for="status"><?php echo OC_Shorty_L10n::t('Status').':'; ?></label>
+ <select id="status" name="status" data="shared" value="shared" style="width:8em;" class="">
+ <?php
+ foreach ( OC_Shorty_Type::$STATUS as $status )
+ if ( 'deleted'!=$status )
+ echo sprintf ( "<option value=\"%s\">%s</option>\n", $status, OC_Shorty_L10n::t($status) );
+ ?>
+ </select>
+ <span style="display:inline;">
+ <label for="until"><?php echo OC_Shorty_L10n::t('Expiration').':'; ?></label>
+ <input id="until" name="until" type="text" value=""
+ maxlength="10" data="" class="" style="width:8em;"
+ placeholder="-<?php echo OC_Shorty_L10n::t('never'); ?>-"
+ icon="<?php echo OCP\Util::imagePath('shorty', 'calendar.png'); ?>" />
+ </span>
+ <br />
+ <label for="notes"><?php echo OC_Shorty_L10n::t('Notes').':'; ?></label>
+ <textarea id="notes" name="notes" maxlength="4096" data="" class=""
+ placeholder="<?php echo OC_Shorty_L10n::t('Anything that appears helpful …'); ?>">
+ </textarea>
+ <br />
+ <span class="label-line">
+ <label for="clicks"><?php echo OC_Shorty_L10n::t('Clicks').':'; ?></label>
+ <input id="clicks" name="clicks" data="" type="textarea" class="" style="width:1em;" readonly disabled />
+ <label for="created"><?php echo OC_Shorty_L10n::t('Creation').':'; ?></label>
+ <input id="created" name="created" type="text" data="" class="" style="width:7em;" readonly disabled />
+ <label for="accessed"><?php echo OC_Shorty_L10n::t('Access').':'; ?></label>
+ <input id="accessed" name="accessed" type="text" data="" class="" style="width:10em;" readonly disabled />
+ </span>
+ <br />
+ <label for="confirm"></label>
+ <button id="confirm" class="shorty-button-submit"><?php echo OC_Shorty_L10n::t('Save'); ?></button>
+ </fieldset>
+</form>
diff --git a/apps/shorty/templates/tmpl_url_list.php b/apps/shorty/templates/tmpl_url_list.php
new file mode 100644
index 00000000000..7a93eab9d8d
--- /dev/null
+++ b/apps/shorty/templates/tmpl_url_list.php
@@ -0,0 +1,167 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+?>
+
+<?php
+/**
+ * @file templates/tmpl_url_list.php
+ * A table to visualize the list of existing shortys.
+ * @access public
+ * @author Christian Reiner
+ */
+?>
+
+<div id="hourglass" class="shorty-hourglass" style="left:10em;top:10em;"><img src="<?php echo OCP\Util::imagePath('shorty', 'loading-disk.gif'); ?>"></div>
+<div id="vacuum" class="shorty-vacuum"><span class="shorty-label"><?php echo OC_Shorty_L10n::t('List currently empty.') ?></span></div>
+
+<!-- the list of urls -->
+<table id="list" class="shorty-list" style="display:none;">
+ <thead>
+ <tr id="titlebar">
+ <!-- a button to open/close the toolbar below -->
+ <th id="favicon"><span><img id="tools" alt="toolbar" title="toggle toolbar"
+ src="<?php echo OCP\Util::imagePath('shorty','actions/plus.png'); ?>"
+ data-plus="<?php echo OCP\Util::imagePath('shorty','actions/plus.png'); ?>"
+ data-minus="<?php echo OCP\Util::imagePath('shorty','actions/minus.png'); ?>"></span></th>
+ <th id="title" ><span><?php echo OC_Shorty_L10n::t('Title') ?></span></th>
+ <th id="target" ><span><?php echo OC_Shorty_L10n::t('Target') ?></span></th>
+ <th id="clicks" ><span><?php echo OC_Shorty_L10n::t('Clicks') ?></span></th>
+ <th id="until" ><span><?php echo OC_Shorty_L10n::t('Expiration') ?></span></th>
+ <th id="status" ><span><?php echo OC_Shorty_L10n::t('Status') ?></span></th>
+ <th id="action" ><span>&nbsp;</span></th>
+ </tr>
+ <!-- toolbar opened/closed by the button above -->
+ <tr id="toolbar">
+ <th id="favicon">
+ <div style="display:none;">
+ <a id="reload"><img alt="<?php echo $l->t('reload'); ?>" title="<?php echo $l->t('Reload list'); ?>" src="<?php echo OCP\Util::imagePath('shorty','actions/reload.png'); ?>"></a>
+ </div>
+ </th>
+ <th id="title">
+ <div style="display:none;">
+ <img id="sort-up" class="shorty-sorter" data-sort-code="ta" data-sort-type="string" data-sort-direction='asc'
+ alt="<?php echo $l->t('up'); ?>" title="<?php echo $l->t('Sort ascending'); ?>"
+ src="<?php echo OCP\Util::imagePath('shorty','actions/up.png'); ?>">
+ <img id="sort-down" class="shorty-sorter" data-sort-code="td" data-sort-type="string" data-sort-direction='desc'
+ alt="<?php echo $l->t('down'); ?>" title="<?php echo $l->t('Sort descending'); ?>"
+ src="<?php echo OCP\Util::imagePath('shorty','actions/down.png'); ?>">
+ <input id='filter' type="text" value="">
+ </div>
+ </th>
+ <th id="target">
+ <div style="display:none;">
+ <img id="sort-up" class="shorty-sorter" data-sort-code="ua" data-sort-type="string" data-sort-direction='asc'
+ alt="<?php echo $l->t('up'); ?>" title="<?php echo $l->t('Sort ascending'); ?>"
+ src="<?php echo OCP\Util::imagePath('shorty','actions/up.png'); ?>">
+ <img id="sort-down" class="shorty-sorter" data-sort-code="ud" data-sort-type="string" data-sort-direction='desc'
+ alt="<?php echo $l->t('down'); ?>" title="<?php echo $l->t('Sort descending'); ?>"
+ src="<?php echo OCP\Util::imagePath('shorty','actions/down.png'); ?>">
+ <input id='filter' type="text" value="">
+ </div>
+ </th>
+ <th id="clicks">
+ <div style="display:none;">
+ <img id="sort-up" class="shorty-sorter" data-sort-code="ha" data-sort-type="int" data-sort-direction='asc'
+ alt="<?php echo $l->t('up'); ?>" title="<?php echo $l->t('Sort ascending'); ?>"
+ src="<?php echo OCP\Util::imagePath('shorty','actions/up.png'); ?>">
+ <img id="sort-down" class="shorty-sorter" data-sort-code="hd" data-sort-type="int" data-sort-direction='desc'
+ alt="<?php echo $l->t('down'); ?>" title="<?php echo $l->t('Sort descending'); ?>"
+ src="<?php echo OCP\Util::imagePath('shorty','actions/down.png'); ?>">
+ </div>
+ </th>
+ <th id="until">
+ <div style="display:none;">
+ <img id="sort-up" class="shorty-sorter" data-sort-code="da" data-sort-type="date" data-sort-direction='asc'
+ alt="<?php echo $l->t('up'); ?>" title="<?php echo $l->t('Sort ascending'); ?>"
+ src="<?php echo OCP\Util::imagePath('shorty','actions/up.png'); ?>">
+ <img id="sort-down" class="shorty-sorter" data-sort-code="dd" data-sort-type="date" data-sort-direction='desc'
+ alt="<?php echo $l->t('down'); ?>" title="<?php echo $l->t('Sort descending'); ?>"
+ src="<?php echo OCP\Util::imagePath('shorty','actions/down.png'); ?>">
+ </div>
+ </th>
+ <!-- status filter, colspan 2 to prevent width enhancement of column -->
+ <th id="status" colspan=2>
+ <div style="display:none;">
+ <span id="horst" class="shorty-select">
+ <select id='filter' value="" data-placeholder=" ">
+ <?php foreach($_['shorty-status'] as $status=>$label)
+ echo sprintf("<option value=\"%s\">%s</option>\n",($status?$label:''),$label);
+ ?>
+ </select>
+ </span>
+ </div>
+ </th>
+ </tr>
+ <!-- the 'dummy' row, a blueprint -->
+ <tr id=""
+ data-id=""
+ data-status=""
+ data-source=""
+ data-relay=""
+ data-title=""
+ data-favicon=""
+ data-target=""
+ data-clicks=""
+ data-until=""
+ data-created=""
+ data-accessed=""
+ data-notes="">
+ <td id="favicon"></td>
+ <td id="title" ></td>
+ <td id="target" ></td>
+ <td id="clicks" ></td>
+ <td id="until" ></td>
+ <td id="status" ></td>
+ <td id="actions">
+ <span class="shorty-actions">
+ <a id="show" title="<?php echo $l->t('show'); ?>" class="">
+ <img class="shorty-icon" alt="<?php echo $l->t('show'); ?>" title="<?php echo $l->t('Show details'); ?>"
+ src="<?php echo OCP\Util::imagePath('shorty','actions/info.png'); ?>" />
+ </a>
+ <a id="edit" title="<?php echo $l->t('edit'); ?>" class="">
+ <img class="shorty-icon" alt="<?php echo $l->t('modify'); ?>" title="<?php echo $l->t('Modify shorty'); ?>"
+ src="<?php echo OCP\Util::imagePath('core','actions/rename.png'); ?>" />
+ </a>
+ <a id="del" title="<?php echo $l->t('delete'); ?>" class="">
+ <img class="shorty-icon" alt="<?php echo $l->t('delete'); ?>" title="<?php echo $l->t('Delete shorty'); ?>"
+ src="<?php echo OCP\Util::imagePath('core','actions/delete.png'); ?>" />
+ </a>
+ <a id="share" title="<?php echo $l->t('share'); ?>" class="">
+ <img class="shorty-icon" alt="<?php echo $l->t('share'); ?>" title="<?php echo $l->t('Test and use links'); ?>"
+ src="<?php echo OCP\Util::imagePath('core','actions/share.png'); ?>" />
+ </a>
+ <a id="open" title="<?php echo $l->t('open'); ?>" class="">
+ <img class="shorty-icon" alt="<?php echo $l->t('open'); ?>" title="<?php echo $l->t('Open target'); ?>"
+ src="<?php echo OCP\Util::imagePath('shorty','actions/open.png'); ?>" />
+ </a>
+ </span>
+ </td>
+ </tr>
+ </thead>
+ <!-- the standard body for non-empty lists -->
+ <tbody>
+ </tbody>
+</table>
diff --git a/apps/shorty/templates/tmpl_url_share.php b/apps/shorty/templates/tmpl_url_share.php
new file mode 100644
index 00000000000..b42396d6a05
--- /dev/null
+++ b/apps/shorty/templates/tmpl_url_share.php
@@ -0,0 +1,89 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+?>
+
+<?php
+/**
+ * @file templates/tmpl_url_share.php
+ * A dialog offering control over an entries state and offers the source url
+ * @access public
+ * @author Christian Reiner
+ */
+?>
+
+<!-- (hidden) dialog to share a shorty from the list -->
+<form id="dialog-share" class="shorty-dialog shorty-embedded">
+ <fieldset>
+ <legend class="">
+ <a id="close" class="shorty-close-button"
+ title="<?php echo OC_Shorty_L10n::t('Close'); ?>">
+ <img alt="<?php echo OC_Shorty_L10n::t('Close'); ?>"
+ src="<?php echo OCP\Util::imagePath('apps/shorty','actions/shade.png'); ?>">
+ </a>
+ <?php echo OC_Shorty_L10n::t('Test and use').':'; ?>
+ </legend>
+ <input id="id" name="id" type="hidden" readonly data="" class="" readonly disabled />
+ <label for="status"><?php echo OC_Shorty_L10n::t('Status').':'; ?></label>
+ <select id="status" name="status" data="" class="" value="">
+ <?php
+ foreach ( OC_Shorty_Type::$STATUS as $status )
+ if ( 'deleted'!=$status )
+ echo sprintf ( "<option value=\"%s\">%s</option>\n", $status, OC_Shorty_L10n::t($status) );
+ ?>
+ </select>
+ <br />
+ <label for="source"><?php echo OC_Shorty_L10n::t('Source url').':'; ?></label>
+ <a id="source" class="shorty-clickable" target="_blank"
+ title="<?php echo OC_Shorty_L10n::t('Open source url'); ?>"
+ href=""></a>
+ <br />
+ <label for="relay"><?php echo OC_Shorty_L10n::t('Relay url').':'; ?></label>
+ <a id="relay" class="shorty-clickable" target="_blank"
+ title="<?php echo OC_Shorty_L10n::t('Open relay url'); ?>"
+ href=""></a>
+ <br />
+ <label for="target"><?php echo OC_Shorty_L10n::t('Target url').':'; ?></label>
+ <a id="target" class="shorty-clickable" target="_blank"
+ title="<?php echo OC_Shorty_L10n::t('Open target url'); ?>"
+ href=""></a>
+ <br />
+ <img id="usage-email" name="usage-email" class="shorty-usage" alt="email"
+ src="<?php echo OCP\Util::imagePath('apps/shorty','usage/64/email.png'); ?>"
+ title="<?php echo OC_Shorty_L10n::t("Send by email"); ?>" />
+ <img id="usage-sms" type="image" name="usage-sms" alt="sms"
+ class="shorty-usage <?php echo $_['sms-control']; ?>"
+ src="<?php echo OCP\Util::imagePath('apps/shorty','usage/64/sms.png'); ?>"
+ title="<?php echo OC_Shorty_L10n::t("Send by SMS"); ?>" />
+ <img id="usage-qrcode" type="image" name="usage-qrcode" class="shorty-usage" alt="qrcode"
+ src="<?php echo OCP\Util::imagePath('apps/shorty','usage/64/qrcode.png'); ?>"
+ title="<?php echo OC_Shorty_L10n::t("Show as QRCode"); ?>" />
+ <img id="usage-clipboard" type="image" name="usage-clipboard" class="shorty-usage" alt="clipbaord"
+ src="<?php echo OCP\Util::imagePath('apps/shorty','usage/64/clipboard.png'); ?>"
+ title="<?php echo OC_Shorty_L10n::t("Copy to clipboard"); ?>" />
+ </fieldset>
+</form>
+
+<?php require_once('tmpl_dlg_qrcode.php'); ?>
diff --git a/apps/shorty/templates/tmpl_url_show.php b/apps/shorty/templates/tmpl_url_show.php
new file mode 100644
index 00000000000..462d8e8b9cb
--- /dev/null
+++ b/apps/shorty/templates/tmpl_url_show.php
@@ -0,0 +1,85 @@
+<?php
+/**
+* @package shorty an ownCloud url shortener plugin
+* @category internet
+* @author Christian Reiner
+* @copyright 2011-2012 Christian Reiner <foss@christian-reiner.info>
+* @license GNU Affero General Public license (AGPL)
+* @link information
+* @link repository https://svn.christian-reiner.info/svn/app/oc/shorty
+*
+* 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/>.
+*
+*/
+?>
+
+<?php
+/**
+ * @file templates/tmpl_url_show.php
+ * A read-only dialog visualizing all aspects of a selected shorty.
+ * @access public
+ * @author Christian Reiner
+ */
+?>
+
+<!-- (hidden) dialog to show a shorty from the list -->
+<form id="dialog-show" class="shorty-dialog shorty-standalone">
+ <fieldset>
+ <legend class="">
+ <a id="close" class="shorty-close-button"
+ title="<?php echo OC_Shorty_L10n::t('Close'); ?>">
+ <img alt="<?php echo OC_Shorty_L10n::t('Close'); ?>"
+ src="<?php echo OCP\Util::imagePath('shorty','actions/shade.png'); ?>">
+ </a>
+ <?php echo OC_Shorty_L10n::t('Show details').':'; ?>
+ </legend>
+ <label for="source"><?php echo OC_Shorty_L10n::t('Source url').':'; ?></label>
+ <input id="source" name="source" type="text" data="" class="" readonly disabled />
+ <br />
+ <label for="target"><?php echo OC_Shorty_L10n::t('Target url').':'; ?></label>
+ <input id="target" name="target" data="" class="" readonly disabled />
+ <br />
+ <label for="meta">&nbsp;</label>
+ <span id="meta">
+ <img id="staticon" class="shorty-icon" src="" data="<?php echo OCP\Util::imagePath('shorty', 'status/neutral.png'); ?>">
+ <img id="schemicon" class="shorty-icon" src="" data="<?php echo OCP\Util::imagePath('shorty', 'blank.png'); ?>">
+ <img id="favicon" class="shorty-icon" src="" data="<?php echo OCP\Util::imagePath('shorty', 'blank.png'); ?>">
+ <img id="mimicon" class="shorty-icon" src="" data="<?php echo OCP\Util::imagePath('shorty', 'blank.png'); ?>">
+ <a id="explanation" maxlength="80" data="" class="shorty-value"></a>
+ </span>
+ <br />
+ <label for="title"><?php echo OC_Shorty_L10n::t('Shorty title').':'; ?></label>
+ <input id="title" name="title" type="text" data="" class="" readonly disabled />
+ <br />
+ <label for="status"><?php echo OC_Shorty_L10n::t('Status').':'; ?></label>
+ <input id="status" name="status" type="text" data="" class="" style="width:8em;" readonly disabled />
+ <span class="label-line">
+ <label for="until"><?php echo OC_Shorty_L10n::t('Expiration').':'; ?></label>
+ <input id="until" name="until" type="text" data="" class="" style="width:12em;" readonly disabled />
+ </span>
+ <br />
+ <label for="notes"><?php echo OC_Shorty_L10n::t('Notes').':'; ?></label>
+ <input id="notes" name="notes" data="" class="" readonly disabled />
+ <br />
+ <span class="label-line">
+ <label for="clicks"><?php echo OC_Shorty_L10n::t('Clicks').':'; ?></label>
+ <input id="clicks" name="clicks" data="" type="textarea" class="" style="width:1em;" readonly disabled />
+ <label for="created"><?php echo OC_Shorty_L10n::t('Creation').':'; ?></label>
+ <input id="created" name="created" type="text" data="" class="" style="width:7em;" readonly disabled />
+ <label for="accessed"><?php echo OC_Shorty_L10n::t('Access').':'; ?></label>
+ <input id="accessed" name="accessed" type="text" data="" class="" style="width:10em;" readonly disabled />
+ </span>
+ </fieldset>
+</form>
diff --git a/apps/storage_charts/ajax/config.php b/apps/storage_charts/ajax/config.php
new file mode 100644
index 00000000000..22fa0384290
--- /dev/null
+++ b/apps/storage_charts/ajax/config.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('storage_charts');
+
+if(in_array($_POST['k'], Array('hu_size','sc_sort','hu_size_hus'))){
+ switch($_POST['o']){
+ case 'set':
+ $i = NULL;
+ if(is_array($_POST['i'])){
+ $i = serialize($_POST['i']);
+
+ }elseif(is_numeric($_POST['i'])){
+ $i = $_POST['i'];
+ }
+ OC_DLStCharts::setUConfValue($_POST['k'], $i);
+ break;
+ case 'get':
+ $v = OC_DLStCharts::getUConfValue($_POST['k']);
+ OCP\JSON::encodedPrint(Array('r' => $v['uc_val']));
+ break;
+ }
+}
diff --git a/apps/storage_charts/ajax/data.php b/apps/storage_charts/ajax/data.php
new file mode 100644
index 00000000000..a9a36d3c642
--- /dev/null
+++ b/apps/storage_charts/ajax/data.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('storage_charts');
+
+$l = new OC_L10N('storage_charts');
+
+// Update and save the new configuration
+if(is_numeric($_POST['s']) && in_array($_POST['k'], Array('hu_size','hu_size_hus'))){
+ OC_DLStCharts::setUConfValue($_POST['k'], $_POST['s']);
+ if(strcmp($_POST['k'],'hu_size') == 0){
+ OCP\JSON::encodedPrint(Array('r' => OC_DLStChartsLoader::loadChart('clines_usse', $l)));
+ }else{
+ OCP\JSON::encodedPrint(Array('r' => OC_DLStChartsLoader::loadChart('chisto_us', $l)));
+ }
+}
diff --git a/apps/storage_charts/appinfo/app.php b/apps/storage_charts/appinfo/app.php
new file mode 100644
index 00000000000..3efec67ea8f
--- /dev/null
+++ b/apps/storage_charts/appinfo/app.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+OCP\App::checkAppEnabled('storage_charts');
+
+OC::$CLASSPATH['OC_DLStCharts'] = "apps/storage_charts/lib/db.class.php";
+OC::$CLASSPATH['OC_DLStChartsLoader'] = "apps/storage_charts/lib/loader.class.php";
+
+OCP\App::register(Array(
+ 'order' => 60,
+ 'id' => 'storage_charts',
+ 'name' => 'Storage Charts'
+));
+
+OCP\App::addNavigationEntry(Array(
+ 'id' => 'storage_charts',
+ 'order' => 60,
+ 'href' => OCP\Util::linkTo('storage_charts', 'charts.php'),
+ 'icon' => OCP\Util::imagePath('storage_charts', 'chart.png'),
+ 'name' => 'DL Charts'
+));
+
+OCP\App::registerPersonal('storage_charts','settings');
+
+$data_dir = OCP\Config::getSystemValue('datadirectory', '');
+if(OCP\User::getUser() && strlen($data_dir) != 0){
+ $fs = OCP\Files::getStorage('files');
+ $used = OC_DLStCharts::getTotalDataSize(OC::$CONFIG_DATADIRECTORY);
+ $total = OC_DLStCharts::getTotalDataSize($data_dir) + $fs->free_space();
+ OC_DLStCharts::update($used, $total);
+}
diff --git a/apps/storage_charts/appinfo/app_admin.php b/apps/storage_charts/appinfo/app_admin.php
new file mode 100644
index 00000000000..9181d2f54bb
--- /dev/null
+++ b/apps/storage_charts/appinfo/app_admin.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+OCP\App::checkAppEnabled('storage_charts');
+
+$l = OC_L10N::get('storage_charts');
+
+OC::$CLASSPATH['OC_DLStCharts'] = "apps/storage_charts/lib/db.class.php";
+OC::$CLASSPATH['OC_DLStChartsLoader'] = "apps/storage_charts/lib/loader.class.php";
+
+if(OC_Group::inGroup(OCP\User::getUser(), 'admin')){
+ OCP\App::register(Array(
+ 'order' => 60,
+ 'id' => 'storage_charts',
+ 'name' => 'Storage Charts'
+ ));
+
+ OCP\App::addNavigationEntry(Array(
+ 'id' => 'storage_charts',
+ 'order' => 60,
+ 'href' => OCP\Util::linkTo('storage_charts', 'charts.php'),
+ 'icon' => OCP\Util::imagePath('storage_charts', 'chart.png'),
+ 'name' => 'DL Charts'
+ ));
+
+ OCP\App::registerPersonal('storage_charts','settings');
+}elseif(OCP\User::isLoggedIn() && $_GET['app'] == 'storage_charts'){
+ die($l->t('Permission denied.'));
+}
+
+// Get storage value for logged in user
+$data_dir = OCP\Config::getSystemValue('datadirectory', '');
+if(OCP\User::getUser() && strlen($data_dir) != 0){
+ $fs = OCP\Files::getStorage('files');
+ $used = OC_DLStCharts::getTotalDataSize(OC::$CONFIG_DATADIRECTORY);
+ $total = OC_DLStCharts::getTotalDataSize($data_dir) + $fs->free_space();
+ OC_DLStCharts::update($used, $total);
+} \ No newline at end of file
diff --git a/apps/storage_charts/appinfo/database.xml b/apps/storage_charts/appinfo/database.xml
new file mode 100644
index 00000000000..cac8136d2e9
--- /dev/null
+++ b/apps/storage_charts/appinfo/database.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<database>
+ <name>*dbname*</name>
+ <create>true</create>
+ <overwrite>false</overwrite>
+ <charset>utf8</charset>
+ <table>
+ <name>*dbprefix*dlstcharts</name>
+ <declaration>
+ <field>
+ <name>stc_id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <unsigned>true</unsigned>
+ <length>4</length>
+ </field>
+ <field>
+ <name>oc_uid</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>64</length>
+ </field>
+ <field>
+ <name>stc_month</name>
+ <type>integer</type>
+ <notnull>true</notnull>
+ <length>6</length>
+ </field>
+ <field>
+ <name>stc_dayts</name>
+ <type>integer</type>
+ <notnull>true</notnull>
+ <length>11</length>
+ </field>
+ <field>
+ <name>stc_used</name>
+ <type>integer</type>
+ <notnull>true</notnull>
+ <length>30</length>
+ </field>
+ <field>
+ <name>stc_total</name>
+ <type>integer</type>
+ <notnull>true</notnull>
+ <length>30</length>
+ </field>
+ <index>
+ <name>stc_pk</name>
+ <primary>true</primary>
+ <field>
+ <name>stc_id</name>
+ <sorting>ascending</sorting>
+ </field>
+ </index>
+ </declaration>
+ </table>
+ <table>
+ <name>*dbprefix*dlstcharts_uconf</name>
+ <declaration>
+ <field>
+ <name>uc_id</name>
+ <type>integer</type>
+ <default>0</default>
+ <notnull>true</notnull>
+ <autoincrement>1</autoincrement>
+ <unsigned>true</unsigned>
+ <length>4</length>
+ </field>
+ <field>
+ <name>oc_uid</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>64</length>
+ </field>
+ <field>
+ <name>uc_key</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>64</length>
+ </field>
+ <field>
+ <name>uc_val</name>
+ <type>text</type>
+ <notnull>true</notnull>
+ <length>255</length>
+ </field>
+ <index>
+ <name>uc_pk</name>
+ <primary>true</primary>
+ <field>
+ <name>uc_id</name>
+ <sorting>ascending</sorting>
+ </field>
+ </index>
+ </declaration>
+ </table>
+</database>
diff --git a/apps/storage_charts/appinfo/info.xml b/apps/storage_charts/appinfo/info.xml
new file mode 100644
index 00000000000..435c5a40934
--- /dev/null
+++ b/apps/storage_charts/appinfo/info.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<info>
+ <id>storage_charts</id>
+ <name>Storage Charts</name>
+ <description>Display storage charts, using HighCharts non-commercial free library (www.highcharts.com)</description>
+ <version>2.2</version>
+ <licence>AGPL</licence>
+ <author>Xavier Beurois (www.djazz-lab.net)</author>
+ <require>4</require>
+ <shipped>true</shipped>
+</info>
diff --git a/apps/storage_charts/charts.php b/apps/storage_charts/charts.php
new file mode 100644
index 00000000000..774f1135540
--- /dev/null
+++ b/apps/storage_charts/charts.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+OCP\User::checkLoggedIn();
+OCP\App::checkAppEnabled('storage_charts');
+
+$tmpl = new OCP\Template('storage_charts', 'charts.tpl', 'user');
+
+// Get data for all users if admin or just for the current user
+$displays = OC_DLStCharts::getUConfValue('c_disp', Array('uc_val' => 'a:3:{s:10:"cpie_rfsus";i:1;s:11:"clines_usse";i:1;s:9:"chisto_us";i:1;}'));
+$displays = unserialize($displays['uc_val']);
+$tmpl->assign('c_disp', $displays);
+
+$sc_sort = OC_DLStCharts::getUConfValue('sc_sort', Array('uc_val' => 'a:3:{i:0;s:10:"cpie_rfsus";i:1;s:11:"clines_usse";i:2;s:9:"chisto_us";}'));
+$tmpl->assign('sc_sort', unserialize($sc_sort['uc_val']));
+
+if($displays['clines_usse']){
+ $hu_size = OC_DLStCharts::getUConfValue('hu_size', Array('uc_val' => 3));
+ $tmpl->assign('hu_size', $hu_size['uc_val']);
+}
+if($displays['chisto_us']){
+ $hu_size_hus = OC_DLStCharts::getUConfValue('hu_size_hus', Array('uc_val' => 3));
+ $tmpl->assign('hu_size_hus', $hu_size_hus['uc_val']);
+}
+
+$tmpl->printPage();
diff --git a/apps/storage_charts/css/styles.css b/apps/storage_charts/css/styles.css
new file mode 100644
index 00000000000..e5fb7a3dad0
--- /dev/null
+++ b/apps/storage_charts/css/styles.css
@@ -0,0 +1,32 @@
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+#storage-charts div.personalblock.topblock span{float:right;color:#BBB;font-style:italic;}
+
+#stc_sortable div.personalblock.bottomblock, #storage-charts div.personalblock.bottomblock{margin-bottom:1em;}
+#stc_sortable div.personalblock.titleblock, #storage-charts div.personalblock.titleblock{font-weight:bold;}
+#stc_sortable div.personalblock, #storage-charts div.personalblock{margin:1em 1em 0;padding:0.5em 1em;background: none repeat scroll 0 0 #F8F8F8;border-radius:0.5em 0.5em 0.5em 0.5em;color:#555555;text-shadow:0 1px 0 #FFFFFF;border:1px solid #DDDDDD;box-shadow:0 1px 1px #FFFFFF,0 1px 1px #FFFFFF inset;}
+
+#stc_frame{margin:1em 1em 0;}
+#stc_frame #stc_sortable div.personalblock{margin:0;margin-bottom:1em;}
+#stc_frame #stc_sortable div.personalblock h3{line-height:2.6;}
+#stc_frame #stc_sortable div.personalblock h3 img{vertical-align:middle;margin-right:10px;}
+#stc_frame #stc_sortable div.personalblock h3 span#selunits,#stc_frame #stc_sortable div.personalblock h3 span#selunits_hus{float:right;}
diff --git a/apps/storage_charts/img/chart.png b/apps/storage_charts/img/chart.png
new file mode 100644
index 00000000000..d8a99816641
--- /dev/null
+++ b/apps/storage_charts/img/chart.png
Binary files differ
diff --git a/apps/storage_charts/img/loader.gif b/apps/storage_charts/img/loader.gif
new file mode 100644
index 00000000000..190582b8226
--- /dev/null
+++ b/apps/storage_charts/img/loader.gif
Binary files differ
diff --git a/apps/storage_charts/img/move.png b/apps/storage_charts/img/move.png
new file mode 100644
index 00000000000..afb605ed9a8
--- /dev/null
+++ b/apps/storage_charts/img/move.png
Binary files differ
diff --git a/apps/storage_charts/js/highCharts-2.2.1/highcharts.min.js b/apps/storage_charts/js/highCharts-2.2.1/highcharts.min.js
new file mode 100644
index 00000000000..203d89b6ea1
--- /dev/null
+++ b/apps/storage_charts/js/highCharts-2.2.1/highcharts.min.js
@@ -0,0 +1,202 @@
+/*
+ Highcharts JS v2.2.1 (2012-03-15)
+
+ (c) 2009-2011 Torstein H?nsi
+
+ License: www.highcharts.com/license
+*/
+(function(){function L(a,b){var c;a||(a={});for(c in b)a[c]=b[c];return a}function za(){for(var a=0,b=arguments,c=b.length,d={};a<c;a++)d[b[a++]]=b[a];return d}function S(a,b){return parseInt(a,b||10)}function Ab(a){return typeof a==="string"}function mb(a){return typeof a==="object"}function Fb(a){return Object.prototype.toString.call(a)==="[object Array]"}function Bb(a){return typeof a==="number"}function nb(a){return oa.log(a)/oa.LN10}function cb(a){return oa.pow(10,a)}function Gb(a,b){for(var c=
+a.length;c--;)if(a[c]===b){a.splice(c,1);break}}function s(a){return a!==X&&a!==null}function A(a,b,c){var d,e;if(Ab(b))s(c)?a.setAttribute(b,c):a&&a.getAttribute&&(e=a.getAttribute(b));else if(s(b)&&mb(b))for(d in b)a.setAttribute(d,b[d]);return e}function Hb(a){return Fb(a)?a:[a]}function p(){var a=arguments,b,c,d=a.length;for(b=0;b<d;b++)if(c=a[b],typeof c!=="undefined"&&c!==null)return c}function P(a,b){if(Qb&&b&&b.opacity!==X)b.filter="alpha(opacity="+b.opacity*100+")";L(a.style,b)}function Aa(a,
+b,c,d,e){a=x.createElement(a);b&&L(a,b);e&&P(a,{padding:0,border:Ma,margin:0});c&&P(a,c);d&&d.appendChild(a);return a}function pa(a,b){var c=function(){};c.prototype=new a;L(c.prototype,b);return c}function dc(a,b,c,d){var e=Ea.lang,f=isNaN(b=Ba(b))?2:b,b=c===void 0?e.decimalPoint:c,d=d===void 0?e.thousandsSep:d,e=a<0?"-":"",c=String(S(a=Ba(+a||0).toFixed(f))),g=c.length>3?c.length%3:0;return e+(g?c.substr(0,g)+d:"")+c.substr(g).replace(/(\d{3})(?=\d)/g,"$1"+d)+(f?b+Ba(a-c).toFixed(f).slice(2):"")}
+function Na(a,b){return Array((b||2)+1-String(a).length).join(0)+a}function ec(a,b,c,d){var e,c=p(c,1);e=a/c;b||(b=[1,2,2.5,5,10],d&&d.allowDecimals===!1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;d<b.length;d++)if(a=b[d],e<=(b[d]+(b[d+1]||b[d]))/2)break;a*=c;return a}function Kc(a,b){var c=b||[[Rb,[1,2,5,10,20,25,50,100,200,500]],[ob,[1,2,5,10,15,30]],[pb,[1,2,5,10,15,30]],[va,[1,2,3,4,6,8,12]],[Ca,[1,2]],[Ya,[1,2]],[Da,[1,2,3,4,6]],[Za,null]],d=c[c.length-1],e=E[d[0]],f=d[1],g;for(g=0;g<c.length;g++)if(d=
+c[g],e=E[d[0]],f=d[1],c[g+1]&&a<=(e*f[f.length-1]+E[c[g+1][0]])/2)break;e===E[Za]&&a<5*e&&(f=[1,2,5]);e===E[Za]&&a<5*e&&(f=[1,2,5]);c=ec(a/e,f);return{unitRange:e,count:c,unitName:d[0]}}function Lc(a,b,c,d){var e=[],f={},g=Ea.global.useUTC,h,i=new Date(b),b=a.unitRange,k=a.count;b>=E[ob]&&(i.setMilliseconds(0),i.setSeconds(b>=E[pb]?0:k*Ta(i.getSeconds()/k)));if(b>=E[pb])i[pc](b>=E[va]?0:k*Ta(i[fc]()/k));if(b>=E[va])i[qc](b>=E[Ca]?0:k*Ta(i[gc]()/k));if(b>=E[Ca])i[hc](b>=E[Da]?1:k*Ta(i[db]()/k));b>=
+E[Da]&&(i[rc](b>=E[Za]?0:k*Ta(i[qb]()/k)),h=i[rb]());b>=E[Za]&&(h-=h%k,i[sc](h));if(b===E[Ya])i[hc](i[db]()-i[ic]()+p(d,1));d=1;h=i[rb]();for(var j=i.getTime(),l=i[qb](),i=i[db]();j<c;)e.push(j),b===E[Za]?j=sb(h+d*k,0):b===E[Da]?j=sb(h,l+d*k):!g&&(b===E[Ca]||b===E[Ya])?j=sb(h,l,i+d*k*(b===E[Ca]?1:7)):(j+=b*k,b<=E[va]&&j%E[Ca]===0&&(f[j]=Ca)),d++;e.push(j);e.info=L(a,{higherRanks:f,totalRange:b*k});return e}function tc(){this.symbol=this.color=0}function uc(a,b,c,d,e,f,g,h,i){var k=g.x,g=g.y,i=k+c+
+(i?h:-a-h),j=g-b+d+15,l;i<7&&(i=c+k+h);i+a>c+e&&(i-=i+a-(c+e),j=g-b+d-h,l=!0);j<d+5?(j=d+5,l&&g>=j&&g<=j+b&&(j=g+d+h)):j+b>d+f&&(j=d+f-b-h);return{x:i,y:j}}function Mc(a,b){var c=a.length,d,e;for(e=0;e<c;e++)a[e].ss_i=e;a.sort(function(a,c){d=b(a,c);return d===0?a.ss_i-c.ss_i:d});for(e=0;e<c;e++)delete a[e].ss_i}function Sb(a){for(var b=a.length,c=a[0];b--;)a[b]<c&&(c=a[b]);return c}function Ib(a){for(var b=a.length,c=a[0];b--;)a[b]>c&&(c=a[b]);return c}function Jb(a){for(var b in a)a[b]&&a[b].destroy&&
+a[b].destroy(),delete a[b]}function Tb(a){tb||(tb=Aa(Ra));a&&tb.appendChild(a);tb.innerHTML=""}function jc(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+a;if(b)throw c;else ca.console&&console.log(c)}function Cb(a){return parseFloat(a.toPrecision(14))}function Kb(a,b){Ub=p(a,b.animation)}function vc(){var a=Ea.global.useUTC,b=a?"getUTC":"get",c=a?"setUTC":"set";sb=a?Date.UTC:function(a,b,c,g,h,i){return(new Date(a,b,p(c,1),p(g,0),p(h,0),p(i,0))).getTime()};fc=b+"Minutes";gc=b+"Hours";
+ic=b+"Day";db=b+"Date";qb=b+"Month";rb=b+"FullYear";pc=c+"Minutes";qc=c+"Hours";hc=c+"Date";rc=c+"Month";sc=c+"FullYear"}function Sa(){}function wc(a,b){function c(a){function b(a,c){this.pos=a;this.type=c||"";this.isNew=!0;c||this.addLabel()}function c(a){if(a)this.options=a,this.id=a.id;return this}function d(a,b,c,e){this.isNegative=b;this.options=a;this.x=c;this.stack=e;this.alignOptions={align:a.align||(U?b?"left":"right":"center"),verticalAlign:a.verticalAlign||(U?"middle":b?"bottom":"top"),
+y:p(a.y,U?4:b?14:-6),x:p(a.x,U?b?-6:6:0)};this.textAlign=a.textAlign||(U?b?"right":"left":"center")}function e(){var a=[],b=[],c;C=M=null;o(B.series,function(e){if(e.visible||!q.ignoreHiddenSeries){var f=e.options,g,h,i,j,k,m,l,n,Y,ea=f.threshold,B,o=[],xc=0;if(R&&ea<=0)ea=f.threshold=null;if(t)f=e.xData,f.length&&(C=Ua(p(C,f[0]),Sb(f)),M=W(p(M,f[0]),Ib(f)));else{var y,r,G,v=e.cropped,Z=e.xAxis.getExtremes(),V=!!e.modifyValue;g=f.stacking;Ha=g==="percent";if(g)k=f.stack,j=e.type+p(k,""),m="-"+j,e.stackKey=
+j,h=a[j]||[],a[j]=h,i=b[m]||[],b[m]=i;Ha&&(C=0,M=99);f=e.processedXData;l=e.processedYData;B=l.length;for(c=0;c<B;c++)if(n=f[c],Y=l[c],Y!==null&&Y!==X&&(g?(r=(y=Y<ea)?i:h,G=y?m:j,Y=r[n]=s(r[n])?r[n]+Y:Y,qa[G]||(qa[G]={}),qa[G][n]||(qa[G][n]=new d(u.stackLabels,y,n,k)),qa[G][n].setTotal(Y)):V&&(Y=e.modifyValue(Y)),v||(f[c+1]||n)>=Z.min&&(f[c-1]||n)<=Z.max))if(n=Y.length)for(;n--;)Y[n]!==null&&(o[xc++]=Y[n]);else o[xc++]=Y;!Ha&&o.length&&(C=Ua(p(C,o[0]),Sb(o)),M=W(p(M,o[0]),Ib(o)));s(ea)&&(C>=ea?(C=
+ea,Ma=!0):M<ea&&(M=ea,Na=!0))}}})}function f(a,b,c){for(var d,b=Cb(Ta(b/a)*a),c=Cb(Yb(c/a)*a),e=[];b<=c;){e.push(b);b=Cb(b+a);if(b===d)break;d=b}return e}function g(a,b,c,d){var e=[];if(!d)B._minorAutoInterval=null;if(a>=0.5)a=z(a),e=f(a,b,c);else if(a>=0.08){var h=Ta(b),i,j,k,n,m,l;for(i=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];h<c+1&&!l;h++){k=i.length;for(j=0;j<k&&!l;j++)n=nb(cb(h)*i[j]),n>b&&e.push(m),m>c&&(l=!0),m=n}}else if(b=cb(b),c=cb(c),a=u[d?"minorTickInterval":"tickInterval"],
+a=p(a==="auto"?null:a,B._minorAutoInterval,(c-b)*(u.tickPixelInterval/(d?5:1))/((d?D/N.length:D)||1)),a=ec(a,null,oa.pow(10,Ta(oa.log(a)/oa.LN10))),e=Vb(f(a,b,c),nb),!d)B._minorAutoInterval=a/5;d||(Oa=a);return e}function h(){var a=[],b,c;if(R){c=N.length;for(b=1;b<c;b++)a=a.concat(g(Ga,N[b-1],N[b],!0))}else for(b=F+(N[0]-F)%Ga;b<=H;b+=Ga)a.push(b);return a}function i(){var a,b=M-C>=fb,c,d,e,f,g,h;t&&fb===X&&!R&&(s(u.min)||s(u.max)?fb=null:(o(B.series,function(a){f=a.xData;for(d=g=a.xIncrement?1:
+f.length-1;d>0;d--)if(e=f[d]-f[d-1],c===X||e<c)c=e}),fb=Ua(c*5,M-C)));H-F<fb&&(a=(fb-H+F)/2,a=[F-a,p(u.min,F-a)],b&&(a[2]=C),F=Ib(a),h=[F+fb,p(u.max,F+fb)],b&&(h[2]=M),H=Sb(h),H-F<fb&&(a[0]=H-fb,a[1]=p(u.min,H-fb),F=Ib(a)))}function j(a){var b,c=u.tickInterval,d=u.tickPixelInterval;da?(na=m[t?"xAxis":"yAxis"][u.linkedTo],b=na.getExtremes(),F=p(b.min,b.dataMin),H=p(b.max,b.dataMax),u.type!==na.options.type&&jc(11,1)):(F=p(ba,u.min,C),H=p(ca,u.max,M));R&&(!a&&Ua(F,C)<=0&&jc(10,1),F=nb(F),H=nb(H));ja&&
+(ba=F=W(F,H-ja),ca=H,a&&(ja=null));i();if(!Va&&!Ha&&!da&&s(F)&&s(H)){b=H-F||1;if(!s(u.min)&&!s(ba)&&Ea&&(C<0||!Ma))F-=b*Ea;if(!s(u.max)&&!s(ca)&&Ja&&(M>0||!Na))H+=b*Ja}Oa=F===H||F===void 0||H===void 0?1:da&&!c&&d===na.options.tickPixelInterval?na.tickInterval:p(c,Va?1:(H-F)*d/(D||1));t&&!a&&o(B.series,function(a){a.processData(F!==ma||H!==za)});ib();B.beforeSetTickPositions&&B.beforeSetTickPositions();B.postProcessTickInterval&&(Oa=B.postProcessTickInterval(Oa));!V&&!R&&(Wa=oa.pow(10,Ta(oa.log(Oa)/
+oa.LN10)),s(u.tickInterval)||(Oa=ec(Oa,null,Wa,u)));B.tickInterval=Oa;Ga=u.minorTickInterval==="auto"&&Oa?Oa/5:u.minorTickInterval;(N=u.tickPositions||Xa&&Xa.apply(B,[F,H]))||(N=V?(B.getNonLinearTimeTicks||Lc)(Kc(Oa,u.units),F,H,u.startOfWeek,B.ordinalPositions,B.closestPointRange,!0):R?g(Oa,F,H):f(Oa,F,H));if(!da&&(a=N[0],c=N[N.length-1],u.startOnTick?F=a:F>a&&N.shift(),u.endOnTick?H=c:H<c&&N.pop(),gb||(gb={x:0,y:0}),!V&&N.length>gb[w]&&u.alignTicks!==!1))gb[w]=N.length}function k(a){a=(new c(a)).render();
+ta.push(a);return a}function l(){var a=u.title,d=u.stackLabels,e=u.alternateGridColor,f=u.lineWidth,g,i,j=m.hasRendered&&s(ma)&&!isNaN(ma),n=(g=B.series.length&&s(F)&&s(H))||p(u.showEmpty,!0),Y,q;if(g||da)if(Ga&&!Va&&o(h(),function(a){va[a]||(va[a]=new b(a,"minor"));j&&va[a].isNew&&va[a].render(null,!0);va[a].isActive=!0;va[a].render()}),o(N.slice(1).concat([N[0]]),function(a,c){c=c===N.length-1?0:c+1;if(!da||a>=F&&a<=H)Pa[a]||(Pa[a]=new b(a)),j&&Pa[a].isNew&&Pa[a].render(c,!0),Pa[a].isActive=!0,
+Pa[a].render(c)}),e&&o(N,function(a,b){if(b%2===0&&a<H)Da[a]||(Da[a]=new c),Y=a,q=N[b+1]!==X?N[b+1]:H,Da[a].options={from:R?cb(Y):Y,to:R?cb(q):q,color:e},Da[a].render(),Da[a].isActive=!0}),!B._addedPlotLB)o((u.plotLines||[]).concat(u.plotBands||[]),function(a){k(a)}),B._addedPlotLB=!0;o([Pa,va,Da],function(a){for(var b in a)a[b].isActive?a[b].isActive=!1:(a[b].destroy(),delete a[b])});f&&(g=x+(y?A:0)+T,i=ra-Lb-(y?hb:0)+T,g=I.crispLine([wa,G?x:g,G?i:E,fa,G?sa-Zb:g,G?i:ra-Lb],f),$?$.animate({d:g}):
+$=I.path(g).attr({stroke:u.lineColor,"stroke-width":f,zIndex:7}).add(),$[n?"show":"hide"]());if(v&&n)n=G?x:E,f=S(a.style.fontSize||12),n={low:n+(G?0:D),middle:n+D/2,high:n+(G?D:0)}[a.align],f=(G?E+hb:x)+(G?1:-1)*(y?-1:1)*Ya+(r===2?f:0),v[v.isNew?"attr":"animate"]({x:G?n:f+(y?A:0)+T+(a.x||0),y:G?f-(y?hb:0)+T:n+(a.y||0)}),v.isNew=!1;if(d&&d.enabled){var t,yc,d=B.stackTotalGroup;if(!d)B.stackTotalGroup=d=I.g("stack-labels").attr({visibility:eb,zIndex:6}).translate(O,J).add();for(t in qa)for(yc in a=
+qa[t],a)a[yc].render(d)}B.isDirty=!1}function n(a){for(var b=ta.length;b--;)ta[b].id===a&&ta[b].destroy()}var t=a.isX,y=a.opposite,G=U?!t:t,r=G?y?0:2:y?1:3,qa={},u=K(t?$b:kc,[Nc,Oc,zc,Pc][r],a),B=this,v,Z=u.type,V=Z==="datetime",R=Z==="logarithmic",T=u.offset||0,w=t?"x":"y",D=0,ua,ia,bb,jb,x,E,A,hb,Lb,Zb,Mb,ib,P,Q,bc,$,C,M,fb=u.minRange||u.maxZoom,ja=u.range,ba,ca,Aa,Ca,H=null,F=null,ma,za,Ea=u.minPadding,Ja=u.maxPadding,Ka=0,da=s(u.linkedTo),na,Ma,Na,Ha,Z=u.events,Sa,ta=[],Oa,Ga,Wa,N,Xa=u.tickPositioner,
+Pa={},va={},Da={},Fa,La,Ya,Va=u.categories,db=u.labels.formatter||function(){var a=this.value,b=this.dateTimeLabelFormat;return b?ac(b,a):Oa%1E6===0?a/1E6+"M":Oa%1E3===0?a/1E3+"k":!Va&&a>=1E3?dc(a,0):a},Ra=G&&u.labels.staggerLines,ya=u.reversed,Ia=Va&&u.tickmarkPlacement==="between"?0.5:0;b.prototype={addLabel:function(){var a=this.pos,b=u.labels,c=Va&&G&&Va.length&&!b.step&&!b.staggerLines&&!b.rotation&&ka/Va.length||!G&&ka/2,d=a===N[0],e=a===N[N.length-1],f=Va&&s(Va[a])?Va[a]:a,g=this.label,h=N.info,
+i;V&&h&&(i=u.dateTimeLabelFormats[h.higherRanks[a]||h.unitName]);this.isFirst=d;this.isLast=e;a=db.call({axis:B,chart:m,isFirst:d,isLast:e,dateTimeLabelFormat:i,value:R?Cb(cb(f)):f});c=c&&{width:W(1,z(c-2*(b.padding||10)))+ga};c=L(c,b.style);s(g)?g&&g.attr({text:a}).css(c):this.label=s(a)&&b.enabled?I.text(a,0,0,b.useHTML).attr({align:b.align,rotation:b.rotation}).css(c).add(Q):null},getLabelSize:function(){var a=this.label;return a?(this.labelBBox=a.getBBox())[G?"height":"width"]:0},getLabelSides:function(){var a=
+u.labels,b=this.labelBBox.width,a=b*{left:0,center:0.5,right:1}[a.align]-a.x;return[-a,b-a]},handleOverflow:function(a){var b=!0,c=this.isFirst,d=this.isLast,e=this.label,f=e.x;if(c||d){var g=this.getLabelSides(),h=g[0],g=g[1],i=m.plotLeft,j=i+B.len,k=(a=Pa[N[a+(c?1:-1)]])&&a.label.x+a.getLabelSides()[c?0:1];c&&!ya||d&&ya?f+h<i&&(f=i-h,a&&f+g>k&&(b=!1)):f+g>j&&(f=j-g,a&&f+h<k&&(b=!1));e.x=f}return b},render:function(a,b){var c=this.type,d=this.label,e=this.pos,f=u.labels,g=this.gridLine,h=c?c+"Grid":
+"grid",i=c?c+"Tick":"tick",j=u[h+"LineWidth"],k=u[h+"LineColor"],n=u[h+"LineDashStyle"],m=u[i+"Length"],h=u[i+"Width"]||0,l=u[i+"Color"],Y=u[i+"Position"],i=this.mark,q=f.step,ea=b&&Za||ra,qa=!0,o;o=G?Mb(e+Ia,null,null,b)+bb:x+T+(y?(b&&$a||sa)-Zb-x:0);ea=G?ea-Lb+T-(y?hb:0):ea-Mb(e+Ia,null,null,b)-bb;if(j){e=P(e+Ia,j,b);if(g===X){g={stroke:k,"stroke-width":j};if(n)g.dashstyle=n;if(!c)g.zIndex=1;this.gridLine=g=j?I.path(e).attr(g).add(bc):null}!b&&g&&e&&g.animate({d:e})}if(h)Y==="inside"&&(m=-m),y&&
+(m=-m),c=I.crispLine([wa,o,ea,fa,o+(G?0:-m),ea+(G?m:0)],h),i?i.animate({d:c}):this.mark=I.path(c).attr({stroke:l,"stroke-width":h}).add(Q);if(d&&!isNaN(o))o=o+f.x-(Ia&&G?Ia*ia*(ya?-1:1):0),ea=ea+f.y-(Ia&&!G?Ia*ia*(ya?1:-1):0),s(f.y)||(ea+=S(d.styles.lineHeight)*0.9-d.getBBox().height/2),Ra&&(ea+=a/(q||1)%Ra*16),d.x=o,d.y=ea,this.isFirst&&!p(u.showFirstLabel,1)||this.isLast&&!p(u.showLastLabel,1)?qa=!1:!Ra&&G&&f.overflow==="justify"&&!this.handleOverflow(a)&&(qa=!1),q&&a%q&&(qa=!1),qa?(d[this.isNew?
+"attr":"animate"]({x:d.x,y:d.y}),d.show(),this.isNew=!1):d.hide()},destroy:function(){Jb(this)}};c.prototype={render:function(){var a=this,b=(B.pointRange||0)/2,c=a.options,d=c.label,e=a.label,f=c.width,g=c.to,h=c.from,i=c.value,j,k=c.dashStyle,n=a.svgElem,m=[],l,Y,u=c.color;Y=c.zIndex;var ea=c.events;R&&(h=nb(h),g=nb(g),i=nb(i));if(f){if(m=P(i,f),b={stroke:u,"stroke-width":f},k)b.dashstyle=k}else if(s(h)&&s(g))h=W(h,F-b),g=Ua(g,H+b),j=P(g),(m=P(h))&&j?m.push(j[4],j[5],j[1],j[2]):m=null,b={fill:u};
+else return;if(s(Y))b.zIndex=Y;if(n)m?n.animate({d:m},null,n.onGetPath):(n.hide(),n.onGetPath=function(){n.show()});else if(m&&m.length&&(a.svgElem=n=I.path(m).attr(b).add(),ea))for(l in k=function(b){n.on(b,function(c){ea[b].apply(a,[c])})},ea)k(l);if(d&&s(d.text)&&m&&m.length&&A>0&&hb>0){d=K({align:G&&j&&"center",x:G?!j&&4:10,verticalAlign:!G&&j&&"middle",y:G?j?16:10:j?6:-4,rotation:G&&!j&&90},d);if(!e)a.label=e=I.text(d.text,0,0).attr({align:d.textAlign||d.align,rotation:d.rotation,zIndex:Y}).css(d.style).add();
+j=[m[1],m[4],p(m[6],m[1])];m=[m[2],m[5],p(m[7],m[2])];l=Sb(j);Y=Sb(m);e.align(d,!1,{x:l,y:Y,width:Ib(j)-l,height:Ib(m)-Y});e.show()}else e&&e.hide();return a},destroy:function(){Jb(this);Gb(ta,this)}};d.prototype={destroy:function(){Jb(this)},setTotal:function(a){this.cum=this.total=a},render:function(a){var b=this.options.formatter.call(this);this.label?this.label.attr({text:b,visibility:ab}):this.label=m.renderer.text(b,0,0).css(this.options.style).attr({align:this.textAlign,rotation:this.options.rotation,
+visibility:ab}).add(a)},setOffset:function(a,b){var c=this.isNegative,d=B.translate(this.total,0,0,0,1),e=B.translate(0),e=Ba(d-e),f=m.xAxis[0].translate(this.x)+a,g=m.plotHeight,c={x:U?c?d:d-e:f,y:U?g-f-b:c?g-d-e:g-d,width:U?e:b,height:U?b:e};this.label&&this.label.align(this.alignOptions,null,c).attr({visibility:eb})}};Mb=function(a,b,c,d,e){var f=1,g=0,h=d?jb:ia,d=d?ma:F,e=u.ordinal||R&&e;h||(h=ia);c&&(f*=-1,g=D);ya&&(f*=-1,g-=f*D);b?(ya&&(a=D-a),a=a/h+d,e&&(a=B.lin2val(a))):(e&&(a=B.val2lin(a)),
+a=f*(a-d)*h+g+f*Ka);return a};P=function(a,b,c){var d,e,f,a=Mb(a,null,null,c),g=c&&Za||ra,h=c&&$a||sa,i,c=e=z(a+bb);d=f=z(g-a-bb);if(isNaN(a))i=!0;else if(G){if(d=E,f=g-Lb,c<x||c>x+A)i=!0}else if(c=x,e=h-Zb,d<E||d>E+hb)i=!0;return i?null:I.crispLine([wa,c,d,fa,e,f],b||0)};ib=function(){var a=H-F,b=0,c,d;if(t)da?b=na.pointRange:o(B.series,function(a){b=W(b,a.pointRange);d=a.closestPointRange;!a.noSharedTooltip&&s(d)&&(c=s(c)?Ua(c,d):d)}),B.pointRange=b,B.closestPointRange=c;jb=ia;B.translationSlope=
+ia=D/(a+b||1);bb=G?x:Lb;Ka=ia*(b/2)};xa.push(B);m[t?"xAxis":"yAxis"].push(B);U&&t&&ya===X&&(ya=!0);L(B,{addPlotBand:k,addPlotLine:k,adjustTickAmount:function(){if(gb&&gb[w]&&!V&&!Va&&!da&&u.alignTicks!==!1){var a=Fa,b=N.length;Fa=gb[w];if(b<Fa){for(;N.length<Fa;)N.push(Cb(N[N.length-1]+Oa));ia*=(b-1)/(Fa-1);H=N[N.length-1]}if(s(a)&&Fa!==a)B.isDirty=!0}},categories:Va,getExtremes:function(){return{min:R?Cb(cb(F)):F,max:R?Cb(cb(H)):H,dataMin:C,dataMax:M,userMin:ba,userMax:ca}},getPlotLinePath:P,getThreshold:function(a){var b=
+R?cb(F):F,c=R?cb(H):H;b>a||a===null?a=b:c<a&&(a=c);return Mb(a,0,1,0,1)},isXAxis:t,options:u,plotLinesAndBands:ta,getOffset:function(){var a=B.series.length&&s(F)&&s(H),c=a||p(u.showEmpty,!0),d=0,e,f=0,g=u.title,h=u.labels,i=[-1,1,1,-1][r],j;Q||(Q=I.g("axis").attr({zIndex:7}).add(),bc=I.g("grid").attr({zIndex:u.gridZIndex||1}).add());La=0;if(a||da)o(N,function(a){Pa[a]?Pa[a].addLabel():Pa[a]=new b(a)}),o(N,function(a){if(r===0||r===2||{1:"left",3:"right"}[r]===h.align)La=W(Pa[a].getLabelSize(),La)}),
+Ra&&(La+=(Ra-1)*16);else for(j in Pa)Pa[j].destroy(),delete Pa[j];if(g&&g.text){if(!v)v=B.axisTitle=I.text(g.text,0,0,g.useHTML).attr({zIndex:7,rotation:g.rotation||0,align:g.textAlign||{low:"left",middle:"center",high:"right"}[g.align]}).css(g.style).add(),v.isNew=!0;if(c)d=v.getBBox()[G?"height":"width"],f=p(g.margin,G?5:10),e=g.offset;v[c?"show":"hide"]()}T=i*p(u.offset,pa[r]);Ya=p(e,La+f+(r!==2&&La&&i*u.labels[G?"y":"x"]));pa[r]=W(pa[r],Ya+d+i*T)},render:l,setAxisSize:function(){var a=u.offsetLeft||
+0,b=u.offsetRight||0;x=p(u.left,O+a);E=p(u.top,J);A=p(u.width,ka-a+b);hb=p(u.height,la);Lb=ra-hb-E;Zb=sa-A-x;D=G?A:hb;B.left=x;B.top=E;B.len=D},setAxisTranslation:ib,setCategories:function(b,c){B.categories=a.categories=Va=b;o(B.series,function(a){a.translate();a.setTooltipPoints(!0)});B.isDirty=!0;p(c,!0)&&m.redraw()},setExtremes:function(a,b,c,d,e){c=p(c,!0);e=L(e,{min:a,max:b});aa(B,"setExtremes",e,function(){ba=a;ca=b;B.isDirtyExtremes=!0;c&&m.redraw(d)})},setScale:function(){var a,b,c,d;ma=F;
+za=H;ua=D;D=G?A:hb;d=D!==ua;o(B.series,function(a){if(a.isDirtyData||a.isDirty||a.xAxis.isDirty)c=!0});if(d||c||da||ba!==Aa||ca!==Ca){e();j();Aa=ba;Ca=ca;if(!t)for(a in qa)for(b in qa[a])qa[a][b].cum=qa[a][b].total;if(!B.isDirty)B.isDirty=d||F!==ma||H!==za}},setTickPositions:j,translate:Mb,redraw:function(){ub.resetTracker&&ub.resetTracker();l();o(ta,function(a){a.render()});o(B.series,function(a){a.isDirty=!0})},removePlotBand:n,removePlotLine:n,reversed:ya,setTitle:function(a,b){u.title=K(u.title,
+a);v=v.destroy();B.isDirty=!0;p(b,!0)&&m.redraw()},series:[],stacks:qa,destroy:function(){var a;Qa(B);for(a in qa)Jb(qa[a]),qa[a]=null;if(B.stackTotalGroup)B.stackTotalGroup=B.stackTotalGroup.destroy();o([Pa,va,Da,ta],function(a){Jb(a)});o([$,Q,bc,v],function(a){a&&a.destroy()});$=Q=bc=v=null}});for(Sa in Z)ha(B,Sa,Z[Sa]);if(R)B.val2lin=nb,B.lin2val=cb}function d(a){function b(){var c=this.points||Hb(this),d=c[0].series,e;e=[d.tooltipHeaderFormatter(c[0].key)];o(c,function(a){d=a.series;e.push(d.tooltipFormatter&&
+d.tooltipFormatter(a)||a.point.tooltipFormatter(d.tooltipOptions.pointFormat))});e.push(a.footerFormat||"");return e.join("")}function c(a,b){l=n?a:(2*l+a)/3;q=n?b:(q+b)/2;t.attr({x:l,y:q});lb=Ba(a-l)>1||Ba(b-q)>1?function(){c(a,b)}:null}function d(){if(!n){var a=m.hoverPoints;t.hide();a&&o(a,function(a){a.setState()});m.hoverPoints=null;n=!0}}var e,f=a.borderWidth,g=a.crosshairs,h=[],i=a.style,j=a.shared,k=S(i.padding),n=!0,l=0,q=0;i.padding=0;var t=I.label("",0,0,null,null,null,a.useHTML).attr({padding:k,
+fill:a.backgroundColor,"stroke-width":f,r:a.borderRadius,zIndex:8}).css(i).hide().add();Fa||t.shadow(a.shadow);return{shared:j,refresh:function(f){var i,k,l,q,r={},y=[];l=f.tooltipPos;i=a.formatter||b;var r=m.hoverPoints,v;j&&(!f.series||!f.series.noSharedTooltip)?(q=0,r&&o(r,function(a){a.setState()}),m.hoverPoints=f,o(f,function(a){a.setState(ta);q+=a.plotY;y.push(a.getLabelConfig())}),k=f[0].plotX,q=z(q)/f.length,r={x:f[0].category},r.points=y,f=f[0]):r=f.getLabelConfig();r=i.call(r);e=f.series;
+k=p(k,f.plotX);q=p(q,f.plotY);i=z(l?l[0]:U?ka-q:k);k=z(l?l[1]:U?la-k:q);l=j||!e.isCartesian||e.tooltipOutsidePlot||Db(i,k);r===!1||!l?d():(n&&(t.show(),n=!1),t.attr({text:r}),v=a.borderColor||f.color||e.color||"#606060",t.attr({stroke:v}),l=uc(t.width,t.height,O,J,ka,la,{x:i,y:k},p(a.distance,12),U),c(z(l.x),z(l.y)));if(g){g=Hb(g);var R;l=g.length;for(var Z;l--;)if(R=f.series[l?"yAxis":"xAxis"],g[l]&&R)if(R=R.getPlotLinePath(l?p(f.stackY,f.y):f.x,1),h[l])h[l].attr({d:R,visibility:eb});else{Z={"stroke-width":g[l].width||
+1,stroke:g[l].color||"#C0C0C0",zIndex:g[l].zIndex||2};if(g[l].dashStyle)Z.dashstyle=g[l].dashStyle;h[l]=I.path(R).attr(Z).add()}}aa(m,"tooltipRefresh",{text:r,x:i+O,y:k+J,borderColor:v})},hide:d,hideCrosshairs:function(){o(h,function(a){a&&a.hide()})},destroy:function(){o(h,function(a){a&&a.destroy()});t&&(t=t.destroy())}}}function e(a){function b(a){var c,d,e,a=a||ca.event;if(!a.target)a.target=a.srcElement;if(a.originalEvent)a=a.originalEvent;if(a.event)a=a.event;c=a.touches?a.touches.item(0):a;
+ya=Ac(D);d=ya.left;e=ya.top;Qb?(d=a.x,c=a.y):(d=c.pageX-d,c=c.pageY-e);return L(a,{chartX:z(d),chartY:z(c)})}function c(a){var b={xAxis:[],yAxis:[]};o(xa,function(c){var d=c.translate,e=c.isXAxis;b[e?"xAxis":"yAxis"].push({axis:c,value:d((U?!e:e)?a.chartX-O:la-a.chartY+J,!0)})});return b}function e(){var a=m.hoverSeries,b=m.hoverPoint;if(b)b.onMouseOut();if(a)a.onMouseOut();vb&&(vb.hide(),vb.hideCrosshairs());ob=null}function f(){if(n){var a={xAxis:[],yAxis:[]},b=n.getBBox(),c=b.x-O,d=b.y-J;k&&(o(xa,
+function(e){if(e.options.zoomEnabled!==!1){var f=e.translate,g=e.isXAxis,h=U?!g:g,i=f(h?c:la-d-b.height,!0,0,0,1),f=f(h?c+b.width:la-d,!0,0,0,1);a[g?"xAxis":"yAxis"].push({axis:e,min:Ua(i,f),max:W(i,f)})}}),aa(m,"selection",a,zb));n=n.destroy()}P(D,{cursor:"auto"});m.mouseIsDown=Da=k=!1;Qa(x,Ga?"touchend":"mouseup",f)}function g(a){var b=s(a.pageX)?a.pageX:a.page.x,a=s(a.pageX)?a.pageY:a.page.y;ya&&!Db(b-ya.left-O,a-ya.top-J)&&e()}function h(){e();ya=null}var i,j,k,n,l=Fa?"":q.zoomType,t=/x/.test(l),
+r=/y/.test(l),y=t&&!U||r&&U,p=r&&!U||t&&U;if(!db)m.trackerGroup=db=I.g("tracker").attr({zIndex:9}).add();if(a.enabled)m.tooltip=vb=d(a),Bb=setInterval(function(){lb&&lb()},32);(function(){D.onmousedown=function(a){a=b(a);!Ga&&a.preventDefault&&a.preventDefault();m.mouseIsDown=Da=!0;m.mouseDownX=i=a.chartX;j=a.chartY;ha(x,Ga?"touchend":"mouseup",f)};var d=function(c){if(!c||!(c.touches&&c.touches.length>1)){c=b(c);if(!Ga)c.returnValue=!1;var d=c.chartX,e=c.chartY,f=!Db(d-O,e-J);Ga&&c.type==="touchstart"&&
+(A(c.target,"isTracker")?m.runTrackerClick||c.preventDefault():!kb&&!f&&c.preventDefault());f&&(d<O?d=O:d>O+ka&&(d=O+ka),e<J?e=J:e>J+la&&(e=J+la));if(Da&&c.type!=="touchstart"){if(k=Math.sqrt(Math.pow(i-d,2)+Math.pow(j-e,2)),k>10){var g=Db(i-O,j-J);if(Nb&&(t||r)&&g)n||(n=I.rect(O,J,y?1:ka,p?1:la,0).attr({fill:q.selectionMarkerFill||"rgba(69,114,167,0.25)",zIndex:7}).add());n&&y&&(c=d-i,n.attr({width:Ba(c),x:(c>0?0:c)+i}));n&&p&&(e-=j,n.attr({height:Ba(e),y:(e>0?0:e)+j}));g&&!n&&q.panning&&m.pan(d)}}else if(!f){var h,
+d=m.hoverPoint,e=m.hoverSeries,l,o,g=sa,R=U?c.chartY:c.chartX-O;if(vb&&a.shared&&(!e||!e.noSharedTooltip)){h=[];l=Q.length;for(o=0;o<l;o++)if(Q[o].visible&&Q[o].options.enableMouseTracking!==!1&&!Q[o].noSharedTooltip&&Q[o].tooltipPoints.length)c=Q[o].tooltipPoints[R],c._dist=Ba(R-c.plotX),g=Ua(g,c._dist),h.push(c);for(l=h.length;l--;)h[l]._dist>g&&h.splice(l,1);if(h.length&&h[0].plotX!==ob)vb.refresh(h),ob=h[0].plotX}if(e&&e.tracker&&(c=e.tooltipPoints[R])&&c!==d)c.onMouseOver()}return f||!Nb}};D.onmousemove=
+d;ha(D,"mouseleave",h);ha(x,"mousemove",g);D.ontouchstart=function(a){if(t||r)D.onmousedown(a);d(a)};D.ontouchmove=d;D.ontouchend=function(){k&&e()};D.onclick=function(a){var d=m.hoverPoint,a=b(a);a.cancelBubble=!0;if(!k)if(d&&(A(a.target,"isTracker")||A(a.target.parentNode,"isTracker"))){var e=d.plotX,f=d.plotY;L(d,{pageX:ya.left+O+(U?ka-f:e),pageY:ya.top+J+(U?la-e:f)});aa(d.series,"click",L(a,{point:d}));d.firePointEvent("click",a)}else L(a,c(a)),Db(a.chartX-O,a.chartY-J)&&aa(m,"click",a);k=!1}})();
+L(this,{zoomX:t,zoomY:r,resetTracker:e,normalizeMouseEvent:b,destroy:function(){if(m.trackerGroup)m.trackerGroup=db=m.trackerGroup.destroy();Qa(D,"mouseleave",h);Qa(x,"mousemove",g);D.onclick=D.onmousedown=D.onmousemove=D.ontouchstart=D.ontouchend=D.ontouchmove=null}})}function f(a){var b=a.type||q.type||q.defaultSeriesType,c=Ha[b],d=m.hasRendered;if(d)if(U&&b==="column")c=Ha.bar;else if(!U&&b==="bar")c=Ha.column;b=new c;b.init(m,a);!d&&b.inverted&&(U=!0);if(b.isCartesian)Nb=b.isCartesian;Q.push(b);
+return b}function g(){q.alignTicks!==!1&&o(xa,function(a){a.adjustTickAmount()});gb=null}function h(a){var b=m.isDirtyLegend,c,d=m.isDirtyBox,e=Q.length,f=e,h=m.clipRect;for(Kb(a,m);f--;)if(a=Q[f],a.isDirty&&a.options.stacking){c=!0;break}if(c)for(f=e;f--;)if(a=Q[f],a.options.stacking)a.isDirty=!0;o(Q,function(a){a.isDirty&&a.options.legendType==="point"&&(b=!0)});if(b&&Xa.renderLegend)Xa.renderLegend(),m.isDirtyLegend=!1;Nb&&(La||(gb=null,o(xa,function(a){a.setScale()})),g(),Wb(),o(xa,function(a){if(a.isDirtyExtremes)a.isDirtyExtremes=
+!1,aa(a,"afterSetExtremes",a.getExtremes());if(a.isDirty||d)a.redraw(),d=!0}));d&&(qb(),h&&(Ob(h),h.animate({width:m.plotSizeX,height:m.plotSizeY+1})));o(Q,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});ub&&ub.resetTracker&&ub.resetTracker();I.draw();aa(m,"redraw")}function i(){var a=v.xAxis||{},b=v.yAxis||{},a=Hb(a);o(a,function(a,b){a.index=b;a.isX=!0});b=Hb(b);o(b,function(a,b){a.index=b});a=a.concat(b);o(a,function(a){new c(a)});g()}function k(){var a=Ea.lang,b=q.resetZoomButton,
+c=b.theme,d=c.states,e=b.relativeTo==="chart"?null:{x:O,y:J,width:ka,height:la};m.resetZoomButton=I.button(a.resetZoom,null,null,Fb,c,d&&d.hover).attr({align:b.position.align,title:a.resetZoomTitle}).add().align(b.position,!1,e)}function j(a,b){$=K(v.title,a);ja=K(v.subtitle,b);o([["title",a,$],["subtitle",b,ja]],function(a){var b=a[0],c=m[b],d=a[1],a=a[2];c&&d&&(c=c.destroy());a&&a.text&&!c&&(m[b]=I.text(a.text,0,0,a.useHTML).attr({align:a.align,"class":Ia+b,zIndex:a.zIndex||4}).css(a.style).add().align(a,
+!1,ib))})}function l(){Ja=q.renderTo;na=Ia+lc++;Ab(Ja)&&(Ja=x.getElementById(Ja));Ja||jc(13,!0);Ja.innerHTML="";Ja.offsetWidth||(C=Ja.cloneNode(0),P(C,{position:wb,top:"-9999px",display:""}),x.body.appendChild(C));za=(C||Ja).offsetWidth;Ca=(C||Ja).offsetHeight;m.chartWidth=sa=q.width||za||600;m.chartHeight=ra=q.height||(Ca>19?Ca:400);m.container=D=Aa(Ra,{className:Ia+"container"+(q.className?" "+q.className:""),id:na},L({position:mc,overflow:ab,width:sa+ga,height:ra+ga,textAlign:"left",lineHeight:"normal"},
+q.style),C||Ja);m.renderer=I=q.forExport?new Eb(D,sa,ra,!0):new Xb(D,sa,ra);Fa&&I.create(m,D,sa,ra);var a,b;Bc&&D.getBoundingClientRect&&(a=function(){P(D,{left:0,top:0});b=D.getBoundingClientRect();P(D,{left:-(b.left-S(b.left))+ga,top:-(b.top-S(b.top))+ga})},a(),ha(ca,"resize",a),ha(m,"destroy",function(){Qa(ca,"resize",a)}))}function n(){function a(c){var d=q.width||Ja.offsetWidth,e=q.height||Ja.offsetHeight,c=c?c.target:ca;if(d&&e&&(c===ca||c===x)){if(d!==za||e!==Ca)clearTimeout(b),b=setTimeout(function(){tb(d,
+e,!1)},100);za=d;Ca=e}}var b;ha(ca,"resize",a);ha(m,"destroy",function(){Qa(ca,"resize",a)})}function t(){m&&aa(m,"endResize",null,function(){La-=1})}function r(){for(var a=U||q.inverted||q.type==="bar"||q.defaultSeriesType==="bar",b=v.series,c=b&&b.length;!a&&c--;)b[c].type==="bar"&&(a=!0);m.inverted=U=a}function Z(){var a=v.labels,b=v.credits,c;j();Xa=m.legend=new Rb;o(xa,function(a){a.setScale()});Wb();o(xa,function(a){a.setTickPositions(!0)});g();Wb();qb();Nb&&o(xa,function(a){a.render()});if(!m.seriesGroup)m.seriesGroup=
+I.g("series-group").attr({zIndex:3}).add();o(Q,function(a){a.translate();a.setTooltipPoints();a.render()});a.items&&o(a.items,function(){var b=L(a.style,this.style),c=S(b.left)+O,d=S(b.top)+J+12;delete b.left;delete b.top;I.text(this.html,c,d).attr({zIndex:2}).css(b).add()});if(b.enabled&&!m.credits)c=b.href,m.credits=I.text(b.text,0,0).on("click",function(){if(c)location.href=c}).attr({align:b.position.align,zIndex:8}).css(b.style).add().align(b.position);m.hasRendered=!0}function V(){if(!Pb&&ca==
+ca.top&&x.readyState!=="complete"||Fa&&!ca.canvg)Fa?Cc.push(V,v.global.canvasToolsURL):x.attachEvent("onreadystatechange",function(){x.detachEvent("onreadystatechange",V);x.readyState==="complete"&&V()});else{l();aa(m,"init");if(Highcharts.RangeSelector&&v.rangeSelector.enabled)m.rangeSelector=new Highcharts.RangeSelector(m);rb();sb();r();i();o(v.series||[],function(a){f(a)});if(Highcharts.Scroller&&(v.navigator.enabled||v.scrollbar.enabled))m.scroller=new Highcharts.Scroller(m);m.render=Z;m.tracker=
+ub=new e(v.tooltip);Z();I.draw();b&&b.apply(m,[m]);o(m.callbacks,function(a){a.apply(m,[m])});C&&(Ja.appendChild(D),Tb(C));aa(m,"load")}}var v,y=a.series;a.series=null;v=K(Ea,a);v.series=a.series=y;var q=v.chart,y=q.margin,y=mb(y)?y:[y,y,y,y],T=p(q.marginTop,y[0]),w=p(q.marginRight,y[1]),E=p(q.marginBottom,y[2]),bb=p(q.marginLeft,y[3]),ia=q.spacingTop,ua=q.spacingRight,R=q.spacingBottom,jb=q.spacingLeft,ib,$,ja,J,ba,M,O,pa,Ja,C,D,na,za,Ca,sa,ra,$a,Za,ma,Na,Sa,da,m=this,kb=(y=q.events)&&!!y.click,
+va,Db,vb,Da,xb,pb,Ya,la,ka,ub,db,Xa,Wa,yb,ya,Nb=q.showAxes,La=0,xa=[],gb,Q=[],U,I,lb,Bb,ob,qb,Wb,rb,sb,tb,zb,Fb,Rb=function(){function a(b,c){var d=b.legendItem,e=b.legendLine,g=b.legendSymbol,h=q.color,i=c?f.itemStyle.color:h,h=c?b.color:h;d&&d.css({fill:i});e&&e.attr({stroke:h});g&&g.attr({stroke:h,fill:h})}function b(a){var c=a.legendItem,d=a.legendLine,e=a._legendItemPos,f=e[0],e=e[1],g=a.legendSymbol,a=a.checkbox;c&&c.attr({x:r?f:Wa-f,y:e});d&&d.translate(r?f:Wa-f,e-4);g&&(c=f+g.xOff,g.attr({x:r?
+c:Wa-c,y:e+g.yOff}));if(a)a.x=f,a.y=e}function c(){o(j,function(a){var b=a.checkbox,c=A.alignAttr;b&&P(b,{left:c.translateX+a.legendItemWidth+b.x-40+ga,top:c.translateY+b.y-11+ga})})}function d(b){var c,e,j,k,m=b.legendItem;k=b.series||b;var o=k.options,v=o&&o.borderWidth||0;if(!m){k=/^(bar|pie|area|column)$/.test(k.type);b.legendItem=m=I.text(f.labelFormatter.call(b),0,0,f.useHTML).css(b.visible?n:q).on("mouseover",function(){b.setState(ta);m.css(l)}).on("mouseout",function(){m.css(b.visible?n:q);
+b.setState()}).on("click",function(){var a=function(){b.setVisible()};b.firePointEvent?b.firePointEvent("legendItemClick",null,a):aa(b,"legendItemClick",null,a)}).attr({align:r?"left":"right",zIndex:2}).add(A);if(!k&&o&&o.lineWidth){var u={"stroke-width":o.lineWidth,zIndex:2};if(o.dashStyle)u.dashstyle=o.dashStyle;b.legendLine=I.path([wa,(-h-i)*(r?1:-1),0,fa,-i*(r?1:-1),0]).attr(u).add(A)}if(k)j=I.rect(c=-h-i,e=-11,h,12,2).attr({zIndex:3}).add(A),r||(c+=h);else if(o&&o.marker&&o.marker.enabled)j=
+o.marker.radius,j=I.symbol(b.symbol,c=-h/2-i-j,e=-4-j,2*j,2*j).attr(b.pointAttr[Ka]).attr({zIndex:3}).add(A),r||(c+=h/2);if(j)j.xOff=c+v%2/2,j.yOff=e+v%2/2;b.legendSymbol=j;a(b,b.visible);if(o&&o.showCheckbox)b.checkbox=Aa("input",{type:"checkbox",checked:b.selected,defaultChecked:b.selected},f.itemCheckboxStyle,D),ha(b.checkbox,"click",function(a){aa(b,"checkboxClick",{checked:a.target.checked},function(){b.select()})})}c=m.getBBox();e=b.legendItemWidth=f.itemWidth||h+i+c.width+t;w=c.height;if(g&&
+s-V+e>(J||sa-2*t-V))s=V,z+=y+w+p;!g&&z+f.y+w>ra-ia-R&&(z=T,s+=Z,Z=0);Z=W(Z,e);x=W(x,z+p);b._legendItemPos=[s,z];g?s+=e:z+=y+w+p;C=J||W(s-V+(g?0:e),C)}function e(){s=V;z=T;x=C=0;A||(A=I.g("legend").attr({zIndex:7}).add());j=[];o(M,function(a){var b=a.options;b.showInLegend&&(j=j.concat(a.legendItems||(b.legendType==="point"?a.data:a)))});Mc(j,function(a,b){return(a.options.legendIndex||0)-(b.options.legendIndex||0)});jb&&j.reverse();o(j,d);Wa=J||C;yb=x-v+w;if(E||bb){Wa+=2*t;yb+=2*t;if(ua){if(Wa>0&&
+yb>0)ua[ua.isNew?"attr":"animate"](ua.crisp(null,null,null,Wa,yb)),ua.isNew=!1}else ua=I.rect(0,0,Wa,yb,f.borderRadius,E||0).attr({stroke:f.borderColor,"stroke-width":E||0,fill:bb||Ma}).add(A).shadow(f.shadow),ua.isNew=!0;ua[j.length?"show":"hide"]()}o(j,b);for(var a=["left","right","top","bottom"],g,h=4;h--;)g=a[h],k[g]&&k[g]!=="auto"&&(f[h<2?"align":"verticalAlign"]=g,f[h<2?"x":"y"]=S(k[g])*(h%2?-1:1));j.length&&A.align(L(f,{width:Wa,height:yb}),!0,ib);La||c()}var f=m.options.legend;if(f.enabled){var g=
+f.layout==="horizontal",h=f.symbolWidth,i=f.symbolPadding,j,k=f.style,n=f.itemStyle,l=f.itemHoverStyle,q=K(n,f.itemHiddenStyle),t=f.padding||S(k.padding),r=!f.rtl,y=f.itemMarginTop||0,p=f.itemMarginBottom||0,v=18,Z=0,V=4+t+h+i,T=t+y+v-5,s,z,x,w=0,ua,E=f.borderWidth,bb=f.backgroundColor,A,C,J=f.width,M=m.series,jb=f.reversed;e();ha(m,"endResize",c);return{colorizeItem:a,destroyItem:function(a){var b=a.checkbox;o(["legendItem","legendLine","legendSymbol"],function(b){a[b]&&a[b].destroy()});b&&Tb(a.checkbox)},
+renderLegend:e,destroy:function(){ua&&(ua=ua.destroy());A&&(A=A.destroy())}}}};Db=function(a,b){return a>=0&&a<=ka&&b>=0&&b<=la};Fb=function(){var a=m.resetZoomButton;aa(m,"selection",{resetSelection:!0},zb);if(a)m.resetZoomButton=a.destroy()};zb=function(a){var b;m.resetZoomEnabled!==!1&&!m.resetZoomButton&&k();!a||a.resetSelection?o(xa,function(a){a.options.zoomEnabled!==!1&&(a.setExtremes(null,null,!1),b=!0)}):o(a.xAxis.concat(a.yAxis),function(a){var c=a.axis;if(m.tracker[c.isXAxis?"zoomX":"zoomY"])c.setExtremes(a.min,
+a.max,!1),b=!0});b&&h(p(q.animation,m.pointCount<100))};m.pan=function(a){var b=m.xAxis[0],c=m.mouseDownX,d=b.pointRange/2,e=b.getExtremes(),f=b.translate(c-a,!0)+d,c=b.translate(c+ka-a,!0)-d;(d=m.hoverPoints)&&o(d,function(a){a.setState()});f>Ua(e.dataMin,e.min)&&c<W(e.dataMax,e.max)&&b.setExtremes(f,c,!0,!1);m.mouseDownX=a;P(D,{cursor:"move"})};Wb=function(){var a=v.legend,b=p(a.margin,10),c=a.x,d=a.y,e=a.align,f=a.verticalAlign,g;rb();if((m.title||m.subtitle)&&!s(T))(g=W(m.title&&!$.floating&&
+!$.verticalAlign&&$.y||0,m.subtitle&&!ja.floating&&!ja.verticalAlign&&ja.y||0))&&(J=W(J,g+p($.margin,15)+ia));a.enabled&&!a.floating&&(e==="right"?s(w)||(ba=W(ba,Wa-c+b+ua)):e==="left"?s(bb)||(O=W(O,Wa+c+b+jb)):f==="top"?s(T)||(J=W(J,yb+d+b+ia)):f==="bottom"&&(s(E)||(M=W(M,yb-d+b+R))));m.extraBottomMargin&&(M+=m.extraBottomMargin);m.extraTopMargin&&(J+=m.extraTopMargin);Nb&&o(xa,function(a){a.getOffset()});s(bb)||(O+=pa[3]);s(T)||(J+=pa[0]);s(E)||(M+=pa[2]);s(w)||(ba+=pa[1]);sb()};tb=function(a,b,
+c){var d=m.title,e=m.subtitle;La+=1;Kb(c,m);Za=ra;$a=sa;if(s(a))m.chartWidth=sa=z(a);if(s(b))m.chartHeight=ra=z(b);P(D,{width:sa+ga,height:ra+ga});I.setSize(sa,ra,c);ka=sa-O-ba;la=ra-J-M;gb=null;o(xa,function(a){a.isDirty=!0;a.setScale()});o(Q,function(a){a.isDirty=!0});m.isDirtyLegend=!0;m.isDirtyBox=!0;Wb();d&&d.align(null,null,ib);e&&e.align(null,null,ib);h(c);Za=null;aa(m,"resize");Ub===!1?t():setTimeout(t,Ub&&Ub.duration||500)};sb=function(){m.plotLeft=O=z(O);m.plotTop=J=z(J);m.plotWidth=ka=
+z(sa-O-ba);m.plotHeight=la=z(ra-J-M);m.plotSizeX=U?la:ka;m.plotSizeY=U?ka:la;ib={x:jb,y:ia,width:sa-jb-ua,height:ra-ia-R};o(xa,function(a){a.setAxisSize();a.setAxisTranslation()})};rb=function(){J=p(T,ia);ba=p(w,ua);M=p(E,R);O=p(bb,jb);pa=[0,0,0,0]};qb=function(){var a=q.borderWidth||0,b=q.backgroundColor,c=q.plotBackgroundColor,d=q.plotBackgroundImage,e,f={x:O,y:J,width:ka,height:la};e=a+(q.shadow?8:0);if(a||b)ma?ma.animate(ma.crisp(null,null,null,sa-e,ra-e)):ma=I.rect(e/2,e/2,sa-e,ra-e,q.borderRadius,
+a).attr({stroke:q.borderColor,"stroke-width":a,fill:b||Ma}).add().shadow(q.shadow);c&&(Na?Na.animate(f):Na=I.rect(O,J,ka,la,0).attr({fill:c}).add().shadow(q.plotShadow));d&&(Sa?Sa.animate(f):Sa=I.image(d,O,J,ka,la).add());q.plotBorderWidth&&(da?da.animate(da.crisp(null,O,J,ka,la)):da=I.rect(O,J,ka,la,0,q.plotBorderWidth).attr({stroke:q.plotBorderColor,"stroke-width":q.plotBorderWidth,zIndex:4}).add());m.isDirtyBox=!1};q.reflow!==!1&&ha(m,"load",n);if(y)for(va in y)ha(m,va,y[va]);m.options=v;m.series=
+Q;m.xAxis=[];m.yAxis=[];m.addSeries=function(a,b,c){var d;a&&(Kb(c,m),b=p(b,!0),aa(m,"addSeries",{options:a},function(){d=f(a);d.isDirty=!0;m.isDirtyLegend=!0;b&&m.redraw()}));return d};m.animation=Fa?!1:p(q.animation,!0);m.Axis=c;m.destroy=function(){var a,b=D&&D.parentNode;if(m!==null){aa(m,"destroy");Qa(m);for(a=xa.length;a--;)xa[a]=xa[a].destroy();for(a=Q.length;a--;)Q[a]=Q[a].destroy();o("title,subtitle,seriesGroup,clipRect,credits,tracker,scroller,rangeSelector".split(","),function(a){var b=
+m[a];b&&(m[a]=b.destroy())});o([ma,da,Na,Xa,vb,I,ub],function(a){a&&a.destroy&&a.destroy()});ma=da=Na=Xa=vb=I=ub=null;if(D)D.innerHTML="",Qa(D),b&&Tb(D),D=null;clearInterval(Bb);for(a in m)delete m[a];v=m=null}};m.get=function(a){var b,c,d;for(b=0;b<xa.length;b++)if(xa[b].options.id===a)return xa[b];for(b=0;b<Q.length;b++)if(Q[b].options.id===a)return Q[b];for(b=0;b<Q.length;b++){d=Q[b].points||[];for(c=0;c<d.length;c++)if(d[c].id===a)return d[c]}return null};m.getSelectedPoints=function(){var a=
+[];o(Q,function(b){a=a.concat(nc(b.points,function(a){return a.selected}))});return a};m.getSelectedSeries=function(){return nc(Q,function(a){return a.selected})};m.hideLoading=function(){xb&&cc(xb,{opacity:0},{duration:v.loading.hideDuration||100,complete:function(){P(xb,{display:Ma})}});Ya=!1};m.initSeries=f;m.isInsidePlot=Db;m.redraw=h;m.setSize=tb;m.setTitle=j;m.showLoading=function(a){var b=v.loading;xb||(xb=Aa(Ra,{className:Ia+"loading"},L(b.style,{left:O+ga,top:J+ga,width:ka+ga,height:la+ga,
+zIndex:10,display:Ma}),D),pb=Aa("span",null,b.labelStyle,xb));pb.innerHTML=a||v.lang.loading;Ya||(P(xb,{opacity:0,display:""}),cc(xb,{opacity:b.style.opacity},{duration:b.showDuration||0}),Ya=!0)};m.pointCount=0;m.counters=new tc;V()}var X,x=document,ca=window,oa=Math,z=oa.round,Ta=oa.floor,Yb=oa.ceil,W=oa.max,Ua=oa.min,Ba=oa.abs,ja=oa.cos,C=oa.sin,da=oa.PI,Dc=da*2/360,kb=navigator.userAgent,Qb=/msie/i.test(kb)&&!ca.opera,Xa=x.documentMode===8,Ec=/AppleWebKit/.test(kb),Bc=/Firefox/.test(kb),Pb=!!x.createElementNS&&
+!!x.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect,Qc=Bc&&parseInt(kb.split("Firefox/")[1],10)<4,Fa=!Pb&&!Qb&&!!x.createElement("canvas").getContext,Xb,Ga=x.documentElement.ontouchstart!==X,Fc={},lc=0,tb,Ea,ac,Ub,La,E,Ra="div",wb="absolute",mc="relative",ab="hidden",Ia="highcharts-",eb="visible",ga="px",Ma="none",wa="M",fa="L",Gc="rgba(192,192,192,"+(Pb?1.0E-6:0.0020)+")",Ka="",ta="hover",Rb="millisecond",ob="second",pb="minute",va="hour",Ca="day",Ya="week",Da="month",Za="year",
+sb,fc,gc,ic,db,qb,rb,pc,qc,hc,rc,sc,w=ca.HighchartsAdapter,na=w||{},Hc=na.getScript,o=na.each,nc=na.grep,Ac=na.offset,Vb=na.map,K=na.merge,ha=na.addEvent,Qa=na.removeEvent,aa=na.fireEvent,cc=na.animate,Ob=na.stop,Ha={};ca.Highcharts={};ac=function(a,b,c){if(!s(b)||isNaN(b))return"Invalid date";var a=p(a,"%Y-%m-%d %H:%M:%S"),d=new Date(b),e,f=d[gc](),g=d[ic](),h=d[db](),i=d[qb](),k=d[rb](),j=Ea.lang,l=j.weekdays,b={a:l[g].substr(0,3),A:l[g],d:Na(h),e:h,b:j.shortMonths[i],B:j.months[i],m:Na(i+1),y:k.toString().substr(2,
+2),Y:k,H:Na(f),I:Na(f%12||12),l:f%12||12,M:Na(d[fc]()),p:f<12?"AM":"PM",P:f<12?"am":"pm",S:Na(d.getSeconds()),L:Na(z(b%1E3),3)};for(e in b)a=a.replace("%"+e,b[e]);return c?a.substr(0,1).toUpperCase()+a.substr(1):a};tc.prototype={wrapColor:function(a){if(this.color>=a)this.color=0},wrapSymbol:function(a){if(this.symbol>=a)this.symbol=0}};E=za(Rb,1,ob,1E3,pb,6E4,va,36E5,Ca,864E5,Ya,6048E5,Da,2592E6,Za,31556952E3);La={init:function(a,b,c){var b=b||"",d=a.shift,e=b.indexOf("C")>-1,f=e?7:3,g,b=b.split(" "),
+c=[].concat(c),h,i,k=function(a){for(g=a.length;g--;)a[g]===wa&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(k(b),k(c));a.isArea&&(h=b.splice(b.length-6,6),i=c.splice(c.length-6,6));d===1&&(c=[].concat(c).splice(0,f).concat(c));a.shift=0;if(b.length)for(a=c.length;b.length<a;)d=[].concat(b).splice(b.length-f,f),e&&(d[f-6]=d[f-2],d[f-5]=d[f-1]),b=b.concat(d);h&&(b=b.concat(h),c=c.concat(i));return[b,c]},step:function(a,b,c,d){var e=[],f=a.length;if(c===1)e=d;else if(f===b.length&&c<1)for(;f--;)d=
+parseFloat(a[f]),e[f]=isNaN(d)?a[f]:c*parseFloat(b[f]-d)+d;else e=b;return e}};w&&w.init&&w.init(La);if(!w&&ca.jQuery){var ba=jQuery,Hc=ba.getScript,o=function(a,b){for(var c=0,d=a.length;c<d;c++)if(b.call(a[c],a[c],c,a)===!1)return c},nc=ba.grep,Vb=function(a,b){for(var c=[],d=0,e=a.length;d<e;d++)c[d]=b.call(a[d],a[d],d,a);return c},K=function(){var a=arguments;return ba.extend(!0,null,a[0],a[1],a[2],a[3])},Ac=function(a){return ba(a).offset()},ha=function(a,b,c){ba(a).bind(b,c)},Qa=function(a,
+b,c){var d=x.removeEventListener?"removeEventListener":"detachEvent";x[d]&&!a[d]&&(a[d]=function(){});ba(a).unbind(b,c)},aa=function(a,b,c,d){var e=ba.Event(b),f="detached"+b,g;L(e,c);a[b]&&(a[f]=a[b],a[b]=null);o(["preventDefault","stopPropagation"],function(a){var b=e[a];e[a]=function(){try{b.call(e)}catch(c){a==="preventDefault"&&(g=!0)}}});ba(a).trigger(e);a[f]&&(a[b]=a[f],a[f]=null);d&&!e.isDefaultPrevented()&&!g&&d(e)},cc=function(a,b,c){var d=ba(a);if(b.d)a.toD=b.d,b.d=1;d.stop();d.animate(b,
+c)},Ob=function(a){ba(a).stop()};ba.extend(ba.easing,{easeOutQuad:function(a,b,c,d,e){return-d*(b/=e)*(b-2)+c}});var Ic=jQuery.fx,Jc=Ic.step;o(["cur","_default","width","height"],function(a,b){var c=b?Jc:Ic.prototype,d=c[a],e;d&&(c[a]=function(a){a=b?a:this;e=a.elem;return e.attr?e.attr(a.prop,a.now):d.apply(this,arguments)})});Jc.d=function(a){var b=a.elem;if(!a.started){var c=La.init(b,b.d,b.toD);a.start=c[0];a.end=c[1];a.started=!0}b.attr("d",La.step(a.start,a.end,a.pos,b.toD))}}w={enabled:!0,
+align:"center",x:0,y:15,style:{color:"#666",fontSize:"11px",lineHeight:"14px"}};Ea={colors:"#4572A7,#AA4643,#89A54E,#80699B,#3D96AE,#DB843D,#92A8CD,#A47D7C,#B5CA92".split(","),symbols:["circle","diamond","square","triangle","triangle-down"],lang:{loading:"Loading...",months:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),shortMonths:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),weekdays:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),
+decimalPoint:".",resetZoom:"Reset zoom",resetZoomTitle:"Reset zoom level 1:1",thousandsSep:","},global:{useUTC:!0,canvasToolsURL:"http://code.highcharts.com/2.2.1/modules/canvas-tools.js"},chart:{borderColor:"#4572A7",borderRadius:5,defaultSeriesType:"line",ignoreHiddenSeries:!0,spacingTop:10,spacingRight:10,spacingBottom:15,spacingLeft:10,style:{fontFamily:'"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif',fontSize:"12px"},backgroundColor:"#FFFFFF",plotBorderColor:"#C0C0C0",
+resetZoomButton:{theme:{zIndex:20},position:{align:"right",x:-10,y:10}}},title:{text:"Chart title",align:"center",y:15,style:{color:"#3E576F",fontSize:"16px"}},subtitle:{text:"",align:"center",y:30,style:{color:"#6D869F"}},plotOptions:{line:{allowPointSelect:!1,showCheckbox:!1,animation:{duration:1E3},events:{},lineWidth:2,shadow:!0,marker:{enabled:!0,lineWidth:0,radius:4,lineColor:"#FFFFFF",states:{hover:{},select:{fillColor:"#FFFFFF",lineColor:"#000000",lineWidth:2}}},point:{events:{}},dataLabels:K(w,
+{enabled:!1,y:-6,formatter:function(){return this.y}}),cropThreshold:300,pointRange:0,showInLegend:!0,states:{hover:{marker:{}},select:{marker:{}}},stickyTracking:!0}},labels:{style:{position:wb,color:"#3E576F"}},legend:{enabled:!0,align:"center",layout:"horizontal",labelFormatter:function(){return this.name},borderWidth:1,borderColor:"#909090",borderRadius:5,shadow:!1,style:{padding:"5px"},itemStyle:{cursor:"pointer",color:"#3E576F"},itemHoverStyle:{color:"#000000"},itemHiddenStyle:{color:"#C0C0C0"},
+itemCheckboxStyle:{position:wb,width:"13px",height:"13px"},symbolWidth:16,symbolPadding:5,verticalAlign:"bottom",x:0,y:0},loading:{labelStyle:{fontWeight:"bold",position:mc,top:"1em"},style:{position:wb,backgroundColor:"white",opacity:0.5,textAlign:"center"}},tooltip:{enabled:!0,backgroundColor:"rgba(255, 255, 255, .85)",borderWidth:2,borderRadius:5,headerFormat:'<span style="font-size: 10px">{point.key}</span><br/>',pointFormat:'<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>',
+shadow:!0,shared:Fa,snap:Ga?25:10,style:{color:"#333333",fontSize:"12px",padding:"5px",whiteSpace:"nowrap"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer",color:"#909090",fontSize:"10px"}}};var $b={dateTimeLabelFormats:za(Rb,"%H:%M:%S.%L",ob,"%H:%M:%S",pb,"%H:%M",va,"%H:%M",Ca,"%e. %b",Ya,"%e. %b",Da,"%b '%y",Za,"%Y"),endOnTick:!1,gridLineColor:"#C0C0C0",labels:w,lineColor:"#C0D0E0",lineWidth:1,
+max:null,min:null,minPadding:0.01,maxPadding:0.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,minorTickColor:"#A0A0A0",minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:5,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",tickWidth:1,title:{align:"middle",style:{color:"#6D869F",fontWeight:"bold"}},type:"linear"},kc=K($b,{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{align:"right",x:-8,
+y:3},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,tickWidth:0,title:{rotation:270,text:"Y-values"},stackLabels:{enabled:!1,formatter:function(){return this.total},style:w.style}}),Pc={labels:{align:"right",x:-8,y:null},title:{rotation:270}},Oc={labels:{align:"left",x:8,y:null},title:{rotation:90}},zc={labels:{align:"center",x:0,y:14,overflow:"justify"},title:{rotation:0}},Nc=K(zc,{labels:{y:-5,overflow:"justify"}}),M=Ea.plotOptions,w=M.line;M.spline=K(w);M.scatter=K(w,{lineWidth:0,states:{hover:{lineWidth:0}},
+tooltip:{headerFormat:'<span style="font-size: 10px; color:{series.color}">{series.name}</span><br/>',pointFormat:"x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>"}});M.area=K(w,{threshold:0});M.areaspline=K(M.area);M.column=K(w,{borderColor:"#FFFFFF",borderWidth:1,borderRadius:0,groupPadding:0.2,marker:null,pointPadding:0.1,minPointLength:0,cropThreshold:50,pointRange:null,states:{hover:{brightness:0.1,shadow:!1},select:{color:"#C0C0C0",borderColor:"#000000",shadow:!1}},dataLabels:{y:null,verticalAlign:null},
+threshold:0});M.bar=K(M.column,{dataLabels:{align:"left",x:5,y:null,verticalAlign:"middle"}});M.pie=K(w,{borderColor:"#FFFFFF",borderWidth:1,center:["50%","50%"],colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.point.name},y:5},legendType:"point",marker:null,size:"75%",showInLegend:!1,slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}}});vc();var ma=function(a){var b=[],c;(function(a){(c=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(a))?
+b=[S(c[1]),S(c[2]),S(c[3]),parseFloat(c[4],10)]:(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))&&(b=[S(c[1],16),S(c[2],16),S(c[3],16),1])})(a);return{get:function(c){return b&&!isNaN(b[0])?c==="rgb"?"rgb("+b[0]+","+b[1]+","+b[2]+")":c==="a"?b[3]:"rgba("+b.join(",")+")":a},brighten:function(a){if(Bb(a)&&a!==0){var c;for(c=0;c<3;c++)b[c]+=S(a*255),b[c]<0&&(b[c]=0),b[c]>255&&(b[c]=255)}return this},setOpacity:function(a){b[3]=a;return this}}};Sa.prototype={init:function(a,b){this.element=
+b==="span"?Aa(b):x.createElementNS("http://www.w3.org/2000/svg",b);this.renderer=a;this.attrSetters={}},animate:function(a,b,c){b=p(b,Ub,!0);Ob(this);if(b){b=K(b);if(c)b.complete=c;cc(this,a,b)}else this.attr(a),c&&c()},attr:function(a,b){var c,d,e,f,g=this.element,h=g.nodeName,i=this.renderer,k,j=this.attrSetters,l=this.shadows,n,o=this;Ab(a)&&s(b)&&(c=a,a={},a[c]=b);if(Ab(a))c=a,h==="circle"?c={x:"cx",y:"cy"}[c]||c:c==="strokeWidth"&&(c="stroke-width"),o=A(g,c)||this[c]||0,c!=="d"&&c!=="visibility"&&
+(o=parseFloat(o));else for(c in a)if(k=!1,d=a[c],e=j[c]&&j[c](d,c),e!==!1){e!==X&&(d=e);if(c==="d")d&&d.join&&(d=d.join(" ")),/(NaN| {2}|^$)/.test(d)&&(d="M 0 0"),this.d=d;else if(c==="x"&&h==="text"){for(e=0;e<g.childNodes.length;e++)f=g.childNodes[e],A(f,"x")===A(g,"x")&&A(f,"x",d);this.rotation&&A(g,"transform","rotate("+this.rotation+" "+d+" "+S(a.y||A(g,"y"))+")")}else if(c==="fill")d=i.color(d,g,c);else if(h==="circle"&&(c==="x"||c==="y"))c={x:"cx",y:"cy"}[c]||c;else if(h==="rect"&&c==="r")A(g,
+{rx:d,ry:d}),k=!0;else if(c==="translateX"||c==="translateY"||c==="rotation"||c==="verticalAlign")this[c]=d,this.updateTransform(),k=!0;else if(c==="stroke")d=i.color(d,g,c);else if(c==="dashstyle")if(c="stroke-dasharray",d=d&&d.toLowerCase(),d==="solid")d=Ma;else{if(d){d=d.replace("shortdashdotdot","3,1,1,1,1,1,").replace("shortdashdot","3,1,1,1").replace("shortdot","1,1,").replace("shortdash","3,1,").replace("longdash","8,3,").replace(/dot/g,"1,3,").replace("dash","4,3,").replace(/,$/,"").split(",");
+for(e=d.length;e--;)d[e]=S(d[e])*a["stroke-width"];d=d.join(",")}}else c==="isTracker"?this[c]=d:c==="width"?d=S(d):c==="align"?(c="text-anchor",d={left:"start",center:"middle",right:"end"}[d]):c==="title"&&(e=x.createElementNS("http://www.w3.org/2000/svg","title"),e.appendChild(x.createTextNode(d)),g.appendChild(e));c==="strokeWidth"&&(c="stroke-width");Ec&&c==="stroke-width"&&d===0&&(d=1.0E-6);this.symbolName&&/^(x|y|r|start|end|innerR|anchorX|anchorY)/.test(c)&&(n||(this.symbolAttr(a),n=!0),k=
+!0);if(l&&/^(width|height|visibility|x|y|d|transform)$/.test(c))for(e=l.length;e--;)A(l[e],c,d);if((c==="width"||c==="height")&&h==="rect"&&d<0)d=0;c==="text"?(this.textStr=d,this.added&&i.buildText(this)):k||A(g,c,d)}if(Ec&&/Chrome\/(18|19)/.test(kb)&&h==="text"&&(a.x!==X||a.y!==X))c=g.parentNode,d=g.nextSibling,c&&(c.removeChild(g),d?c.insertBefore(g,d):c.appendChild(g));return o},symbolAttr:function(a){var b=this;o("x,y,r,start,end,width,height,innerR,anchorX,anchorY".split(","),function(c){b[c]=
+p(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,b.y,b.width,b.height,b)})},clip:function(a){return this.attr("clip-path","url("+this.renderer.url+"#"+a.id+")")},crisp:function(a,b,c,d,e){var f,g={},h={},i,a=a||this.strokeWidth||this.attr&&this.attr("stroke-width")||0;i=z(a)%2/2;h.x=Ta(b||this.x||0)+i;h.y=Ta(c||this.y||0)+i;h.width=Ta((d||this.width||0)-2*i);h.height=Ta((e||this.height||0)-2*i);h.strokeWidth=a;for(f in h)this[f]!==h[f]&&(this[f]=g[f]=h[f]);return g},css:function(a){var b=
+this.element,b=a&&a.width&&b.nodeName==="text",c,d="",e=function(a,b){return"-"+b.toLowerCase()};if(a&&a.color)a.fill=a.color;this.styles=a=L(this.styles,a);if(Qb&&!Pb)b&&delete a.width,P(this.element,a);else{for(c in a)d+=c.replace(/([A-Z])/g,e)+":"+a[c]+";";this.attr({style:d})}b&&this.added&&this.renderer.buildText(this);return this},on:function(a,b){var c=b;Ga&&a==="click"&&(a="touchstart",c=function(a){a.preventDefault();b()});this.element["on"+a]=c;return this},translate:function(a,b){return this.attr({translateX:a,
+translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},htmlCss:function(a){var b=this.element;if(b=a&&b.tagName==="SPAN"&&a.width)delete a.width,this.textWidth=b,this.updateTransform();this.styles=L(this.styles,a);P(this.element,a);return this},htmlGetBBox:function(a){var b=this.element,c=this.bBox;if(!c||a){if(b.nodeName==="text")b.style.position=wb;c=this.bBox={x:b.offsetLeft,y:b.offsetTop,width:b.offsetWidth,height:b.offsetHeight}}return c},htmlUpdateTransform:function(){if(this.added){var a=
+this.renderer,b=this.element,c=this.translateX||0,d=this.translateY||0,e=this.x||0,f=this.y||0,g=this.textAlign||"left",h={left:0,center:0.5,right:1}[g],i=g&&g!=="left",k=this.shadows;if(c||d)P(b,{marginLeft:c,marginTop:d}),k&&o(k,function(a){P(a,{marginLeft:c+1,marginTop:d+1})});this.inverted&&o(b.childNodes,function(c){a.invertChild(c,b)});if(b.tagName==="SPAN"){var j,l,k=this.rotation,n;j=0;var t=1,r=0,Z;n=S(this.textWidth);var V=this.xCorr||0,v=this.yCorr||0,y=[k,g,b.innerHTML,this.textWidth].join(",");
+if(y!==this.cTT)s(k)&&(j=k*Dc,t=ja(j),r=C(j),P(b,{filter:k?["progid:DXImageTransform.Microsoft.Matrix(M11=",t,", M12=",-r,", M21=",r,", M22=",t,", sizingMethod='auto expand')"].join(""):Ma})),j=p(this.elemWidth,b.offsetWidth),l=p(this.elemHeight,b.offsetHeight),j>n&&(P(b,{width:n+ga,display:"block",whiteSpace:"normal"}),j=n),n=a.fontMetrics(b.style.fontSize).b,V=t<0&&-j,v=r<0&&-l,Z=t*r<0,V+=r*n*(Z?1-h:h),v-=t*n*(k?Z?h:1-h:1),i&&(V-=j*h*(t<0?-1:1),k&&(v-=l*h*(r<0?-1:1)),P(b,{textAlign:g})),this.xCorr=
+V,this.yCorr=v;P(b,{left:e+V+ga,top:f+v+ga});this.cTT=y}}else this.alignOnAdd=!0},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=this.inverted,d=this.rotation,e=[];c&&(a+=this.attr("width"),b+=this.attr("height"));(a||b)&&e.push("translate("+a+","+b+")");c?e.push("rotate(90) scale(-1,1)"):d&&e.push("rotate("+d+" "+this.x+" "+this.y+")");e.length&&A(this.element,"transform",e.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,
+b,c){a?(this.alignOptions=a,this.alignByTranslate=b,c||this.renderer.alignedObjects.push(this)):(a=this.alignOptions,b=this.alignByTranslate);var c=p(c,this.renderer),d=a.align,e=a.verticalAlign,f=(c.x||0)+(a.x||0),g=(c.y||0)+(a.y||0),h={};/^(right|center)$/.test(d)&&(f+=(c.width-(a.width||0))/{right:1,center:2}[d]);h[b?"translateX":"x"]=z(f);/^(bottom|middle)$/.test(e)&&(g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1));h[b?"translateY":"y"]=z(g);this[this.placed?"animate":"attr"](h);this.placed=
+!0;this.alignAttr=h;return this},getBBox:function(a){var b,c,d=this.rotation;c=this.element;var e=d*Dc;if(c.namespaceURI==="http://www.w3.org/2000/svg"){try{b=c.getBBox?L({},c.getBBox()):{width:c.offsetWidth,height:c.offsetHeight}}catch(f){}if(!b||b.width<0)b={width:0,height:0};a=b.width;c=b.height;if(d)b.width=Ba(c*C(e))+Ba(a*ja(e)),b.height=Ba(c*ja(e))+Ba(a*C(e))}else b=this.htmlGetBBox(a);return b},show:function(){return this.attr({visibility:eb})},hide:function(){return this.attr({visibility:ab})},
+add:function(a){var b=this.renderer,c=a||b,d=c.element||b.box,e=d.childNodes,f=this.element,g=A(f,"zIndex"),h;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);if(g)c.handleZ=!0,g=S(g);if(c.handleZ)for(c=0;c<e.length;c++)if(a=e[c],b=A(a,"zIndex"),a!==f&&(S(b)>g||!s(g)&&s(b))){d.insertBefore(f,a);h=!0;break}h||d.appendChild(f);this.added=!0;aa(this,"add");return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||
+{},c=a.shadows,d=a.box,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=null;Ob(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f=0;f<a.stops.length;f++)a.stops[f]=a.stops[f].destroy();a.stops=null}a.safeRemoveChild(b);c&&o(c,function(b){a.safeRemoveChild(b)});d&&d.destroy();Gb(a.renderer.alignedObjects,a);for(e in a)delete a[e];return null},empty:function(){for(var a=this.element,b=a.childNodes,c=b.length;c--;)a.removeChild(b[c])},shadow:function(a,b){var c=[],d,e,f=this.element,
+g=this.parentInverted?"(-1,-1)":"(1,1)";if(a){for(d=1;d<=3;d++)e=f.cloneNode(0),A(e,{isShadow:"true",stroke:"rgb(0, 0, 0)","stroke-opacity":0.05*d,"stroke-width":7-2*d,transform:"translate"+g,fill:Ma}),b?b.element.appendChild(e):f.parentNode.insertBefore(e,f),c.push(e);this.shadows=c}return this}};var Eb=function(){this.init.apply(this,arguments)};Eb.prototype={Element:Sa,init:function(a,b,c,d){var e=location,f;f=this.createElement("svg").attr({xmlns:"http://www.w3.org/2000/svg",version:"1.1"});a.appendChild(f.element);
+this.isSVG=!0;this.box=f.element;this.boxWrapper=f;this.alignedObjects=[];this.url=Qb?"":e.href.replace(/#.*?$/,"").replace(/([\('\)])/g,"\\$1");this.defs=this.createElement("defs").add();this.forExport=d;this.gradients={};this.setSize(b,c,!1)},destroy:function(){var a=this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy();Jb(this.gradients||{});this.gradients=null;if(a)this.defs=a.destroy();return this.alignedObjects=null},createElement:function(a){var b=new this.Element;b.init(this,a);
+return b},draw:function(){},buildText:function(a){for(var b=a.element,c=p(a.textStr,"").toString().replace(/<(b|strong)>/g,'<span style="font-weight:bold">').replace(/<(i|em)>/g,'<span style="font-style:italic">').replace(/<a/g,"<span").replace(/<\/(b|strong|i|em|a)>/g,"</span>").split(/<br.*?>/g),d=b.childNodes,e=/style="([^"]+)"/,f=/href="([^"]+)"/,g=A(b,"x"),h=a.styles,i=h&&S(h.width),k=h&&h.lineHeight,j,h=d.length;h--;)b.removeChild(d[h]);i&&!a.added&&this.box.appendChild(b);c[c.length-1]===""&&
+c.pop();o(c,function(c,d){var h,r=0,p,c=c.replace(/<span/g,"|||<span").replace(/<\/span>/g,"</span>|||");h=c.split("|||");o(h,function(c){if(c!==""||h.length===1){var l={},o=x.createElementNS("http://www.w3.org/2000/svg","tspan");e.test(c)&&A(o,"style",c.match(e)[1].replace(/(;| |^)color([ :])/,"$1fill$2"));f.test(c)&&(A(o,"onclick",'location.href="'+c.match(f)[1]+'"'),P(o,{cursor:"pointer"}));c=(c.replace(/<(.|\n)*?>/g,"")||" ").replace(/&lt;/g,"<").replace(/&gt;/g,">");o.appendChild(x.createTextNode(c));
+r?l.dx=3:l.x=g;if(!r){if(d){!Pb&&a.renderer.forExport&&P(o,{display:"block"});p=ca.getComputedStyle&&S(ca.getComputedStyle(j,null).getPropertyValue("line-height"));if(!p||isNaN(p))p=k||j.offsetHeight||18;A(o,"dy",p)}j=o}A(o,l);b.appendChild(o);r++;if(i)for(var c=c.replace(/-/g,"- ").split(" "),q,T=[];c.length||T.length;)q=a.getBBox().width,l=q>i,!l||c.length===1?(c=T,T=[],c.length&&(o=x.createElementNS("http://www.w3.org/2000/svg","tspan"),A(o,{dy:k||16,x:g}),b.appendChild(o),q>i&&(i=q))):(o.removeChild(o.firstChild),
+T.unshift(c.pop())),c.length&&o.appendChild(x.createTextNode(c.join(" ").replace(/- /g,"-")))}})})},button:function(a,b,c,d,e,f,g){var h=this.label(a,b,c),i=0,k,j,l,n,o,a={x1:0,y1:0,x2:0,y2:1},e=K(za("stroke-width",1,"stroke","#999","fill",za("linearGradient",a,"stops",[[0,"#FFF"],[1,"#DDD"]]),"r",3,"padding",3,"style",za("color","black")),e);l=e.style;delete e.style;f=K(e,za("stroke","#68A","fill",za("linearGradient",a,"stops",[[0,"#FFF"],[1,"#ACF"]])),f);n=f.style;delete f.style;g=K(e,za("stroke",
+"#68A","fill",za("linearGradient",a,"stops",[[0,"#9BD"],[1,"#CDF"]])),g);o=g.style;delete g.style;ha(h.element,"mouseenter",function(){h.attr(f).css(n)});ha(h.element,"mouseleave",function(){k=[e,f,g][i];j=[l,n,o][i];h.attr(k).css(j)});h.setState=function(a){(i=a)?a===2&&h.attr(g).css(o):h.attr(e).css(l)};return h.on("click",function(){d.call(h)}).attr(e).css(L({cursor:"default"},l))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=z(a[1])+b%2/2);a[2]===a[5]&&(a[2]=a[5]=z(a[2])+b%2/2);return a},path:function(a){return this.createElement("path").attr({d:a,
+fill:Ma})},circle:function(a,b,c){a=mb(a)?a:{x:a,y:b,r:c};return this.createElement("circle").attr(a)},arc:function(a,b,c,d,e,f){if(mb(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;return this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0})},rect:function(a,b,c,d,e,f){if(mb(a))b=a.y,c=a.width,d=a.height,e=a.r,f=a.strokeWidth,a=a.x;e=this.createElement("rect").attr({rx:e,ry:e,fill:Ma});return e.attr(e.crisp(f,a,b,W(c,0),W(d,0)))},setSize:function(a,b,c){var d=this.alignedObjects,
+e=d.length;this.width=a;this.height=b;for(this.boxWrapper[p(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return s(a)?b.attr({"class":Ia+a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:Ma};arguments.length>1&&L(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,
+b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(z(b),z(c),d,e,f),i=/^url\((.*?)\)$/,k;if(h)g=this.path(h),L(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&L(g,f);else if(i.test(a)){var j=function(a,b){a.attr({width:b[0],height:b[1]}).translate(-z(b[0]/2),-z(b[1]/2))};k=a.match(i)[1];a=Fc[k];g=this.image(k).attr({x:b,y:c});a?j(g,a):(g.attr({width:0,height:0}),Aa("img",{onload:function(){j(g,Fc[k]=[this.width,this.height])},src:k}))}return g},symbols:{circle:function(a,b,c,d){var e=0.166*c;return[wa,a+c/2,
+b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return[wa,a,b,fa,a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return[wa,a+c/2,b,fa,a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return[wa,a,b,fa,a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return[wa,a+c/2,b,fa,a+c,b+d/2,a+c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start,c=e.r||c||d,g=e.end-1.0E-6,d=e.innerR,h=ja(f),i=C(f),k=ja(g),g=C(g),e=e.end-f<da?0:1;return[wa,a+c*h,b+c*i,
+"A",c,c,0,e,1,a+c*k,b+c*g,fa,a+d*k,b+d*g,"A",d,d,0,e,0,a+d*h,b+d*i,"Z"]}},clipRect:function(a,b,c,d){var e=Ia+lc++,f=this.createElement("clipPath").attr({id:e}).add(this.defs),a=this.rect(a,b,c,d,0).add(f);a.id=e;a.clipPath=f;return a},color:function(a,b,c){var d,e=/^rgba/;if(a&&a.linearGradient){var f=this,g=a.linearGradient,b=!Fb(g),c=f.gradients,h,i=g.x1||g[0]||0,k=g.y1||g[1]||0,j=g.x2||g[2]||0,l=g.y2||g[3]||0,n,t,r=[b,i,k,j,l,a.stops.join(",")].join(",");c[r]?g=A(c[r].element,"id"):(g=Ia+lc++,
+h=f.createElement("linearGradient").attr(L({id:g,x1:i,y1:k,x2:j,y2:l},b?null:{gradientUnits:"userSpaceOnUse"})).add(f.defs),h.stops=[],o(a.stops,function(a){e.test(a[1])?(d=ma(a[1]),n=d.get("rgb"),t=d.get("a")):(n=a[1],t=1);a=f.createElement("stop").attr({offset:a[0],"stop-color":n,"stop-opacity":t}).add(h);h.stops.push(a)}),c[r]=h);return"url("+this.url+"#"+g+")"}else return e.test(a)?(d=ma(a),A(b,c+"-opacity",d.get("a")),d.get("rgb")):(b.removeAttribute(c+"-opacity"),a)},text:function(a,b,c,d){var e=
+Ea.chart.style;if(d&&!this.forExport)return this.html(a,b,c);b=z(p(b,0));c=z(p(c,0));a=this.createElement("text").attr({x:b,y:c,text:a}).css({fontFamily:e.fontFamily,fontSize:e.fontSize});a.x=b;a.y=c;return a},html:function(a,b,c){var d=Ea.chart.style,e=this.createElement("span"),f=e.attrSetters,g=e.element,h=e.renderer;f.text=function(a){g.innerHTML=a;return!1};f.x=f.y=f.align=function(a,b){b==="align"&&(b="textAlign");e[b]=a;e.htmlUpdateTransform();return!1};e.attr({text:a,x:z(b),y:z(c)}).css({position:wb,
+whiteSpace:"nowrap",fontFamily:d.fontFamily,fontSize:d.fontSize});e.css=e.htmlCss;if(h.isSVG)e.add=function(a){var b,c,d=h.box.parentNode;if(a){if(b=a.div,!b)b=a.div=Aa(Ra,{className:A(a.element,"class")},{position:wb,left:a.attr("translateX")+ga,top:a.attr("translateY")+ga},d),c=b.style,L(a.attrSetters,{translateX:function(a){c.left=a+ga},translateY:function(a){c.top=a+ga},visibility:function(a,b){c[b]=a}})}else b=d;b.appendChild(g);e.added=!0;e.alignOnAdd&&e.htmlUpdateTransform();return e};return e},
+fontMetrics:function(a){var a=S(a||11),a=a<24?a+4:z(a*1.2),b=z(a*0.8);return{h:a,b:b}},label:function(a,b,c,d,e,f,g,h){function i(){var a=n.styles,a=a&&a.textAlign,b=v,c;c=h?0:ia;if(s(y)&&(a==="center"||a==="right"))b+={center:0.5,right:1}[a]*(y-p.width);(b!==t.x||c!==t.y)&&t.attr({x:b,y:c});t.x=b;t.y=c}function k(a,b){r?r.attr(a,b):x[a]=b}function j(){n.attr({text:a,x:b,y:c,anchorX:e,anchorY:f})}var l=this,n=l.g(),t=l.text("",0,0,g).attr({zIndex:1}).add(n),r,p,V="left",v=3,y,q,T,A,w=0,x={},ia,g=
+n.attrSetters;ha(n,"add",j);g.width=function(a){y=a;return!1};g.height=function(a){q=a;return!1};g.padding=function(a){s(a)&&a!==v&&(v=a,i());return!1};g.align=function(a){V=a;return!1};g.text=function(a,b){t.attr(b,a);var c;c=t.element.style;p=(y===void 0||q===void 0||n.styles.textAlign)&&t.getBBox(!0);n.width=(y||p.width)+2*v;n.height=(q||p.height)+2*v;ia=v+l.fontMetrics(c&&c.fontSize).b;if(!r)c=h?-ia:0,n.box=r=d?l.symbol(d,0,c,n.width,n.height):l.rect(0,c,n.width,n.height,0,x["stroke-width"]),
+r.add(n);r.attr(K({width:n.width,height:n.height},x));x=null;i();return!1};g["stroke-width"]=function(a,b){w=a%2/2;k(b,a);return!1};g.stroke=g.fill=g.r=function(a,b){k(b,a);return!1};g.anchorX=function(a,b){e=a;k(b,a+w-T);return!1};g.anchorY=function(a,b){f=a;k(b,a-A);return!1};g.x=function(a){a-={left:0,center:0.5,right:1}[V]*((y||p.width)+v);T=n.x=z(a);n.attr("translateX",T);return!1};g.y=function(a){A=n.y=z(a);n.attr("translateY",a);return!1};var ua=n.css;return L(n,{css:function(a){if(a){var b=
+{},a=K({},a);o("fontSize,fontWeight,fontFamily,color,lineHeight,width".split(","),function(c){a[c]!==X&&(b[c]=a[c],delete a[c])});t.css(b)}return ua.call(n,a)},getBBox:function(){return r.getBBox()},shadow:function(a){r.shadow(a);return n},destroy:function(){Qa(n,"add",j);Qa(n.element,"mouseenter");Qa(n.element,"mouseleave");t&&(t=t.destroy());Sa.prototype.destroy.call(n)}})}};Xb=Eb;var $a;if(!Pb&&!Fa)$a={init:function(a,b){var c=["<",b,' filled="f" stroked="f"'],d=["position: ",wb,";"];(b==="shape"||
+b===Ra)&&d.push("left:0;top:0;width:10px;height:10px;");Xa&&d.push("visibility: ",b===Ra?ab:eb);c.push(' style="',d.join(""),'"/>');if(b)c=b===Ra||b==="span"||b==="img"?c.join(""):a.prepVML(c),this.element=Aa(c);this.renderer=a;this.attrSetters={}},add:function(a){var b=this.renderer,c=this.element,d=b.box,d=a?a.element||a:d;a&&a.inverted&&b.invertChild(c,d);Xa&&d.gVis===ab&&P(c,{visibility:ab});d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();aa(this,
+"add");return this},toggleChildren:function(a,b){for(var c=a.childNodes,d=c.length;d--;)P(c[d],{visibility:b}),c[d].nodeName==="DIV"&&this.toggleChildren(c[d],b)},updateTransform:Sa.prototype.htmlUpdateTransform,attr:function(a,b){var c,d,e,f=this.element||{},g=f.style,h=f.nodeName,i=this.renderer,k=this.symbolName,j,l=this.shadows,n,o=this.attrSetters,r=this;Ab(a)&&s(b)&&(c=a,a={},a[c]=b);if(Ab(a))c=a,r=c==="strokeWidth"||c==="stroke-width"?this.strokeweight:this[c];else for(c in a)if(d=a[c],n=!1,
+e=o[c]&&o[c](d,c),e!==!1&&d!==null){e!==X&&(d=e);if(k&&/^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(c))j||(this.symbolAttr(a),j=!0),n=!0;else if(c==="d"){d=d||[];this.d=d.join(" ");e=d.length;for(n=[];e--;)n[e]=Bb(d[e])?z(d[e]*10)-5:d[e]==="Z"?"x":d[e];d=n.join(" ")||"x";f.path=d;if(l)for(e=l.length;e--;)l[e].path=d;n=!0}else if(c==="zIndex"||c==="visibility"){if(Xa&&c==="visibility"&&h==="DIV")f.gVis=d,this.toggleChildren(f,d),d===eb&&(d=null);d&&(g[c]=d);n=!0}else if(c==="width"||
+c==="height")d=W(0,d),this[c]=d,this.updateClipping?(this[c]=d,this.updateClipping()):g[c]=d,n=!0;else if(c==="x"||c==="y")this[c]=d,g[{x:"left",y:"top"}[c]]=d;else if(c==="class")f.className=d;else if(c==="stroke")d=i.color(d,f,c),c="strokecolor";else if(c==="stroke-width"||c==="strokeWidth")f.stroked=d?!0:!1,c="strokeweight",this[c]=d,Bb(d)&&(d+=ga);else if(c==="dashstyle")(f.getElementsByTagName("stroke")[0]||Aa(i.prepVML(["<stroke/>"]),null,null,f))[c]=d||"solid",this.dashstyle=d,n=!0;else if(c===
+"fill")h==="SPAN"?g.color=d:(f.filled=d!==Ma?!0:!1,d=i.color(d,f,c),c="fillcolor");else if(c==="translateX"||c==="translateY"||c==="rotation")this[c]=d,this.updateTransform(),n=!0;else if(c==="text")this.bBox=null,f.innerHTML=d,n=!0;if(l&&c==="visibility")for(e=l.length;e--;)l[e].style[c]=d;n||(Xa?f[c]=d:A(f,c,d))}return r},clip:function(a){var b=this,c=a.members;c.push(b);b.destroyClip=function(){Gb(c,b)};return b.css(a.getCSS(b.inverted))},css:Sa.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&
+Tb(a)},destroy:function(){this.destroyClip&&this.destroyClip();return Sa.prototype.destroy.apply(this)},empty:function(){for(var a=this.element.childNodes,b=a.length,c;b--;)c=a[b],c.parentNode.removeChild(c)},on:function(a,b){this.element["on"+a]=function(){var a=ca.event;a.target=a.srcElement;b(a)};return this},shadow:function(a,b){var c=[],d,e=this.element,f=this.renderer,g,h=e.style,i,k=e.path;k&&typeof k.value!=="string"&&(k="x");if(a){for(d=1;d<=3;d++)i=['<shape isShadow="true" strokeweight="',
+7-2*d,'" filled="false" path="',k,'" coordsize="100,100" style="',e.style.cssText,'" />'],g=Aa(f.prepVML(i),null,{left:S(h.left)+1,top:S(h.top)+1}),i=['<stroke color="black" opacity="',0.05*d,'"/>'],Aa(f.prepVML(i),null,null,g),b?b.element.appendChild(g):e.parentNode.insertBefore(g,e),c.push(g);this.shadows=c}return this}},$a=pa(Sa,$a),w={Element:$a,isIE8:kb.indexOf("MSIE 8.0")>-1,init:function(a,b,c){var d,e;this.alignedObjects=[];d=this.createElement(Ra);e=d.element;e.style.position=mc;a.appendChild(d.element);
+this.box=e;this.boxWrapper=d;this.setSize(b,c,!1);if(!x.namespaces.hcv)x.namespaces.add("hcv","urn:schemas-microsoft-com:vml"),x.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "},clipRect:function(a,b,c,d){var e=this.createElement();return L(e,{members:[],left:a,top:b,width:c,height:d,getCSS:function(a){var b=this.top,c=this.left,d=c+this.width,e=b+this.height,b={clip:"rect("+z(a?c:b)+"px,"+z(a?e:d)+"px,"+z(a?d:e)+
+"px,"+z(a?b:c)+"px)"};!a&&Xa&&L(b,{width:d+ga,height:e+ga});return b},updateClipping:function(){o(e.members,function(a){a.css(e.getCSS(a.inverted))})}})},color:function(a,b,c){var d,e=/^rgba/;if(a&&a.linearGradient){var f,g,h=a.linearGradient,i=h.x1||h[0]||0,k=h.y1||h[1]||0,j=h.x2||h[2]||0,h=h.y2||h[3]||0,l,n,p,r;o(a.stops,function(a,b){e.test(a[1])?(d=ma(a[1]),f=d.get("rgb"),g=d.get("a")):(f=a[1],g=1);b?(p=f,r=g):(l=f,n=g)});if(c==="fill")a=90-oa.atan((h-k)/(j-i))*180/da,a=['<fill colors="0% ',l,
+",100% ",p,'" angle="',a,'" opacity="',r,'" o:opacity2="',n,'" type="gradient" focus="100%" method="sigma" />'],Aa(this.prepVML(a),null,null,b);else return f}else if(e.test(a)&&b.tagName!=="IMG")return d=ma(a),a=["<",c,' opacity="',d.get("a"),'"/>'],Aa(this.prepVML(a),null,null,b),d.get("rgb");else{b=b.getElementsByTagName(c);if(b.length)b[0].opacity=1;return a}},prepVML:function(a){var b=this.isIE8,a=a.join("");b?(a=a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />'),a=a.indexOf('style="')===
+-1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')):a=a.replace("<","<hcv:");return a},text:Eb.prototype.html,path:function(a){return this.createElement("shape").attr({coordsize:"100 100",d:a})},circle:function(a,b,c){return this.symbol("circle").attr({x:a-c,y:b-c,width:2*c,height:2*c})},g:function(a){var b;a&&(b={className:Ia+a,"class":Ia+a});return this.createElement(Ra).attr(b)},image:function(a,
+b,c,d,e){var f=this.createElement("img").attr({src:a});arguments.length>1&&f.css({left:b,top:c,width:d,height:e});return f},rect:function(a,b,c,d,e,f){if(mb(a))b=a.y,c=a.width,d=a.height,f=a.strokeWidth,a=a.x;var g=this.symbol("rect");g.r=e;return g.attr(g.crisp(f,a,b,W(c,0),W(d,0)))},invertChild:function(a,b){var c=b.style;P(a,{flip:"x",left:S(c.width)-10,top:S(c.height)-10,rotation:-90})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,c=e.r||c||d,d=ja(f),h=C(f),i=ja(g),k=C(g),e=e.innerR,
+j=0.08/c,l=e&&0.25/e||0;if(g-f===0)return["x"];else 2*da-g+f<j?i=-j:g-f<l&&(i=ja(f+l));return["wa",a-c,b-c,a+c,b+c,a+c*d,b+c*h,a+c*i,b+c*k,"at",a-e,b-e,a+e,b+e,a+e*i,b+e*k,a+e*d,b+e*h,"x","e"]},circle:function(a,b,c,d){return["wa",a,b,a+c,b+d,a+c,b+d/2,a+c,b+d/2,"e"]},rect:function(a,b,c,d,e){if(!s(e))return[];var f=a+c,g=b+d,c=Ua(e.r||0,c,d);return[wa,a+c,b,fa,f-c,b,"wa",f-2*c,b,f,b+2*c,f-c,b,f,b+c,fa,f,g-c,"wa",f-2*c,g-2*c,f,g,f,g-c,f-c,g,fa,a+c,g,"wa",a,g-2*c,a+2*c,g,a+c,g,a,g-c,fa,a,b+c,"wa",
+a,b,a+2*c,b+2*c,a,b+c,a+c,b,"x","e"]}}},$a=function(){this.init.apply(this,arguments)},$a.prototype=K(Eb.prototype,w),Xb=$a;var oc,Cc;Fa&&(oc=function(){},Cc=function(){function a(){var a=b.length,d;for(d=0;d<a;d++)b[d]();b=[]}var b=[];return{push:function(c,d){b.length===0&&Hc(d,a);b.push(c)}}}());Xb=$a||oc||Eb;wc.prototype.callbacks=[];var lb=function(){};lb.prototype={init:function(a,b,c){var d=a.chart.counters;this.series=a;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint){b=
+a.chart.options.colors;if(!this.options)this.options={};this.color=this.options.color=this.color||b[d.color++];d.wrapColor(b.length)}a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,d=typeof a;this.config=a;if(d==="number"||a===null)this.y=a;else if(typeof a[0]==="number")this.x=a[0],this.y=a[1];else if(d==="object"&&typeof a.length!=="number"){if(L(this,a),this.options=a,a.dataLabels)c._hasPointLabels=!0}else if(typeof a[0]==="string")this.name=a[0],this.y=a[1];if(this.x===
+X)this.x=b===X?c.autoIncrement():b},destroy:function(){var a=this.series,b=a.chart.hoverPoints,c;a.chart.pointCount--;b&&(this.setState(),Gb(b,this));if(this===a.chart.hoverPoint)this.onMouseOut();a.chart.hoverPoints=null;if(this.graphic||this.dataLabel)Qa(this),this.destroyElements();this.legendItem&&this.series.chart.legend.destroyItem(this);for(c in this)this[c]=null},destroyElements:function(){for(var a="graphic,tracker,dataLabel,group,connector,shadowGroup".split(","),b,c=6;c--;)b=a[c],this[b]&&
+(this[b]=this[b].destroy())},getLabelConfig:function(){return{x:this.category,y:this.y,key:this.name||this.category,series:this.series,point:this,percentage:this.percentage,total:this.total||this.stackTotal}},select:function(a,b){var c=this,d=c.series.chart,a=p(a,!c.selected);c.firePointEvent(a?"select":"unselect",{accumulate:b},function(){c.selected=a;c.setState(a&&"select");b||o(d.getSelectedPoints(),function(a){if(a.selected&&a!==c)a.selected=!1,a.setState(Ka),a.firePointEvent("unselect")})})},
+onMouseOver:function(){var a=this.series,b=a.chart,c=b.tooltip,d=b.hoverPoint;if(d&&d!==this)d.onMouseOut();this.firePointEvent("mouseOver");c&&(!c.shared||a.noSharedTooltip)&&c.refresh(this);this.setState(ta);b.hoverPoint=this},onMouseOut:function(){this.firePointEvent("mouseOut");this.setState();this.series.chart.hoverPoint=null},tooltipFormatter:function(a){var b=this.series,c=b.tooltipOptions,d=String(this.y).split("."),d=d[1]?d[1].length:0,e=a.match(/\{(series|point)\.[a-zA-Z]+\}/g),f=/[{\.}]/,
+g,h,i,k;for(k in e)h=e[k],Ab(h)&&h!==a&&(i=(" "+h).split(f),g={point:this,series:b}[i[1]],i=i[2],g=g===this&&(i==="y"||i==="open"||i==="high"||i==="low"||i==="close")?(c.valuePrefix||c.yPrefix||"")+dc(this[i],p(c.valueDecimals,c.yDecimals,d))+(c.valueSuffix||c.ySuffix||""):g[i],a=a.replace(h,g));return a},update:function(a,b,c){var d=this,e=d.series,f=d.graphic,g,h=e.data,i=h.length,k=e.chart,b=p(b,!0);d.firePointEvent("update",{options:a},function(){d.applyOptions(a);mb(a)&&(e.getAttribs(),f&&f.attr(d.pointAttr[e.state]));
+for(g=0;g<i;g++)if(h[g]===d){e.xData[g]=d.x;e.yData[g]=d.y;e.options.data[g]=a;break}e.isDirty=!0;e.isDirtyData=!0;b&&k.redraw(c)})},remove:function(a,b){var c=this,d=c.series,e=d.chart,f,g=d.data,h=g.length;Kb(b,e);a=p(a,!0);c.firePointEvent("remove",null,function(){for(f=0;f<h;f++)if(g[f]===c){g.splice(f,1);d.options.data.splice(f,1);d.xData.splice(f,1);d.yData.splice(f,1);break}c.destroy();d.isDirty=!0;d.isDirtyData=!0;a&&e.redraw()})},firePointEvent:function(a,b,c){var d=this,e=this.series.options;
+(e.point.events[a]||d.options&&d.options.events&&d.options.events[a])&&this.importEvents();a==="click"&&e.allowPointSelect&&(c=function(a){d.select(null,a.ctrlKey||a.metaKey||a.shiftKey)});aa(this,a,b,c)},importEvents:function(){if(!this.hasImportedEvents){var a=K(this.series.options.point,this.options).events,b;this.events=a;for(b in a)ha(this,b,a[b]);this.hasImportedEvents=!0}},setState:function(a){var b=this.plotX,c=this.plotY,d=this.series,e=d.options.states,f=M[d.type].marker&&d.options.marker,
+g=f&&!f.enabled,h=f&&f.states[a],i=h&&h.enabled===!1,k=d.stateMarkerGraphic,j=d.chart,l=this.pointAttr,a=a||Ka;if(!(a===this.state||this.selected&&a!=="select"||e[a]&&e[a].enabled===!1||a&&(i||g&&!h.enabled))){if(this.graphic)e=f&&this.graphic.symbolName&&l[a].r,this.graphic.attr(K(l[a],e?{x:b-e,y:c-e,width:2*e,height:2*e}:{}));else{if(a){if(!k)e=f.radius,d.stateMarkerGraphic=k=j.renderer.symbol(d.symbol,-e,-e,2*e,2*e).attr(l[a]).add(d.group);k.translate(b,c)}if(k)k[a?"show":"hide"]()}this.state=
+a}}};var $=function(){};$.prototype={isCartesian:!0,type:"line",pointClass:lb,sorted:!0,pointAttrToOptions:{stroke:"lineColor","stroke-width":"lineWidth",fill:"fillColor",r:"radius"},init:function(a,b){var c,d;d=a.series.length;this.chart=a;this.options=b=this.setOptions(b);this.bindAxes();L(this,{index:d,name:b.name||"Series "+(d+1),state:Ka,pointAttr:{},visible:b.visible!==!1,selected:b.selected===!0});if(Fa)b.animation=!1;d=b.events;for(c in d)ha(this,c,d[c]);if(d&&d.click||b.point&&b.point.events&&
+b.point.events.click||b.allowPointSelect)a.runTrackerClick=!0;this.getColor();this.getSymbol();this.setData(b.data,!1)},bindAxes:function(){var a=this,b=a.options,c=a.chart,d;a.isCartesian&&o(["xAxis","yAxis"],function(e){o(c[e],function(c){d=c.options;if(b[e]===d.index||b[e]===X&&d.index===0)c.series.push(a),a[e]=c,c.isDirty=!0})})},autoIncrement:function(){var a=this.options,b=this.xIncrement,b=p(b,a.pointStart,0);this.pointInterval=p(this.pointInterval,a.pointInterval,1);this.xIncrement=b+this.pointInterval;
+return b},getSegments:function(){var a=-1,b=[],c,d=this.points,e=d.length;if(e)if(this.options.connectNulls){for(c=e;c--;)d[c].y===null&&d.splice(c,1);d.length&&(b=[d])}else o(d,function(c,g){c.y===null?(g>a+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart.options,c=b.plotOptions,d=a.data;a.data=null;c=K(c[this.type],c.series,a);c.data=a.data=d;this.tooltipOptions=K(b.tooltip,c.tooltip);return c},getColor:function(){var a=
+this.chart.options.colors,b=this.chart.counters;this.color=this.options.color||a[b.color++]||"#0000ff";b.wrapColor(a.length)},getSymbol:function(){var a=this.options.marker,b=this.chart,c=b.options.symbols,b=b.counters;this.symbol=a.symbol||c[b.symbol++];if(/^url/.test(this.symbol))a.radius=0;b.wrapSymbol(c.length)},addPoint:function(a,b,c,d){var e=this.data,f=this.graph,g=this.area,h=this.chart,i=this.xData,k=this.yData,j=f&&f.shift||0,l=this.options.data;Kb(d,h);if(f&&c)f.shift=j+1;if(g){if(c)g.shift=
+j+1;g.isArea=!0}b=p(b,!0);d={series:this};this.pointClass.prototype.applyOptions.apply(d,[a]);i.push(d.x);k.push(this.valueCount===4?[d.open,d.high,d.low,d.close]:d.y);l.push(a);c&&(e[0]?e[0].remove(!1):(e.shift(),i.shift(),k.shift(),l.shift()));this.getAttribs();this.isDirtyData=this.isDirty=!0;b&&h.redraw()},setData:function(a,b){var c=this.points,d=this.options,e=this.initialColor,f=this.chart,g=null;this.xIncrement=null;this.pointRange=this.xAxis&&this.xAxis.categories&&1||d.pointRange;if(s(e))f.counters.color=
+e;var h=[],i=[],k=a?a.length:[],j=this.valueCount===4;if(k>(d.turboThreshold||1E3)){for(e=0;g===null&&e<k;)g=a[e],e++;if(Bb(g)){g=p(d.pointStart,0);d=p(d.pointInterval,1);for(e=0;e<k;e++)h[e]=g,i[e]=a[e],g+=d;this.xIncrement=g}else if(Fb(g))if(j)for(e=0;e<k;e++)d=a[e],h[e]=d[0],i[e]=d.slice(1,5);else for(e=0;e<k;e++)d=a[e],h[e]=d[0],i[e]=d[1]}else for(e=0;e<k;e++)d={series:this},this.pointClass.prototype.applyOptions.apply(d,[a[e]]),h[e]=d.x,i[e]=j?[d.open,d.high,d.low,d.close]:d.y;this.data=[];this.options.data=
+a;this.xData=h;this.yData=i;for(e=c&&c.length||0;e--;)c[e]&&c[e].destroy&&c[e].destroy();this.isDirty=this.isDirtyData=f.isDirtyBox=!0;p(b,!0)&&f.redraw(!1)},remove:function(a,b){var c=this,d=c.chart,a=p(a,!0);if(!c.isRemoving)c.isRemoving=!0,aa(c,"remove",null,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=!0;a&&d.redraw(b)});c.isRemoving=!1},processData:function(a){var b=this.xData,c=this.yData,d=b.length,e=0,f=d,g,h,i=this.xAxis,k=this.options,j=k.cropThreshold,l=this.isCartesian;if(l&&!this.isDirty&&
+!i.isDirty&&!this.yAxis.isDirty&&!a)return!1;if(l&&this.sorted&&(!j||d>j||this.forceCrop))if(a=i.getExtremes(),i=a.min,j=a.max,b[d-1]<i||b[0]>j)b=[],c=[];else if(b[0]<i||b[d-1]>j){for(a=0;a<d;a++)if(b[a]>=i){e=W(0,a-1);break}for(;a<d;a++)if(b[a]>j){f=a+1;break}b=b.slice(e,f);c=c.slice(e,f);g=!0}for(a=b.length-1;a>0;a--)if(d=b[a]-b[a-1],d>0&&(h===X||d<h))h=d;this.cropped=g;this.cropStart=e;this.processedXData=b;this.processedYData=c;if(k.pointRange===null)this.pointRange=h||1;this.closestPointRange=
+h},generatePoints:function(){var a=this.options.data,b=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,i,k=this.hasGroupedData,j,l=[],n;if(!b&&!k)b=[],b.length=a.length,b=this.data=b;for(n=0;n<g;n++)i=h+n,k?l[n]=(new f).init(this,[d[n]].concat(Hb(e[n]))):(b[i]?j=b[i]:b[i]=j=(new f).init(this,a[i],d[n]),l[n]=j);if(b&&(g!==(c=b.length)||k))for(n=0;n<c;n++)n===h&&!k&&(n+=g),b[n]&&b[n].destroyElements();this.data=b;this.points=l},translate:function(){this.processedXData||
+this.processData();this.generatePoints();for(var a=this.chart,b=this.options,c=b.stacking,d=this.xAxis,e=d.categories,f=this.yAxis,g=this.points,h=g.length,i=!!this.modifyValue,k,j=f.series,l=j.length;l--;)if(j[l].visible){l===this.index&&(k=!0);break}for(l=0;l<h;l++){var j=g[l],n=j.x,o=j.y,r=j.low,p=f.stacks[(o<b.threshold?"-":"")+this.stackKey];j.plotX=z(d.translate(n,0,0,0,1)*10)/10;if(c&&this.visible&&p&&p[n]){r=p[n];n=r.total;r.cum=r=r.cum-o;o=r+o;if(k)r=b.threshold;c==="percent"&&(r=n?r*100/
+n:0,o=n?o*100/n:0);j.percentage=n?j.y*100/n:0;j.stackTotal=n;j.stackY=o}j.yBottom=s(r)?f.translate(r,0,1,0,1):null;i&&(o=this.modifyValue(o,j));j.plotY=typeof o==="number"?z(f.translate(o,0,1,0,1)*10)/10:X;j.clientX=a.inverted?a.plotHeight-j.plotX:j.plotX;j.category=e&&e[j.x]!==X?e[j.x]:j.x}this.getSegments()},setTooltipPoints:function(a){var b=this.chart,c=b.inverted,d=[],b=z((c?b.plotTop:b.plotLeft)+b.plotSizeX),e,f;e=this.xAxis;var g,h,i=[];if(this.options.enableMouseTracking!==!1){if(a)this.tooltipPoints=
+null;o(this.segments||this.points,function(a){d=d.concat(a)});e&&e.reversed&&(d=d.reverse());a=d.length;for(h=0;h<a;h++){g=d[h];e=d[h-1]?d[h-1]._high+1:0;for(f=g._high=d[h+1]?Ta((g.plotX+(d[h+1]?d[h+1].plotX:b))/2):b;e<=f;)i[c?b-e++:e++]=g}this.tooltipPoints=i}},tooltipHeaderFormatter:function(a){var b=this.tooltipOptions,c=b.xDateFormat||"%A, %b %e, %Y",d=this.xAxis;return b.headerFormat.replace("{point.key}",d&&d.options.type==="datetime"?ac(c,a):a).replace("{series.name}",this.name).replace("{series.color}",
+this.color)},onMouseOver:function(){var a=this.chart,b=a.hoverSeries;if(Ga||!a.mouseIsDown){if(b&&b!==this)b.onMouseOut();this.options.events.mouseOver&&aa(this,"mouseOver");this.setState(ta);a.hoverSeries=this}},onMouseOut:function(){var a=this.options,b=this.chart,c=b.tooltip,d=b.hoverPoint;if(d)d.onMouseOut();this&&a.events.mouseOut&&aa(this,"mouseOut");c&&!a.stickyTracking&&!c.shared&&c.hide();this.setState();b.hoverSeries=null},animate:function(a){var b=this.chart,c=this.clipRect,d=this.options.animation;
+d&&!mb(d)&&(d={});if(a){if(!c.isAnimating)c.attr("width",0),c.isAnimating=!0}else c.animate({width:b.plotSizeX},d),this.animate=null},drawPoints:function(){var a,b=this.points,c=this.chart,d,e,f,g,h,i,k,j;if(this.options.marker.enabled)for(f=b.length;f--;)if(g=b[f],d=g.plotX,e=g.plotY,j=g.graphic,e!==X&&!isNaN(e))if(a=g.pointAttr[g.selected?"select":Ka],h=a.r,i=p(g.marker&&g.marker.symbol,this.symbol),k=i.indexOf("url")===0,j)j.animate(L({x:d-h,y:e-h},j.symbolName?{width:2*h,height:2*h}:{}));else if(h>
+0||k)g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h).attr(a).add(this.group)},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=p(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=M[a.type].marker?a.options.marker:a.options,c=b.states,d=c[ta],e,f=a.color,g={stroke:f,fill:f},h=a.points,i=[],k,j=a.pointAttrToOptions,l;a.options.marker?(d.radius=d.radius||b.radius+2,d.lineWidth=d.lineWidth||b.lineWidth+1):d.color=
+d.color||ma(d.color||f).brighten(d.brightness).get();i[Ka]=a.convertAttribs(b,g);o([ta,"select"],function(b){i[b]=a.convertAttribs(c[b],i[Ka])});a.pointAttr=i;for(f=h.length;f--;){g=h[f];if((b=g.options&&g.options.marker||g.options)&&b.enabled===!1)b.radius=0;e=!1;if(g.options)for(l in j)s(b[j[l]])&&(e=!0);if(e){k=[];c=b.states||{};e=c[ta]=c[ta]||{};if(!a.options.marker)e.color=ma(e.color||g.options.color).brighten(e.brightness||d.brightness).get();k[Ka]=a.convertAttribs(b,i[Ka]);k[ta]=a.convertAttribs(c[ta],
+i[ta],k[Ka]);k.select=a.convertAttribs(c.select,i.select,k[Ka])}else k=i;g.pointAttr=k}},destroy:function(){var a=this,b=a.chart,c=a.clipRect,d=/AppleWebKit\/533/.test(kb),e,f,g=a.data||[],h,i,k;aa(a,"destroy");Qa(a);o(["xAxis","yAxis"],function(b){if(k=a[b])Gb(k.series,a),k.isDirty=!0});a.legendItem&&a.chart.legend.destroyItem(a);for(f=g.length;f--;)(h=g[f])&&h.destroy&&h.destroy();a.points=null;if(c&&c!==b.clipRect)a.clipRect=c.destroy();o(["area","graph","dataLabelsGroup","group","tracker"],function(b){a[b]&&
+(e=d&&b==="group"?"hide":"destroy",a[b][e]())});if(b.hoverSeries===a)b.hoverSeries=null;Gb(b.series,a);for(i in a)delete a[i]},drawDataLabels:function(){var a=this,b=a.options,c=b.dataLabels;if(c.enabled||a._hasPointLabels){var d,e,f=a.points,g,h,i,k=a.dataLabelsGroup,j=a.chart,l=a.xAxis,l=l?l.left:j.plotLeft,n=a.yAxis,n=n?n.top:j.plotTop,t=j.renderer,r=j.inverted,Z=a.type,V=b.stacking,v=Z==="column"||Z==="bar",y=c.verticalAlign===null,q=c.y===null,T=t.fontMetrics(c.style.fontSize),A=T.h,x=T.b,w,
+ia;v&&(T={top:x,middle:x-A/2,bottom:-A+x},V?(y&&(c=K(c,{verticalAlign:"middle"})),q&&(c=K(c,{y:T[c.verticalAlign]}))):y?c=K(c,{verticalAlign:"top"}):q&&(c=K(c,{y:T[c.verticalAlign]})));k?k.translate(l,n):k=a.dataLabelsGroup=t.g("data-labels").attr({visibility:a.visible?eb:ab,zIndex:6}).translate(l,n).add();h=c;o(f,function(f){w=f.dataLabel;c=h;(g=f.options)&&g.dataLabels&&(c=K(c,g.dataLabels));if(ia=c.enabled){var l=f.barX&&f.barX+f.barW/2||p(f.plotX,-999),n=p(f.plotY,-999),o=c.y===null?f.y>=b.threshold?
+-A+x:x:c.y;d=(r?j.plotWidth-n:l)+c.x;e=z((r?j.plotHeight-l:n)+o)}if(w&&a.isCartesian&&(!j.isInsidePlot(d,e)||!ia))f.dataLabel=w.destroy();else if(ia){l=c.align;i=c.formatter.call(f.getLabelConfig(),c);Z==="column"&&(d+={left:-1,right:1}[l]*f.barW/2||0);!V&&r&&f.y<0&&(l="right",d-=10);c.style.color=p(c.color,c.style.color,a.color,"black");if(w)w.attr({text:i}).animate({x:d,y:e});else if(s(i))w=f.dataLabel=t[c.rotation?"text":"label"](i,d,e,null,null,null,c.useHTML,!0).attr({align:l,fill:c.backgroundColor,
+stroke:c.borderColor,"stroke-width":c.borderWidth,r:c.borderRadius,rotation:c.rotation,padding:c.padding,zIndex:1}).css(c.style).add(k).shadow(c.shadow);if(v&&b.stacking&&w)l=f.barX,n=f.barY,o=f.barW,f=f.barH,w.align(c,null,{x:r?j.plotWidth-n-f:l,y:r?j.plotHeight-l-o:n,width:r?f:o,height:r?o:f})}})}},drawGraph:function(){var a=this,b=a.options,c=a.graph,d=[],e,f=a.area,g=a.group,h=b.lineColor||a.color,i=b.lineWidth,k=b.dashStyle,j,l=a.chart.renderer,n=a.yAxis.getThreshold(b.threshold),t=/^area/.test(a.type),
+r=[],s=[];o(a.segments,function(c){j=[];o(c,function(d,e){a.getPointSpline?j.push.apply(j,a.getPointSpline(c,d,e)):(j.push(e?fa:wa),e&&b.step&&j.push(d.plotX,c[e-1].plotY),j.push(d.plotX,d.plotY))});c.length>1?d=d.concat(j):r.push(c[0]);if(t){var e=[],f,g=j.length;for(f=0;f<g;f++)e.push(j[f]);g===3&&e.push(fa,j[1],j[2]);if(b.stacking&&a.type!=="areaspline")for(f=c.length-1;f>=0;f--)f<c.length-1&&b.step&&e.push(c[f+1].plotX,c[f].yBottom),e.push(c[f].plotX,c[f].yBottom);else e.push(fa,c[c.length-1].plotX,
+n,fa,c[0].plotX,n);s=s.concat(e)}});a.graphPath=d;a.singlePoints=r;if(t)e=p(b.fillColor,ma(a.color).setOpacity(b.fillOpacity||0.75).get()),f?f.animate({d:s}):a.area=a.chart.renderer.path(s).attr({fill:e}).add(g);if(c)Ob(c),c.animate({d:d});else if(i){c={stroke:h,"stroke-width":i};if(k)c.dashstyle=k;a.graph=l.path(d).attr(c).add(g).shadow(b.shadow)}},invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};c.attr(a).invert();d&&d.attr(a).invert()}var b=this,c=b.group,d=b.trackerGroup,
+e=b.chart;ha(e,"resize",a);ha(b,"destroy",function(){Qa(e,"resize",a)});a();b.invertGroups=a},render:function(){var a=this,b=a.chart,c,d=a.options,e=d.clip!==!1,f=d.animation,g=f&&a.animate,f=g?f&&f.duration||500:0,h=a.clipRect,i=b.renderer;if(!h&&(h=a.clipRect=!b.hasRendered&&b.clipRect?b.clipRect:i.clipRect(0,0,b.plotSizeX,b.plotSizeY+1),!b.clipRect))b.clipRect=h;if(!a.group)c=a.group=i.g("series"),c.attr({visibility:a.visible?eb:ab,zIndex:d.zIndex}).translate(a.xAxis.left,a.yAxis.top).add(b.seriesGroup);
+a.drawDataLabels();g&&a.animate(!0);a.getAttribs();a.drawGraph&&a.drawGraph();a.drawPoints();a.options.enableMouseTracking!==!1&&a.drawTracker();b.inverted&&a.invertGroups();e&&!a.hasRendered&&(c.clip(h),a.trackerGroup&&a.trackerGroup.clip(b.clipRect));g&&a.animate();setTimeout(function(){h.isAnimating=!1;if((c=a.group)&&h!==b.clipRect&&h.renderer){if(e)c.clip(a.clipRect=b.clipRect);h.destroy()}},f);a.isDirty=a.isDirtyData=!1;a.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirtyData,
+c=this.group;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:this.xAxis.left,translateY:this.yAxis.top}));this.translate();this.setTooltipPoints(!0);this.render();b&&aa(this,"updatedData")},setState:function(a){var b=this.options,c=this.graph,d=b.states,b=b.lineWidth,a=a||Ka;if(this.state!==a)this.state=a,d[a]&&d[a].enabled===!1||(a&&(b=d[a].lineWidth||b+1),c&&!c.dashstyle&&c.attr({"stroke-width":b},a?0:500))},setVisible:function(a,b){var c=this.chart,d=this.legendItem,
+e=this.group,f=this.tracker,g=this.dataLabelsGroup,h,i=this.points,k=c.options.chart.ignoreHiddenSeries;h=this.visible;h=(this.visible=a=a===X?!h:a)?"show":"hide";if(e)e[h]();if(f)f[h]();else if(i)for(e=i.length;e--;)if(f=i[e],f.tracker)f.tracker[h]();if(g)g[h]();d&&c.legend.colorizeItem(this,a);this.isDirty=!0;this.options.stacking&&o(c.series,function(a){if(a.options.stacking&&a.visible)a.isDirty=!0});if(k)c.isDirtyBox=!0;b!==!1&&c.redraw();aa(this,h)},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},
+select:function(a){this.selected=a=a===X?!this.selected:a;if(this.checkbox)this.checkbox.checked=a;aa(this,a?"select":"unselect")},drawTrackerGroup:function(){var a=this.trackerGroup,b=this.chart;if(this.isCartesian){if(!a)this.trackerGroup=a=b.renderer.g().attr({zIndex:this.options.zIndex||1}).add(b.trackerGroup);a.translate(this.xAxis.left,this.yAxis.top)}return a},drawTracker:function(){var a=this,b=a.options,c=[].concat(a.graphPath),d=c.length,e=a.chart,f=e.renderer,g=e.options.tooltip.snap,h=
+a.tracker,i=b.cursor,i=i&&{cursor:i},k=a.singlePoints,j=a.drawTrackerGroup(),l;if(d)for(l=d+1;l--;)c[l]===wa&&c.splice(l+1,0,c[l+1]-g,c[l+2],fa),(l&&c[l]===wa||l===d)&&c.splice(l,0,fa,c[l-2]+g,c[l-1]);for(l=0;l<k.length;l++)d=k[l],c.push(wa,d.plotX-g,d.plotY,fa,d.plotX+g,d.plotY);h?h.attr({d:c}):a.tracker=f.path(c).attr({isTracker:!0,stroke:Gc,fill:Ma,"stroke-linejoin":"bevel","stroke-width":b.lineWidth+2*g,visibility:a.visible?eb:ab}).on(Ga?"touchstart":"mouseover",function(){if(e.hoverSeries!==
+a)a.onMouseOver()}).on("mouseout",function(){if(!b.stickyTracking)a.onMouseOut()}).css(i).add(j)}};w=pa($);Ha.line=w;w=pa($,{type:"area"});Ha.area=w;w=pa($,{type:"spline",getPointSpline:function(a,b,c){var d=b.plotX,e=b.plotY,f=a[c-1],g=a[c+1],h,i,k,j;if(c&&c<a.length-1){a=f.plotY;k=g.plotX;var g=g.plotY,l;h=(1.5*d+f.plotX)/2.5;i=(1.5*e+a)/2.5;k=(1.5*d+k)/2.5;j=(1.5*e+g)/2.5;l=(j-i)*(k-d)/(k-h)+e-j;i+=l;j+=l;i>a&&i>e?(i=W(a,e),j=2*e-i):i<a&&i<e&&(i=Ua(a,e),j=2*e-i);j>g&&j>e?(j=W(g,e),i=2*e-j):j<g&&
+j<e&&(j=Ua(g,e),i=2*e-j);b.rightContX=k;b.rightContY=j}c?(b=["C",f.rightContX||f.plotX,f.rightContY||f.plotY,h||d,i||e,d,e],f.rightContX=f.rightContY=null):b=[wa,d,e];return b}});Ha.spline=w;w=pa(w,{type:"areaspline"});Ha.areaspline=w;var zb=pa($,{type:"column",tooltipOutsidePlot:!0,pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color",r:"borderRadius"},init:function(){$.prototype.init.apply(this,arguments);var a=this,b=a.chart;b.hasRendered&&o(b.series,function(b){if(b.type===
+a.type)b.isDirty=!0})},translate:function(){var a=this,b=a.chart,c=a.options,d=c.stacking,e=c.borderWidth,f=0,g=a.xAxis,h=g.reversed,i={},k,j;$.prototype.translate.apply(a);o(b.series,function(b){if(b.type===a.type&&b.visible&&a.options.group===b.options.group)b.options.stacking?(k=b.stackKey,i[k]===X&&(i[k]=f++),j=i[k]):j=f++,b.columnIndex=j});var b=a.points,g=Ba(g.translationSlope)*(g.ordinalSlope||g.closestPointRange||1),l=g*c.groupPadding,n=(g-2*l)/f,t=c.pointWidth,r=s(t)?(n-t)/2:n*c.pointPadding,
+w=Yb(W(p(t,n-2*r),1+2*e)),x=r+(l+((h?f-a.columnIndex:a.columnIndex)||0)*n-g/2)*(h?-1:1),v=a.yAxis.getThreshold(c.threshold),y=p(c.minPointLength,5);o(b,function(b){var f=b.plotY,g=p(b.yBottom,v),h=b.plotX+x,i=Yb(Ua(f,g)),j=Yb(W(f,g)-i),k=a.yAxis.stacks[(b.y<0?"-":"")+a.stackKey];d&&a.visible&&k&&k[b.x]&&k[b.x].setOffset(x,w);Ba(j)<y&&y&&(j=y,i=Ba(i-v)>y?g-y:v-(f<=v?y:0));L(b,{barX:h,barY:i,barW:w,barH:j});b.shapeType="rect";f={x:h,y:i,width:w,height:j,r:c.borderRadius,strokeWidth:e};e%2&&(f.y-=1,
+f.height+=1);b.shapeArgs=f;b.trackerArgs=Ba(j)<3&&K(b.shapeArgs,{height:6,y:i-3})})},getSymbol:function(){},drawGraph:function(){},drawPoints:function(){var a=this,b=a.options,c=a.chart.renderer,d,e;o(a.points,function(f){var g=f.plotY;if(g!==X&&!isNaN(g)&&f.y!==null)d=f.graphic,e=f.shapeArgs,d?(Ob(d),d.animate(c.Element.prototype.crisp.apply({},[e.strokeWidth,e.x,e.y,e.width,e.height]))):f.graphic=d=c[f.shapeType](e).attr(f.pointAttr[f.selected?"select":Ka]).add(a.group).shadow(b.shadow)})},drawTracker:function(){var a=
+this,b=a.chart,c=b.renderer,d,e,f=+new Date,g=a.options,h=g.cursor,i=h&&{cursor:h},k=a.drawTrackerGroup(),j;o(a.points,function(h){e=h.tracker;d=h.trackerArgs||h.shapeArgs;delete d.strokeWidth;if(h.y!==null)e?e.attr(d):h.tracker=c[h.shapeType](d).attr({isTracker:f,fill:Gc,visibility:a.visible?eb:ab}).on(Ga?"touchstart":"mouseover",function(c){j=c.relatedTarget||c.fromElement;if(b.hoverSeries!==a&&A(j,"isTracker")!==f)a.onMouseOver();h.onMouseOver()}).on("mouseout",function(b){if(!g.stickyTracking&&
+(j=b.relatedTarget||b.toElement,A(j,"isTracker")!==f))a.onMouseOut()}).css(i).add(h.group||k)})},animate:function(a){var b=this,c=b.points,d=b.options;if(!a)o(c,function(a){var c=a.graphic,a=a.shapeArgs,g=b.yAxis,h=d.threshold;c&&(c.attr({height:0,y:s(h)?g.getThreshold(h):g.translate(g.getExtremes().min,0,1,0,1)}),c.animate({height:a.height,y:a.y},d.animation))}),b.animate=null},remove:function(){var a=this,b=a.chart;b.hasRendered&&o(b.series,function(b){if(b.type===a.type)b.isDirty=!0});$.prototype.remove.apply(a,
+arguments)}});Ha.column=zb;w=pa(zb,{type:"bar",init:function(){this.inverted=!0;zb.prototype.init.apply(this,arguments)}});Ha.bar=w;w=pa($,{type:"scatter",sorted:!1,translate:function(){var a=this;$.prototype.translate.apply(a);o(a.points,function(b){b.shapeType="circle";b.shapeArgs={x:b.plotX,y:b.plotY,r:a.chart.options.tooltip.snap}})},drawTracker:function(){for(var a=this,b=a.options.cursor,b=b&&{cursor:b},c=a.points,d=c.length,e;d--;)if(e=c[d].graphic)e.element._i=d;a._hasTracking?a._hasTracking=
+!0:a.group.attr({isTracker:!0}).on(Ga?"touchstart":"mouseover",function(b){a.onMouseOver();if(b.target._i!==X)c[b.target._i].onMouseOver()}).on("mouseout",function(){if(!a.options.stickyTracking)a.onMouseOut()}).css(b)}});Ha.scatter=w;w=pa(lb,{init:function(){lb.prototype.init.apply(this,arguments);var a=this,b;L(a,{visible:a.visible!==!1,name:p(a.name,"Slice")});b=function(){a.slice()};ha(a,"select",b);ha(a,"unselect",b);return a},setVisible:function(a){var b=this.series.chart,c=this.tracker,d=this.dataLabel,
+e=this.connector,f=this.shadowGroup,g;g=(this.visible=a=a===X?!this.visible:a)?"show":"hide";this.group[g]();if(c)c[g]();if(d)d[g]();if(e)e[g]();if(f)f[g]();this.legendItem&&b.legend.colorizeItem(this,a)},slice:function(a,b,c){var d=this.series.chart,e=this.slicedTranslation;Kb(c,d);p(b,!0);a=this.sliced=s(a)?a:!this.sliced;a={translateX:a?e[0]:d.plotLeft,translateY:a?e[1]:d.plotTop};this.group.animate(a);this.shadowGroup&&this.shadowGroup.animate(a)}});w=pa($,{type:"pie",isCartesian:!1,pointClass:w,
+pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},getColor:function(){this.initialColor=this.chart.counters.color},animate:function(){var a=this;o(a.points,function(b){var c=b.graphic,b=b.shapeArgs,d=-da/2;c&&(c.attr({r:0,start:d,end:d}),c.animate({r:b.r,start:b.start,end:b.end},a.options.animation))});a.animate=null},setData:function(){$.prototype.setData.apply(this,arguments);this.processData();this.generatePoints()},translate:function(){this.generatePoints();var a=
+0,b=-0.25,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f=c.center.concat([c.size,c.innerSize||0]),g=this.chart,h=g.plotWidth,i=g.plotHeight,k,j,l,n=this.points,p=2*da,r,s=Ua(h,i),w,v,y,q=c.dataLabels.distance,f=Vb(f,function(a,b){return(w=/%$/.test(a))?[h,i,s,s][b]*S(a)/100:a});this.getX=function(a,b){l=oa.asin((a-f[1])/(f[2]/2+q));return f[0]+(b?-1:1)*ja(l)*(f[2]/2+q)};this.center=f;o(n,function(b){a+=b.y});o(n,function(c){r=a?c.y/a:0;k=z(b*p*1E3)/1E3;b+=r;j=z(b*p*1E3)/1E3;c.shapeType="arc";
+c.shapeArgs={x:f[0],y:f[1],r:f[2]/2,innerR:f[3]/2,start:k,end:j};l=(j+k)/2;c.slicedTranslation=Vb([ja(l)*d+g.plotLeft,C(l)*d+g.plotTop],z);v=ja(l)*f[2]/2;y=C(l)*f[2]/2;c.tooltipPos=[f[0]+v*0.7,f[1]+y*0.7];c.labelPos=[f[0]+v+ja(l)*q,f[1]+y+C(l)*q,f[0]+v+ja(l)*e,f[1]+y+C(l)*e,f[0]+v,f[1]+y,q<0?"center":l<p/4?"left":"right",l];c.percentage=r*100;c.total=a});this.setTooltipPoints()},render:function(){this.getAttribs();this.drawPoints();this.options.enableMouseTracking!==!1&&this.drawTracker();this.drawDataLabels();
+this.options.animation&&this.animate&&this.animate();this.isDirty=!1},drawPoints:function(){var a=this.chart,b=a.renderer,c,d,e,f=this.options.shadow,g,h;o(this.points,function(i){d=i.graphic;h=i.shapeArgs;e=i.group;g=i.shadowGroup;if(f&&!g)g=i.shadowGroup=b.g("shadow").attr({zIndex:4}).add();if(!e)e=i.group=b.g("point").attr({zIndex:5}).add();c=i.sliced?i.slicedTranslation:[a.plotLeft,a.plotTop];e.translate(c[0],c[1]);g&&g.translate(c[0],c[1]);d?d.animate(h):i.graphic=b.arc(h).attr(L(i.pointAttr[Ka],
+{"stroke-linejoin":"round"})).add(i.group).shadow(f,g);i.visible===!1&&i.setVisible(!1)})},drawDataLabels:function(){var a=this.data,b,c=this.chart,d=this.options.dataLabels,e=p(d.connectorPadding,10),f=p(d.connectorWidth,1),g,h,i=p(d.softConnector,!0),k=d.distance,j=this.center,l=j[2]/2,j=j[1],n=k>0,t=[[],[]],r,s,w,v,y=2,q;if(d.enabled){$.prototype.drawDataLabels.apply(this);o(a,function(a){a.dataLabel&&t[a.labelPos[7]<da/2?0:1].push(a)});t[1].reverse();v=function(a,b){return b.y-a.y};for(a=t[0][0]&&
+t[0][0].dataLabel&&t[0][0].dataLabel.getBBox().height;y--;){var x=[],A=[],z=t[y],E=z.length,C;for(q=j-l-k;q<=j+l+k;q+=a)x.push(q);w=x.length;if(E>w){h=[].concat(z);h.sort(v);for(q=E;q--;)h[q].rank=q;for(q=E;q--;)z[q].rank>=w&&z.splice(q,1);E=z.length}for(q=0;q<E;q++){b=z[q];h=b.labelPos;b=9999;for(s=0;s<w;s++)g=Ba(x[s]-h[1]),g<b&&(b=g,C=s);if(C<q&&x[q]!==null)C=q;else for(w<E-q+C&&x[q]!==null&&(C=w-E+q);x[C]===null;)C++;A.push({i:C,y:x[C]});x[C]=null}A.sort(v);for(q=0;q<E;q++){b=z[q];h=b.labelPos;
+g=b.dataLabel;s=A.pop();r=h[1];w=b.visible===!1?ab:eb;C=s.i;s=s.y;if(r>s&&x[C+1]!==null||r<s&&x[C-1]!==null)s=r;r=this.getX(C===0||C===x.length-1?r:s,y);g.attr({visibility:w,align:h[6]})[g.moved?"animate":"attr"]({x:r+d.x+({left:e,right:-e}[h[6]]||0),y:s+d.y});g.moved=!0;if(n&&f)g=b.connector,h=i?[wa,r+(h[6]==="left"?5:-5),s,"C",r,s,2*h[2]-h[4],2*h[3]-h[5],h[2],h[3],fa,h[4],h[5]]:[wa,r+(h[6]==="left"?5:-5),s,fa,h[2],h[3],fa,h[4],h[5]],g?(g.animate({d:h}),g.attr("visibility",w)):b.connector=g=this.chart.renderer.path(h).attr({"stroke-width":f,
+stroke:d.connectorColor||b.color||"#606060",visibility:w,zIndex:3}).translate(c.plotLeft,c.plotTop).add()}}}},drawTracker:zb.prototype.drawTracker,getSymbol:function(){}});Ha.pie=w;L(Highcharts,{Chart:wc,dateFormat:ac,pathAnim:La,getOptions:function(){return Ea},hasBidiBug:Qc,numberFormat:dc,Point:lb,Color:ma,Renderer:Xb,SVGRenderer:Eb,VMLRenderer:$a,CanVGRenderer:oc,seriesTypes:Ha,setOptions:function(a){$b=K($b,a.xAxis);kc=K(kc,a.yAxis);a.xAxis=a.yAxis=X;Ea=K(Ea,a);vc();return Ea},Series:$,addEvent:ha,
+removeEvent:Qa,createElement:Aa,discardElement:Tb,css:P,each:o,extend:L,map:Vb,merge:K,pick:p,splat:Hb,extendClass:pa,placeBox:uc,product:"Highcharts",version:"2.2.1"})})();
diff --git a/apps/storage_charts/js/units.min.js b/apps/storage_charts/js/units.min.js
new file mode 100644
index 00000000000..5815f75fbb7
--- /dev/null
+++ b/apps/storage_charts/js/units.min.js
@@ -0,0 +1,23 @@
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+function getLinesUsseUnitsSelect(s){$('#clines_usse h3').append('<span id="selunits"><span id="selloader"></span><select id="chunits"><option value="1"'+(s==1?' selected':'')+'>'+t('storage_charts','Kilobytes (KB)')+'</option><option value="2"'+(s==2?' selected':'')+'>'+t('storage_charts','Megabytes (MB)')+'</option><option value="3"'+(s==3?' selected':'')+'>'+t('storage_charts','Gigabytes (GB)')+'</option><option value="4"'+(s==4?' selected':'')+'>'+t('storage_charts','Terabytes (TB)')+'</option></select></span>');$('#chunits').chosen();$('#chunits').change(function(){$('#selloader').html('<img src="'+OC.imagePath('storage_charts','loader.gif')+'" />');$.ajax({type:'POST',url:OC.linkTo('storage_charts','ajax/data.php'),dataType:'json',data:{s:$('#chunits').val(),k:'hu_size'},async:true,success:function(s){eval(s.r);$('#selloader img').remove();}});});}
+function getHistoUsUnitsSelect(s){$('#chisto_us h3').append('<span id="selunits_hus"><span id="selloader_hus"></span><select id="chunits_hus"><option value="1"'+(s==1?' selected':'')+'>'+t('storage_charts','Kilobytes (KB)')+'</option><option value="2"'+(s==2?' selected':'')+'>'+t('storage_charts','Megabytes (MB)')+'</option><option value="3"'+(s==3?' selected':'')+'>'+t('storage_charts','Gigabytes (GB)')+'</option><option value="4"'+(s==4?' selected':'')+'>'+t('storage_charts','Terabytes (TB)')+'</option></select></span>');$('#chunits_hus').chosen();$('#chunits_hus').change(function(){$('#selloader_hus').html('<img src="'+OC.imagePath('storage_charts','loader.gif')+'" />');$.ajax({type:'POST',url:OC.linkTo('storage_charts','ajax/data.php'),dataType:'json',data:{s:$('#chunits_hus').val(),k:'hu_size_hus'},async:true,success:function(s){eval(s.r);$('#selloader_hus img').remove();}});});}
diff --git a/apps/storage_charts/l10n/de.php b/apps/storage_charts/l10n/de.php
new file mode 100644
index 00000000000..e58e8acde2e
--- /dev/null
+++ b/apps/storage_charts/l10n/de.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+* English texts
+*
+*/
+
+$TRANSLATIONS = Array(
+ 'all users' => 'Alle Benutzer',
+ 'April' => 'April',
+ 'August' => 'August',
+ 'December' => 'Dezember',
+ 'February' => 'Februar',
+ 'January' => 'Januar',
+ 'July' => 'Juli',
+ 'June' => 'Juni',
+ 'March' => 'März',
+ 'May' => 'Mai',
+ 'November' => 'November',
+ 'October' => 'Oktober',
+ 'September' => 'September',
+ 'Monthly Used Space Evolution' => 'Entwicklung des monatlich genutzten Speichers',
+ 'Last 7 days' => 'Die letzten 7 Tage',
+ 'Daily Used Space Evolution' => 'Entwicklung des täglich genutzten Speichers',
+ 'Current ratio free space / used space' => 'Aktuelles Verhältnis zwischen freier Speicher / genutzter Speicher',
+ 'for' => 'für',
+ 'Free space' => 'Freier Speicher',
+ 'Drag\'N\'Drop on the chart title to re-order' => 'Drag\'N\'Drop der Diagrammnamen zum neuordnern',
+ 'Gigabytes (GB)' => 'Gigabytes (GB)',
+ 'Kilobytes (KB)' => 'Kilobytes (KB)',
+ 'Megabytes (MB)' => 'Megabytes (MB)',
+ 'Terabytes (TB)' => 'Terabytes (TB)',
+ 'Used space' => 'Genutzter Speicher',
+ 'Average used space' => 'Durchschnittlich verbrauchter Speicher',
+
+ // Personal settings
+ 'Save' => 'Speichern',
+ 'Save OK' => 'Speichern erfolgreich',
+ 'Uncheck charts you do not want to display' => 'Diagramme abwählen die nicht angezeigt werden sollen'
+); \ No newline at end of file
diff --git a/apps/storage_charts/l10n/en.php b/apps/storage_charts/l10n/en.php
new file mode 100644
index 00000000000..7676107aa31
--- /dev/null
+++ b/apps/storage_charts/l10n/en.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+* English texts
+*
+*/
+
+$TRANSLATIONS = Array(
+ 'all users' => 'all users',
+ 'April' => 'April',
+ 'August' => 'August',
+ 'Average used space' => 'Average used space',
+ 'December' => 'December',
+ 'February' => 'February',
+ 'January' => 'January',
+ 'July' => 'July',
+ 'June' => 'June',
+ 'March' => 'March',
+ 'May' => 'May',
+ 'November' => 'November',
+ 'October' => 'October',
+ 'September' => 'September',
+ 'Monthly Used Space Evolution' => 'Monthly Used Space Evolution',
+ 'Last 7 days' => 'Last 7 days',
+ 'Daily Used Space Evolution' => 'Daily Used Space Evolution',
+ 'Current ratio free space / used space' => 'Current ratio free space / used space',
+ 'for' => 'for',
+ 'Free space' => 'Free space',
+ 'Drag\'N\'Drop on the chart title to re-order' => 'Drag\'N\'Drop on the chart title to re-order',
+ 'Gigabytes (GB)' => 'Gigabytes (GB)',
+ 'Kilobytes (KB)' => 'Kilobytes (KB)',
+ 'Megabytes (MB)' => 'Megabytes (MB)',
+ 'Terabytes (TB)' => 'Terabytes (TB)',
+ 'Used space' => 'Used space',
+
+ // Personal settings
+ 'Save' => 'Save',
+ 'Save OK' => 'Save OK',
+ 'Uncheck charts you do not want to display' => 'Uncheck charts you do not want to display'
+); \ No newline at end of file
diff --git a/apps/storage_charts/l10n/fr.php b/apps/storage_charts/l10n/fr.php
new file mode 100644
index 00000000000..9d1b23b34df
--- /dev/null
+++ b/apps/storage_charts/l10n/fr.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+* French texts
+*
+*/
+
+$TRANSLATIONS = Array(
+ 'all users' => 'tous les utilisateurs',
+ 'April' => 'Avril',
+ 'August' => 'Août',
+ 'December' => 'Décembre',
+ 'February' => 'Février',
+ 'January' => 'Janvier',
+ 'July' => 'Juillet',
+ 'June' => 'Juin',
+ 'March' => 'Mars',
+ 'May' => 'Mai',
+ 'November' => 'Novembre',
+ 'October' => 'Octobre',
+ 'September' => 'Septembre',
+ 'Monthly Used Space Evolution' => 'Evolution mensuelle de l\'espace utilisé',
+ 'Last 7 days' => 'Les 7 derniers jours',
+ 'Daily Used Space Evolution' => 'Evolution quotidienne de l\'espace utilisé',
+ 'Current ratio free space / used space' => 'Ratio Espace libre / Espace utilisé actuel',
+ 'for' => 'pour',
+ 'Free space' => 'Espace libre',
+ 'Drag\'N\'Drop on the chart title to re-order' => 'Drag\'N\'Drop sur le titre du graph pour changer l\'ordre',
+ 'Gigabytes (GB)' => 'Gigabytes (GB)',
+ 'Kilobytes (KB)' => 'Kilobytes (KB)',
+ 'Megabytes (MB)' => 'Megabytes (MB)',
+ 'Terabytes (TB)' => 'Terabytes (TB)',
+ 'Used space' => 'Espace utilisé',
+ 'Average used space' => 'Espace utilisé moyen',
+
+ // Personal settings
+ 'Save' => 'Enregistrer',
+ 'Save OK' => 'Enregistrement OK',
+ 'Uncheck charts you do not want to display' => 'Décochez les graphs que vous ne voulez pas afficher'
+); \ No newline at end of file
diff --git a/apps/storage_charts/l10n/pt_BR.php b/apps/storage_charts/l10n/pt_BR.php
new file mode 100644
index 00000000000..823e19a6edd
--- /dev/null
+++ b/apps/storage_charts/l10n/pt_BR.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+* Portuguese texts
+* 26/May/2012 - Translated by Tiago Soares (tbsoares@gmail.com)
+*
+*/
+
+$TRANSLATIONS = Array(
+ 'all users' => 'Todos os usuários',
+ 'April' => 'Abril',
+ 'August' => 'Agosto',
+ 'Average used space' => 'Média de espaço utilizado',
+ 'December' => 'Dezembro',
+ 'February' => 'Fevereiro',
+ 'January' => 'Janeiro',
+ 'July' => 'Julho',
+ 'June' => 'Junho',
+ 'March' => 'Março',
+ 'May' => 'Maio',
+ 'November' => 'Novembro',
+ 'October' => 'Outubro',
+ 'September' => 'Setembro',
+ 'Monthly Used Space Evolution' => 'Evolução mensal do espaço utilizado',
+ 'Last 7 days' => 'Últimos 7 dias',
+ 'Daily Used Space Evolution' => 'Evolução diária do espaço utilizado',
+ 'Current ratio free space / used space' => 'Proporção atual entre espaço livre / espaço utilizado',
+ 'for' => 'para',
+ 'Free space' => 'Espaço livre',
+ 'Drag\'N\'Drop on the chart title to re-order' => 'Arraste e solte no título do gráfico para reordenar',
+ 'Gigabytes (GB)' => 'Gigabytes (GB)',
+ 'Kilobytes (KB)' => 'Kilobytes (KB)',
+ 'Megabytes (MB)' => 'Megabytes (MB)',
+ 'Terabytes (TB)' => 'Terabytes (TB)',
+ 'Used space' => 'Espaço utlizado',
+
+ // Personal settings
+ 'Save' => 'Salvar',
+ 'Save OK' => 'Salvo',
+ 'Uncheck charts you do not want to display' => 'Desmarque os gráficos que não deseja exibir'
+);
diff --git a/apps/storage_charts/lib/db.class.php b/apps/storage_charts/lib/db.class.php
new file mode 100644
index 00000000000..4479622180a
--- /dev/null
+++ b/apps/storage_charts/lib/db.class.php
@@ -0,0 +1,290 @@
+<?php
+
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+/**
+ * This class manages storage_charts.
+ */
+class OC_DLStCharts {
+
+ /**
+ * UPDATE day use for a user
+ * @param $used user used space
+ * @param $total total users used space
+ */
+ public static function update($used, $total){
+ $query = OCP\DB::prepare("SELECT stc_id FROM *PREFIX*dlstcharts WHERE oc_uid = ? AND stc_dayts = ?");
+ $result = $query->execute(Array(OCP\User::getUser(), mktime(0,0,0)))->fetchRow();
+ if($result){
+ $query = OCP\DB::prepare("UPDATE *PREFIX*dlstcharts SET stc_used = ?, stc_total = ? WHERE stc_id = ?");
+ $query->execute(Array($used, $total, $result['stc_id']));
+ }else{
+ $query = OCP\DB::prepare("INSERT INTO *PREFIX*dlstcharts (oc_uid,stc_month,stc_dayts,stc_used,stc_total) VALUES (?,?,?,?,?)");
+ $query->execute(Array(OCP\User::getUser(), date('Ym'), mktime(0,0,0), $used, $total));
+ }
+ }
+
+ /**
+ * Get the size of the data folder
+ * @param $path path to the folder you want to calculate the total size
+ */
+ public static function getTotalDataSize($path){
+ if(is_file($path)){
+ $path = dirname($path);
+ }
+ $path = str_replace('//', '/', $path);
+ if(is_dir($path) and strcmp(substr($path, -1), '/') != 0){
+ $path .= '/';
+ }
+ $size = 0;
+ if($dh = opendir($path)){
+ while(($filename = readdir($dh)) !== false) {
+ if(strcmp($filename, '.') != 0 and strcmp($filename, '..') != 0){
+ $subFile = $path . '/' . $filename;
+ if(is_file($subFile)){
+ $size += filesize($subFile);
+ }else{
+ $size += self::getTotalDataSize($subFile);
+ }
+ }
+ }
+ }
+ return $size;
+ }
+
+ /**
+ * Get data to build the pie about the Free-Used space ratio
+ */
+ public static function getPieFreeUsedSpaceRatio(){
+ if(OC_Group::inGroup(OCP\User::getUser(), 'admin')){
+ $query = OCP\DB::prepare("SELECT stc_id, stc_dayts, oc_uid FROM (SELECT * FROM *PREFIX*dlstcharts ORDER BY stc_dayts DESC) last GROUP BY oc_uid");
+ $results = $query->execute()->fetchAll();
+ }else{
+ $query = OCP\DB::prepare("SELECT stc_id, MAX(stc_dayts) as stc_dayts FROM *PREFIX*dlstcharts WHERE oc_uid = ?");
+ $results = $query->execute(Array(OCP\User::getUser()))->fetchAll();
+ }
+
+ $return = Array();
+ foreach($results as $result){
+ $query = OCP\DB::prepare("SELECT oc_uid, stc_used, stc_total FROM *PREFIX*dlstcharts WHERE stc_id = ?");
+ $return[] = $query->execute(Array($result['stc_id']))->fetchAll();
+ }
+
+ return $return;
+ }
+
+ /**
+ * Get data to build the line chart about last 7 days used space evolution
+ */
+ public static function getUsedSpaceOverTime($time){
+ $return = Array();
+ if(OC_Group::inGroup(OCP\User::getUser(), 'admin')){
+ foreach(OCP\User::getUsers() as $user){
+ if(strcmp($time, 'daily') == 0){
+ $return[$user] = self::getDataByUserToLineChart($user);
+ }else{
+ $return[$user] = self::getDataByUserToHistoChart($user);
+ }
+ }
+ }else{
+ if(strcmp($time, 'daily') == 0){
+ $return[OCP\User::getUser()] = self::getDataByUserToLineChart(OCP\User::getUser());
+ }else{
+ $return[OCP\User::getUser()] = self::getDataByUserToHistoChart(OCP\User::getUser());
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * Get configuration values stored in the database
+ * @param $key The conf key
+ * @return Array The conf value
+ */
+ public static function getUConfValue($key, $default = NULL){
+ $query = OCP\DB::prepare("SELECT uc_id,uc_val FROM *PREFIX*dlstcharts_uconf WHERE oc_uid = ? AND uc_key = ?");
+ $result = $query->execute(Array(OCP\User::getUser(), $key))->fetchRow();
+ if($result){
+ return $result;
+ }
+ return $default;
+ }
+
+ /**
+ * Set configuration values stored in the database
+ * @param $key The conf key
+ * @param $val The conf value
+ */
+ public static function setUConfValue($key,$val){
+ $conf = self::getUConfValue($key);
+ if(!is_null($conf)){
+ $query = OCP\DB::prepare("UPDATE *PREFIX*dlstcharts_uconf SET uc_val = ? WHERE uc_id = ?");
+ $query->execute(Array($val, $conf['uc_id']));
+ }else{
+ $query = OCP\DB::prepare("INSERT INTO *PREFIX*dlstcharts_uconf (oc_uid,uc_key,uc_val) VALUES (?,?,?)");
+ $query->execute(Array(OCP\User::getUser(), $key, $val));
+ }
+ }
+
+ /**
+ * Parse an array and return data in the highCharts format
+ * @param $operation operation to do
+ * @param $elements elements to parse
+ */
+ public static function arrayParser($operation, $elements, $l, $data_sep = ',', $ck = 'hu_size'){
+ $return = "";
+ switch($operation){
+ case 'pie':
+ $free = $total = 0;
+ foreach($elements as $element){
+ $element = $element[0];
+
+ $total = $element['stc_total'];
+ $free += $element['stc_used'];
+
+ $return .= "['" . $element['oc_uid'] . "', " . $element['stc_used'] . "],";
+ }
+ $return .= "['" . $l->t('Free space') . "', " . ($total - $free) . "]";
+ break;
+ case 'histo':
+ case 'line':
+ $conf = self::getUConfValue($ck, Array('uc_val' => 3));
+ $div = 1;
+ switch($conf['uc_val']){
+ case 4:
+ $div = 1024;
+ case 3:
+ $div *= 1024;
+ case 2:
+ $div *= 1024;
+ case 1:
+ $div *= 1024;
+ }
+
+ foreach($elements as $user => $data){
+ $return_tmp = '{"name":"' . $user . '","data":[';
+ foreach($data as $number){
+ $return_tmp .= round($number/$div, 2) . ",";
+ }
+ $return_tmp = substr($return_tmp, 0, -1) . "]}";
+
+ $return .= $return_tmp . $data_sep;
+ }
+ $return = substr($return, 0, -(strlen($data_sep)));
+ break;
+ }
+ return $return;
+ }
+
+ /**
+ * Get data by user for Seven Days Line Chart
+ * @param $user the user
+ * @return Array
+ */
+ private static function getDataByUserToLineChart($user){
+ $dates = Array(
+ mktime(0,0,0,date('m'),date('d')-6),
+ mktime(0,0,0,date('m'),date('d')-5),
+ mktime(0,0,0,date('m'),date('d')-4),
+ mktime(0,0,0,date('m'),date('d')-3),
+ mktime(0,0,0,date('m'),date('d')-2),
+ mktime(0,0,0,date('m'),date('d')-1),
+ mktime(0,0,0,date('m'),date('d'))
+ );
+
+ $return = Array();
+ foreach($dates as $kd => $date){
+ $query = OCP\DB::prepare("SELECT stc_used FROM *PREFIX*dlstcharts WHERE oc_uid = ? AND stc_dayts = ?");
+ $result = $query->execute(Array($user, $date))->fetchAll();
+
+ if(count($result) > 0){
+ $return[] = $result[0]['stc_used'];
+ }else{
+ if($kd == 0){
+ $query = OCP\DB::prepare("SELECT stc_used FROM *PREFIX*dlstcharts WHERE oc_uid = ? AND stc_dayts < ? ORDER BY stc_dayts DESC");
+ $result = $query->execute(Array($user, $date))->fetchAll();
+
+ if(count($result) > 0){
+ $return[] = $result[0]['stc_used'];
+ }else{
+ $return[] = 0;
+ }
+ }else{
+ $return[] = 0;
+ }
+ }
+ }
+
+ $last = 0;
+ foreach ($return as $key => $value) {
+ if($value == 0){
+ $return[$key] = $last;
+ }
+ $last = $return[$key];
+ }
+ return $return;
+ }
+
+ /**
+ * Get data by users for monthly evolution
+ * @param $user The user
+ * @return Array
+ */
+ private static function getDataByUserToHistoChart($user){
+ $months = Array(
+ date('Ym',mktime(0,0,0,date('m')-11)),
+ date('Ym',mktime(0,0,0,date('m')-10)),
+ date('Ym',mktime(0,0,0,date('m')-9)),
+ date('Ym',mktime(0,0,0,date('m')-8)),
+ date('Ym',mktime(0,0,0,date('m')-7)),
+ date('Ym',mktime(0,0,0,date('m')-6)),
+ date('Ym',mktime(0,0,0,date('m')-5)),
+ date('Ym',mktime(0,0,0,date('m')-4)),
+ date('Ym',mktime(0,0,0,date('m')-3)),
+ date('Ym',mktime(0,0,0,date('m')-2)),
+ date('Ym',mktime(0,0,0,date('m')-1)),
+ date('Ym',mktime(0,0,0,date('m')))
+ );
+
+ $return = Array();
+ foreach($months as $km => $month){
+ $query = OCP\DB::prepare("SELECT AVG(stc_used) as stc_used FROM *PREFIX*dlstcharts WHERE oc_uid = ? AND stc_month = ?");
+ $result = $query->execute(Array($user, $month))->fetchAll();
+
+ if(count($result) > 0){
+ $return[] = $result[0]['stc_used'];
+ }else{
+ $return[] = 0;
+ }
+ }
+
+ $last = 0;
+ foreach ($return as $key => $value) {
+ if($value == 0){
+ $return[$key] = $last;
+ }
+ $last = $return[$key];
+ }
+ return $return;
+ }
+
+} \ No newline at end of file
diff --git a/apps/storage_charts/lib/loader.class.php b/apps/storage_charts/lib/loader.class.php
new file mode 100644
index 00000000000..3df5abe5842
--- /dev/null
+++ b/apps/storage_charts/lib/loader.class.php
@@ -0,0 +1,102 @@
+<?php
+
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+/**
+ * This class load charts for storage_charts.
+ */
+class OC_DLStChartsLoader {
+
+ private static $l;
+
+ /**
+ * Loader
+ * @param $chart_id The chart iD
+ * @return String
+ */
+ public static function loadChart($chart_id, $l){
+ self::$l = $l;
+
+ switch($chart_id){
+ case 'cpie_rfsus':
+ return self::loadPieFreeUsedSpaceRatioChart();
+ break;
+ case 'clines_usse':
+ return self::loadLinesLastSevenDaysUsedSpaceChart();
+ break;
+ case 'chisto_us':
+ return self::loadHistoMonthlyUsedSpaceChart();
+ break;
+ }
+ }
+
+ /**
+ * Get free/used space ratio chart
+ * @return String
+ */
+ private static function loadPieFreeUsedSpaceRatioChart(){
+ return 'pierfsus=new Highcharts.Chart({chart:{renderTo:\'pie_rfsus\',backgroundColor:\'#F8F8F8\',plotBackgroundColor:\'#F8F8F8\',plotBorderWidth:false,plotShadow:false},title:{text:\'\'},tooltip:{formatter:function(){return \'<b>\'+this.point.name+\'</b>: \'+(Math.round(this.percentage*100)/100)+\' %\';}},plotOptions:{pie:{allowPointSelect:true,cursor:\'pointer\',dataLabels:{enabled:true,color:\'#000000\',connectorColor:\'#000000\',formatter:function(){return\'<b>\'+this.point.name+\'</b>: \'+Math.round(this.percentage)+\' %\';}}}},series:[{type:\'pie\',name:\'Used-Free space ratio\',data:[' . OC_DLStCharts::arrayParser('pie',OC_DLStCharts::getPieFreeUsedSpaceRatio(), self::$l) . ']}],exporting:{enabled:false}});';
+ }
+
+ /**
+ * Get seven days used space evolution chart
+ * @return String
+ */
+ private static function loadLinesLastSevenDaysUsedSpaceChart(){
+ $units = Array('', 'KB', 'MB', 'GB', 'TB');
+ $u = OC_DLStCharts::getUConfValue('hu_size', Array('uc_val' => 3));
+ $u = $units[$u['uc_val']];
+ return 'linesusse=new Highcharts.Chart({chart:{renderTo:\'lines_usse\',backgroundColor:\'#F8F8F8\',plotBackgroundColor:\'#F8F8F8\',type:\'line\'},title:{text:\'\'},subtitle:{text:\''.self::$l->t('Last 7 days').'\',x:-20},xAxis:{categories:["'.date('m/d', mktime(0,0,0,date('m'),date('d')-6)).'","'.date('m/d', mktime(0,0,0,date('m'),date('d')-5)).'","'.date('m/d', mktime(0,0,0,date('m'),date('d')-4)).'","'.date('m/d', mktime(0,0,0,date('m'),date('d')-3)).'","'.date('m/d', mktime(0,0,0,date('m'),date('d')-2)).'","'.date('m/d', mktime(0,0,0,date('m'),date('d')-1)).'","'.date('m/d', mktime(0,0,0,date('m'),date('d'))).'"]},yAxis:{title:{text:\''.self::$l->t('Used space').' ('.$u.')\'},plotLines:[{value:0,width:1,color:\'#808080\'}],startOnTick:false,min:0},tooltip:{crosshairs:true,formatter:function(){return \'<b>\'+this.series.name+\'</b><br/>\'+this.x+\': \'+this.y+\' '.$u.'\';}},legend:{layout:\'horizontal\',align:\'center\',verticalAlign:\'top\',x:-25,y:40,borderWidth:0},series:['.OC_DLStCharts::arrayParser('line', OC_DLStCharts::getUsedSpaceOverTime('daily'), self::$l).'],exporting:{enabled:false}});';
+ }
+
+ /**
+ * Get monthly used space evolution chart
+ * @return String
+ */
+ private static function loadHistoMonthlyUsedSpaceChart(){
+ $units = Array('', 'KB', 'MB', 'GB', 'TB');
+ $u = OC_DLStCharts::getUConfValue('hu_size_hus', Array('uc_val' => 3));
+ $u = $units[$u['uc_val']];
+
+ $months = self::getMonths();
+
+ return 'histous=new Highcharts.Chart({chart:{renderTo:\'histo_us\',backgroundColor:\'#F8F8F8\',plotBackgroundColor:\'#F8F8F8\',type:\'column\'},title:{text:\'\'},xAxis:{categories:["'.self::$l->t($months[0]).' '.date('Y',mktime(0,0,0,date('m')-11)).'","'.self::$l->t($months[1]).' '.date('Y',mktime(0,0,0,date('m')-10)).'","'.self::$l->t($months[2]).' '.date('Y',mktime(0,0,0,date('m')-9)).'","'.self::$l->t($months[3]).' '.date('Y',mktime(0,0,0,date('m')-8)).'","'.self::$l->t($months[4]).' '.date('Y',mktime(0,0,0,date('m')-7)).'","'.self::$l->t($months[5]).' '.date('Y',mktime(0,0,0,date('m')-6)).'","'.self::$l->t($months[6]).' '.date('Y',mktime(0,0,0,date('m')-5)).'","'.self::$l->t($months[7]).' '.date('Y',mktime(0,0,0,date('m')-4)).'","'.self::$l->t($months[8]).' '.date('Y',mktime(0,0,0,date('m')-3)).'","'.self::$l->t($months[9]).' '.date('Y',mktime(0,0,0,date('m')-2)).'","'.self::$l->t($months[10]).' '.date('Y',mktime(0,0,0,date('m')-1)).'","'.self::$l->t($months[11]).' '.date('Y',mktime(0,0,0,date('m'))).'"]},yAxis:{min:0,title:{text:\''.self::$l->t('Average used space').' ('.$u.')\'},stackLabels:{enabled:true,style:{fontWeight:\'bold\',color:(Highcharts.theme&&Highcharts.theme.textColor)||\'gray\'},formatter:function(){return(Math.round(this.total*100)/100);}}},legend:{align:\'center\',x:-20,verticalAlign:\'top\',y:20,floating:true,backgroundColor:(Highcharts.theme&&Highcharts.theme.legendBackgroundColorSolid)||\'white\',borderColor:\'#CCC\',borderWidth:1,shadow:false},tooltip:{formatter:function(){return \'<b>\'+this.x+\'</b><br/>\'+this.series.name+\': \'+(Math.round(this.y*100)/100)+\' '.$u.'<br/>\'+\'Total: \'+(Math.round(this.point.stackTotal*100)/100)+\' '.$u.'\';}},plotOptions:{column:{stacking:\'normal\',dataLabels:{enabled:false,color:(Highcharts.theme&&Highcharts.theme.dataLabelsColor)||\'white\'}}},series:['.OC_DLStCharts::arrayParser('histo',OC_DLStCharts::getUsedSpaceOverTime('monthly'),self::$l,',','hu_size_hus').'],exporting:{enabled:false}});';
+ }
+
+ /**
+ * Get months
+ */
+ private static function getMonths(){
+ $months = Array('January','February','March','April','May','June','July','August','September','October','November','December');
+
+ $tmp = Array();
+ for($i=date('n');$i<12;$i++){
+ $tmp[] = $months[$i];
+ }
+ for($i=0;$i<date('n');$i++){
+ $tmp[] = $months[$i];
+ }
+
+ return $tmp;
+ }
+
+} \ No newline at end of file
diff --git a/apps/storage_charts/settings.php b/apps/storage_charts/settings.php
new file mode 100644
index 00000000000..6a436844525
--- /dev/null
+++ b/apps/storage_charts/settings.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+OCP\User::checkLoggedIn();
+OCP\App::checkAppEnabled('storage_charts');
+
+$tmpl = new OCP\Template('storage_charts', 'settings.tpl');
+
+if(isset($_POST['storage_charts_disp']) && count($_POST['storage_charts_disp']) <= 3){
+ $c = $_POST['storage_charts_disp'];
+ $c_disp = Array('cpie_rfsus'=>0,'clines_usse'=>0,'chisto_us'=>0);
+ foreach(array_keys($c_disp) as $chart){
+ if(in_array($chart, $c)){
+ $c_disp[$chart] = 1;
+ }
+ }
+ OC_DLStCharts::setUConfValue('c_disp', serialize($c_disp));
+ $tmpl->assign('stc_save_ok', TRUE);
+}
+
+$displays = OC_DLStCharts::getUConfValue('c_disp', Array('uc_val' => 'a:3:{s:10:"cpie_rfsus";i:1;s:11:"clines_usse";i:1;s:9:"chisto_us";i:1;}'));
+$tmpl->assign('displays', unserialize($displays['uc_val']));
+return $tmpl->fetchPage(); \ No newline at end of file
diff --git a/apps/storage_charts/templates/charts.tpl.php b/apps/storage_charts/templates/charts.tpl.php
new file mode 100644
index 00000000000..98d20706dfe
--- /dev/null
+++ b/apps/storage_charts/templates/charts.tpl.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+* JS minified by http://fmarcia.info/jsmin/test.html
+*
+*/
+
+OCP\Util::addStyle('storage_charts', 'styles');
+OCP\Util::addScript('storage_charts', 'highCharts-2.2.1/highcharts.min');
+OCP\Util::addScript('3rdparty','chosen/chosen.jquery.min');
+OCP\Util::addStyle('3rdparty','chosen');
+OCP\Util::addScript('storage_charts', 'units.min');
+
+?>
+
+<script type="text/javascript">
+ $(document).ready(function(){
+ $('#stc_sortable').sortable({
+ axis:'y',handle:'h3',placeholder:'ui-state-highlight',update:function(e,u){
+ $.ajax({
+ type:'POST',
+ url:OC.linkTo('storage_charts','ajax/config.php'),
+ dataType:'json',
+ data:{o:'set',k:'sc_sort',i:$('#stc_sortable').sortable('toArray')},
+ async:true
+ });
+ }
+ });
+ $('#stc_sortable').disableSelection();
+ });
+</script>
+
+<div id="storage-charts">
+ <div class="personalblock topblock titleblock">
+ DjazzLab Storage Charts<span><?php print($l->t('Drag\'N\'Drop on the chart title to re-order')); ?></span>
+ </div>
+</div>
+<div id="stc_frame">
+ <div id="stc_sortable">
+ <?php foreach($_['sc_sort'] as $sc_sort){
+ if(strcmp($sc_sort, 'cpie_rfsus') == 0){
+ $sc_sort_title = 'Current ratio free space / used space';
+ }elseif(strcmp($sc_sort, 'clines_usse') == 0){
+ $sc_sort_title = 'Daily Used Space Evolution';
+ }else{
+ $sc_sort_title = 'Monthly Used Space Evolution';
+ }
+ if($_['c_disp'][$sc_sort]){ ?>
+ <div id="<?php print($sc_sort); ?>" class="personalblock">
+ <h3><img src="<?php print(OCP\Util::imagePath('storage_charts', 'move.png')); ?>" /><?php print($l->t($sc_sort_title).' '.$l->t('for')); ?> "<?php print(OC_Group::inGroup(OCP\User::getUser(), 'admin')?$l->t('all users'):OCP\User::getUser()); ?>"</h3>
+ <div id="<?php print(substr($sc_sort, 1)); ?>" style="max-width:100%;height:400px;margin:0 auto"></div>
+ <script type="text/javascript">$(document).ready(function(){<?php print(OC_DLStChartsLoader::loadChart($sc_sort, $l)); ?>});</script>
+ </div>
+ <?php }
+ } ?>
+ </div>
+ <?php if($_['c_disp']['clines_usse']){print('<script type="text/javascript">$(document).ready(function(){getLinesUsseUnitsSelect('.$_['hu_size'].');});</script>');}
+ if($_['c_disp']['chisto_us']){print('<script type="text/javascript">$(document).ready(function(){getHistoUsUnitsSelect(' . $_['hu_size_hus'] . ');});</script>');} ?>
+</div> \ No newline at end of file
diff --git a/apps/storage_charts/templates/settings.tpl.php b/apps/storage_charts/templates/settings.tpl.php
new file mode 100644
index 00000000000..6b86850c938
--- /dev/null
+++ b/apps/storage_charts/templates/settings.tpl.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+* ownCloud - DjazzLab Storage Charts plugin
+*
+* @author Xavier Beurois
+* @copyright 2012 Xavier Beurois www.djazz-lab.net
+*
+* 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 Lesser General Public
+* License along with this library. If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+?>
+<form id="storage_charts" method="POST" action="<?php print(OC_Helper::linkTo('settings','personal.php')); ?>">
+ <fieldset class="personalblock">
+ <strong>DjazzLab Storage Charts</strong><span style="margin-left:10px;color:#BBB;font-style:italic;"><?php print($l->t('Uncheck charts you do not want to display')); ?></span>
+ <?php foreach($_['displays'] as $chart => $is_enable){
+ if(strcmp($chart, 'cpie_rfsus') == 0){
+ $chart_title = 'Current ratio free space / used space';
+ }elseif(strcmp($chart, 'clines_usse') == 0){
+ $chart_title = 'Daily Used Space Evolution';
+ }else{
+ $chart_title = 'Monthly Used Space Evolution';
+ } ?>
+ <div><input type="checkbox" name="storage_charts_disp[]" id="<?php print($chart); ?>_e" style="margin-right:10px;"<?php print($is_enable?' checked':'') ?> value="<?php print($chart); ?>" /><?php print($l->t($chart_title)); ?></div>
+ <?php } ?>
+ <input type="submit" value="<?php print($l->t('Save')); ?>" /><span style="color:#00A220;"><?php if(isset($_['stc_save_ok'])){print($l->t('Save OK'));} ?></span>
+ </fieldset>
+</form>
diff --git a/apps/tal/.gitignore b/apps/tal/.gitignore
new file mode 100644
index 00000000000..e2ff07d14d8
--- /dev/null
+++ b/apps/tal/.gitignore
@@ -0,0 +1,51 @@
+# the default generated dir + db file
+data
+owncloud
+config/config.php
+config/mount.php
+apps/inc.php
+
+# just sane ignores
+.*.sw[po]
+*.bak
+*.BAK
+*~
+*.orig
+*.class
+.cvsignore
+Thumbs.db
+*.py[co]
+_darcs/*
+CVS/*
+.svn/*
+RCS/*
+
+# kdevelop
+.kdev
+*.kdev4
+
+# Lokalize
+*lokalize*
+
+# eclipse
+.project
+.settings
+
+# netbeans
+nbproject
+
+# geany
+*.geany
+
+# Cloud9IDE
+.settings.xml
+
+# vim ex mode
+.vimrc
+
+# Mac OS
+.DS_Store
+
+# WebFinger
+.well-known
+/.buildpath
diff --git a/apps/tal/COPYING-AGPL b/apps/tal/COPYING-AGPL
new file mode 100644
index 00000000000..dba13ed2ddf
--- /dev/null
+++ b/apps/tal/COPYING-AGPL
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/apps/tal/COPYING-README b/apps/tal/COPYING-README
new file mode 100644
index 00000000000..18f06caa3ec
--- /dev/null
+++ b/apps/tal/COPYING-README
@@ -0,0 +1,10 @@
+Files in TAL Templating System for ownCloud are licensed under the Affero General Public License version 3,
+the text of which can be found in COPYING-AGPL, or any later version of the AGPL,
+unless otherwise noted.
+
+Licensing of components:
+* PHPTAL - http://phptal.org/ : LGPL
+
+All unmodified files from these and other sources retain their original copyright
+and license notices: see the relevant individual files.
+
diff --git a/apps/tal/README.md b/apps/tal/README.md
new file mode 100644
index 00000000000..756221c07a6
--- /dev/null
+++ b/apps/tal/README.md
@@ -0,0 +1,26 @@
+# TAL Page Templates for ownCloud
+
+Develop using the Template Attribute Language instead of having clumsy
+`<?php echo $var; ?>` tags in your markup.
+
+Read more at the [PHPTAL site](http://phptal.org/introduction.html)
+
+## Install from git
+
+1. Go to your ownCloud apps dir and clone the repo there:
+ <pre>
+ cd owncloud/apps
+ git clone git://github.com/tanghus/tal.git
+ </pre>
+
+2. Go to the `lib`directory of the newly created `tal` folder and clone the [PHPTAL repo](https://github.com/pornel/PHPTAL):
+
+ <pre>
+ cd tal/lib
+ git clone git://github.com/pornel/PHPTAL.git
+ </pre>
+
+3. From your browser go to the ownCloud apps page (`/settings/apps.php`) and enable the "TAL Page Templates for ownCloud" app.
+
+4. Go to the Personal page (`/settings/personal.php`) and check if the installation has succeeded. You will find a section with a link to the manual.
+
diff --git a/apps/tal/TODO b/apps/tal/TODO
new file mode 100644
index 00000000000..ef1e495249d
--- /dev/null
+++ b/apps/tal/TODO
@@ -0,0 +1,2 @@
+TODO
+- Create script to extract translatable strings.
diff --git a/apps/tal/ajax/loadpage.php b/apps/tal/ajax/loadpage.php
new file mode 100644
index 00000000000..f6a1499f454
--- /dev/null
+++ b/apps/tal/ajax/loadpage.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Copyright (c) 2011 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('tal');
+
+$id = isset($_GET['id'])?trim(strip_tags($_GET['id'])):'';
+
+if($id) {
+ $tmpl = new OC_TALTemplate('tal', 'sections');
+ $tmpl->assign('id',$id);
+ $page = $tmpl->fetchPage();
+ OCP\JSON::success(array('data' => array('id'=>$id, 'page'=>$page)));
+ exit();
+} else {
+ $l10n = new OC_L10N('tal');
+ OCP\JSON::error(array('data' => array('message' => $l10n->t('Page name missing from request.'))));
+} \ No newline at end of file
diff --git a/apps/tal/appinfo/app.php b/apps/tal/appinfo/app.php
new file mode 100644
index 00000000000..65c1c4b5bd5
--- /dev/null
+++ b/apps/tal/appinfo/app.php
@@ -0,0 +1,11 @@
+<?php
+OC::$CLASSPATH['OC_TALTemplate'] = 'tal/lib/taltemplate.php';
+OC::$CLASSPATH['OC_TALL10N'] = 'tal/lib/tall10n.php';
+OC::$CLASSPATH['PHPTAL'] = 'tal/lib/PHPTAL/classes/PHPTAL.php';
+OC::$CLASSPATH['PHPTAL_TranslationService'] = 'tal/lib/PHPTAL/classes/PHPTAL/TranslationService.php';
+
+OCP\App::register( array(
+ 'id' => 'tal',
+ 'name' => 'TAL Page Templates' ));
+
+OCP\App::registerPersonal('tal','settings'); \ No newline at end of file
diff --git a/apps/tal/appinfo/info.xml b/apps/tal/appinfo/info.xml
new file mode 100644
index 00000000000..40224f29ff3
--- /dev/null
+++ b/apps/tal/appinfo/info.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<info>
+ <id>tal</id>
+ <name>TAL Page Templates for ownCloud</name>
+ <description>This is an alternative to OC_Template and OC_L10N - the templating and internationalization classes used in ownCloud.
+ </description>
+ <version>0.1</version>
+ <licence>AGPL</licence>
+ <author>Thomas Tanghus</author>
+ <require>5</require>
+</info>
diff --git a/apps/tal/css/tal.css b/apps/tal/css/tal.css
new file mode 100644
index 00000000000..ed51bc5038d
--- /dev/null
+++ b/apps/tal/css/tal.css
@@ -0,0 +1,25 @@
+#manual {
+ float: left;
+ position: absolute; left: 0; top: 0; right: 0; bottom: 0;
+ margin: 0.5em;
+ padding: 1em;
+ overflow: auto;
+ background-color :#f8f8f8;
+ color: #555;
+ border: 1px solid #ddd;
+ border-radius: 0.5em 0.5em 0.5em 0.5em;
+ -webkit-transition:background-color 200ms; -moz-transition:background-color 200ms; -o-transition:background-color 200ms; transition:background-color 200ms;
+}
+#manual nav { position: fixed; top: 5em; width: 15%; font-weight: bold; font-size: 1.2em; }
+#manual ol { list-style: decimal; padding-left: 3em; }
+#manual section { float:right; width: 80%; height: 95%; padding: 1em; font-size: 1.2em; }
+#manual a { color: #555; }
+#manual a:hover { color: #333; }
+#manual li:hover { color: #333; background-color:#eee; }
+code, pre { font-family: "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", monospace; }
+code { margin: 0; padding: 0; }
+pre { overflow:auto; display:block; margin: 1em; padding: 1em; background-color:#f5f5f5; border:1px solid rgba(0, 0, 0, 0.15); border-radius:4px;white-space:pre;}
+h1 { font-size: 1.6em; font-weight: bold; padding: 0.2em 0.2em 0.8em 0.2em; }
+h2 { font-size: 1.2em; font-weight: bold; padding: 1em 0.2em 0.8em 0em; }
+h3 { font-size: 1em; font-weight: bold; }
+.readmore { font-weight: bold; }
diff --git a/apps/tal/index.php b/apps/tal/index.php
new file mode 100644
index 00000000000..d847195dc9f
--- /dev/null
+++ b/apps/tal/index.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+// Check if we are a user
+OCP\User::checkLoggedIn();
+OCP\App::checkAppEnabled('tal');
+
+OCP\Util::addscript('tal','tal');
+OCP\Util::addscript('tal','modernizr');
+OCP\Util::addStyle('tal','tal');
+
+$sections = array(
+ array('id' => 'intro', 'title' => 'Introduction'),
+ array('id' => 'example-1', 'title' => 'A simple example'),
+ array('id' => 'gotchas', 'title' => 'Caveats & Gotchas'),
+ array('id' => 'ref', 'title' => 'References'),
+ );
+$page = isset($_GET['page'])?trim(strip_tags($_GET['page'])):$sections[0]['id'];
+
+$tmpl = new OC_TALTemplate('tal', 'manual', 'user');
+$tmpl->assign('application', 'TAL');
+$tmpl->assign('page', $page);
+$tmpl->assign('sections', $sections);
+$tmpl->printPage();
+?>
diff --git a/apps/tal/js/modernizr.js b/apps/tal/js/modernizr.js
new file mode 100644
index 00000000000..c1a6a9a51d0
--- /dev/null
+++ b/apps/tal/js/modernizr.js
@@ -0,0 +1,1265 @@
+/*!
+ * Modernizr v2.5.3
+ * www.modernizr.com
+ *
+ * Copyright (c) Faruk Ates, Paul Irish, Alex Sexton
+ * Available under the BSD and MIT licenses: www.modernizr.com/license/
+ */
+
+/*
+ * Modernizr tests which native CSS3 and HTML5 features are available in
+ * the current UA and makes the results available to you in two ways:
+ * as properties on a global Modernizr object, and as classes on the
+ * <html> element. This information allows you to progressively enhance
+ * your pages with a granular level of control over the experience.
+ *
+ * Modernizr has an optional (not included) conditional resource loader
+ * called Modernizr.load(), based on Yepnope.js (yepnopejs.com).
+ * To get a build that includes Modernizr.load(), as well as choosing
+ * which tests to include, go to www.modernizr.com/download/
+ *
+ * Authors Faruk Ates, Paul Irish, Alex Sexton
+ * Contributors Ryan Seddon, Ben Alman
+ */
+
+window.Modernizr = (function( window, document, undefined ) {
+
+ var version = '2.5.3',
+
+ Modernizr = {},
+
+ // option for enabling the HTML classes to be added
+ enableClasses = true,
+
+ docElement = document.documentElement,
+
+ /**
+ * Create our "modernizr" element that we do most feature tests on.
+ */
+ mod = 'modernizr',
+ modElem = document.createElement(mod),
+ mStyle = modElem.style,
+
+ /**
+ * Create the input element for various Web Forms feature tests.
+ */
+ inputElem = document.createElement('input'),
+
+ smile = ':)',
+
+ toString = {}.toString,
+
+ // List of property values to set for css tests. See ticket #21
+ prefixes = ' -webkit- -moz- -o- -ms- '.split(' '),
+
+ // Following spec is to expose vendor-specific style properties as:
+ // elem.style.WebkitBorderRadius
+ // and the following would be incorrect:
+ // elem.style.webkitBorderRadius
+
+ // Webkit ghosts their properties in lowercase but Opera & Moz do not.
+ // Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+
+ // erik.eae.net/archives/2008/03/10/21.48.10/
+
+ // More here: github.com/Modernizr/Modernizr/issues/issue/21
+ omPrefixes = 'Webkit Moz O ms',
+
+ cssomPrefixes = omPrefixes.split(' '),
+
+ domPrefixes = omPrefixes.toLowerCase().split(' '),
+
+ ns = {'svg': 'http://www.w3.org/2000/svg'},
+
+ tests = {},
+ inputs = {},
+ attrs = {},
+
+ classes = [],
+
+ slice = classes.slice,
+
+ featureName, // used in testing loop
+
+
+ // Inject element with style element and some CSS rules
+ injectElementWithStyles = function( rule, callback, nodes, testnames ) {
+
+ var style, ret, node,
+ div = document.createElement('div'),
+ // After page load injecting a fake body doesn't work so check if body exists
+ body = document.body,
+ // IE6 and 7 won't return offsetWidth or offsetHeight unless it's in the body element, so we fake it.
+ fakeBody = body ? body : document.createElement('body');
+
+ if ( parseInt(nodes, 10) ) {
+ // In order not to give false positives we create a node for each test
+ // This also allows the method to scale for unspecified uses
+ while ( nodes-- ) {
+ node = document.createElement('div');
+ node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
+ div.appendChild(node);
+ }
+ }
+
+ // <style> elements in IE6-9 are considered 'NoScope' elements and therefore will be removed
+ // when injected with innerHTML. To get around this you need to prepend the 'NoScope' element
+ // with a 'scoped' element, in our case the soft-hyphen entity as it won't mess with our measurements.
+ // msdn.microsoft.com/en-us/library/ms533897%28VS.85%29.aspx
+ // Documents served as xml will throw if using &shy; so use xml friendly encoded version. See issue #277
+ style = ['&#173;','<style>', rule, '</style>'].join('');
+ div.id = mod;
+ // IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody.
+ // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270
+ fakeBody.innerHTML += style;
+ fakeBody.appendChild(div);
+ if(!body){
+ //avoid crashing IE8, if background image is used
+ fakeBody.style.background = "";
+ docElement.appendChild(fakeBody);
+ }
+
+ ret = callback(div, rule);
+ // If this is done after page load we don't want to remove the body so check if body exists
+ !body ? fakeBody.parentNode.removeChild(fakeBody) : div.parentNode.removeChild(div);
+
+ return !!ret;
+
+ },
+
+
+ // adapted from matchMedia polyfill
+ // by Scott Jehl and Paul Irish
+ // gist.github.com/786768
+ testMediaQuery = function( mq ) {
+
+ var matchMedia = window.matchMedia || window.msMatchMedia;
+ if ( matchMedia ) {
+ return matchMedia(mq).matches;
+ }
+
+ var bool;
+
+ injectElementWithStyles('@media ' + mq + ' { #' + mod + ' { position: absolute; } }', function( node ) {
+ bool = (window.getComputedStyle ?
+ getComputedStyle(node, null) :
+ node.currentStyle)['position'] == 'absolute';
+ });
+
+ return bool;
+
+ },
+
+
+ /**
+ * isEventSupported determines if a given element supports the given event
+ * function from yura.thinkweb2.com/isEventSupported/
+ */
+ isEventSupported = (function() {
+
+ var TAGNAMES = {
+ 'select': 'input', 'change': 'input',
+ 'submit': 'form', 'reset': 'form',
+ 'error': 'img', 'load': 'img', 'abort': 'img'
+ };
+
+ function isEventSupported( eventName, element ) {
+
+ element = element || document.createElement(TAGNAMES[eventName] || 'div');
+ eventName = 'on' + eventName;
+
+ // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those
+ var isSupported = eventName in element;
+
+ if ( !isSupported ) {
+ // If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element
+ if ( !element.setAttribute ) {
+ element = document.createElement('div');
+ }
+ if ( element.setAttribute && element.removeAttribute ) {
+ element.setAttribute(eventName, '');
+ isSupported = is(element[eventName], 'function');
+
+ // If property was created, "remove it" (by setting value to `undefined`)
+ if ( !is(element[eventName], 'undefined') ) {
+ element[eventName] = undefined;
+ }
+ element.removeAttribute(eventName);
+ }
+ }
+
+ element = null;
+ return isSupported;
+ }
+ return isEventSupported;
+ })();
+
+ // hasOwnProperty shim by kangax needed for Safari 2.0 support
+ var _hasOwnProperty = ({}).hasOwnProperty, hasOwnProperty;
+ if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) {
+ hasOwnProperty = function (object, property) {
+ return _hasOwnProperty.call(object, property);
+ };
+ }
+ else {
+ hasOwnProperty = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */
+ return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
+ };
+ }
+
+ // Taken from ES5-shim https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js
+ // ES-5 15.3.4.5
+ // http://es5.github.com/#x15.3.4.5
+
+ if (!Function.prototype.bind) {
+
+ Function.prototype.bind = function bind(that) {
+
+ var target = this;
+
+ if (typeof target != "function") {
+ throw new TypeError();
+ }
+
+ var args = slice.call(arguments, 1),
+ bound = function () {
+
+ if (this instanceof bound) {
+
+ var F = function(){};
+ F.prototype = target.prototype;
+ var self = new F;
+
+ var result = target.apply(
+ self,
+ args.concat(slice.call(arguments))
+ );
+ if (Object(result) === result) {
+ return result;
+ }
+ return self;
+
+ } else {
+
+ return target.apply(
+ that,
+ args.concat(slice.call(arguments))
+ );
+
+ }
+
+ };
+
+ return bound;
+ };
+ }
+
+ /**
+ * setCss applies given styles to the Modernizr DOM node.
+ */
+ function setCss( str ) {
+ mStyle.cssText = str;
+ }
+
+ /**
+ * setCssAll extrapolates all vendor-specific css strings.
+ */
+ function setCssAll( str1, str2 ) {
+ return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
+ }
+
+ /**
+ * is returns a boolean for if typeof obj is exactly type.
+ */
+ function is( obj, type ) {
+ return typeof obj === type;
+ }
+
+ /**
+ * contains returns a boolean for if substr is found within str.
+ */
+ function contains( str, substr ) {
+ return !!~('' + str).indexOf(substr);
+ }
+
+ /**
+ * testProps is a generic CSS / DOM property test; if a browser supports
+ * a certain property, it won't return undefined for it.
+ * A supported CSS property returns empty string when its not yet set.
+ */
+ function testProps( props, prefixed ) {
+ for ( var i in props ) {
+ if ( mStyle[ props[i] ] !== undefined ) {
+ return prefixed == 'pfx' ? props[i] : true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * testDOMProps is a generic DOM property test; if a browser supports
+ * a certain property, it won't return undefined for it.
+ */
+ function testDOMProps( props, obj, elem ) {
+ for ( var i in props ) {
+ var item = obj[props[i]];
+ if ( item !== undefined) {
+
+ // return the property name as a string
+ if (elem === false) return props[i];
+
+ // let's bind a function
+ if (is(item, 'function')){
+ // default to autobind unless override
+ return item.bind(elem || obj);
+ }
+
+ // return the unbound function or obj or value
+ return item;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * testPropsAll tests a list of DOM properties we want to check against.
+ * We specify literally ALL possible (known and/or likely) properties on
+ * the element including the non-vendor prefixed one, for forward-
+ * compatibility.
+ */
+ function testPropsAll( prop, prefixed, elem ) {
+
+ var ucProp = prop.charAt(0).toUpperCase() + prop.substr(1),
+ props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
+
+ // did they call .prefixed('boxSizing') or are we just testing a prop?
+ if(is(prefixed, "string") || is(prefixed, "undefined")) {
+ return testProps(props, prefixed);
+
+ // otherwise, they called .prefixed('requestAnimationFrame', window[, elem])
+ } else {
+ props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
+ return testDOMProps(props, prefixed, elem);
+ }
+ }
+
+ /**
+ * testBundle tests a list of CSS features that require element and style injection.
+ * By bundling them together we can reduce the need to touch the DOM multiple times.
+ */
+ /*>>testBundle*/
+ var testBundle = (function( styles, tests ) {
+ var style = styles.join(''),
+ len = tests.length;
+
+ injectElementWithStyles(style, function( node, rule ) {
+ var style = document.styleSheets[document.styleSheets.length - 1],
+ // IE8 will bork if you create a custom build that excludes both fontface and generatedcontent tests.
+ // So we check for cssRules and that there is a rule available
+ // More here: github.com/Modernizr/Modernizr/issues/288 & github.com/Modernizr/Modernizr/issues/293
+ cssText = style ? (style.cssRules && style.cssRules[0] ? style.cssRules[0].cssText : style.cssText || '') : '',
+ children = node.childNodes, hash = {};
+
+ while ( len-- ) {
+ hash[children[len].id] = children[len];
+ }
+
+ /*>>touch*/ Modernizr['touch'] = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch || (hash['touch'] && hash['touch'].offsetTop) === 9; /*>>touch*/
+ /*>>csstransforms3d*/ Modernizr['csstransforms3d'] = (hash['csstransforms3d'] && hash['csstransforms3d'].offsetLeft) === 9 && hash['csstransforms3d'].offsetHeight === 3; /*>>csstransforms3d*/
+ /*>>generatedcontent*/Modernizr['generatedcontent'] = (hash['generatedcontent'] && hash['generatedcontent'].offsetHeight) >= 1; /*>>generatedcontent*/
+ /*>>fontface*/ Modernizr['fontface'] = /src/i.test(cssText) &&
+ cssText.indexOf(rule.split(' ')[0]) === 0; /*>>fontface*/
+ }, len, tests);
+
+ })([
+ // Pass in styles to be injected into document
+ /*>>fontface*/ '@font-face {font-family:"font";src:url("https://")}' /*>>fontface*/
+
+ /*>>touch*/ ,['@media (',prefixes.join('touch-enabled),('),mod,')',
+ '{#touch{top:9px;position:absolute}}'].join('') /*>>touch*/
+
+ /*>>csstransforms3d*/ ,['@media (',prefixes.join('transform-3d),('),mod,')',
+ '{#csstransforms3d{left:9px;position:absolute;height:3px;}}'].join('')/*>>csstransforms3d*/
+
+ /*>>generatedcontent*/,['#generatedcontent:after{content:"',smile,'";visibility:hidden}'].join('') /*>>generatedcontent*/
+ ],
+ [
+ /*>>fontface*/ 'fontface' /*>>fontface*/
+ /*>>touch*/ ,'touch' /*>>touch*/
+ /*>>csstransforms3d*/ ,'csstransforms3d' /*>>csstransforms3d*/
+ /*>>generatedcontent*/,'generatedcontent' /*>>generatedcontent*/
+
+ ]);/*>>testBundle*/
+
+
+ /**
+ * Tests
+ * -----
+ */
+
+ // The *new* flexbox
+ // dev.w3.org/csswg/css3-flexbox
+
+ tests['flexbox'] = function() {
+ return testPropsAll('flexOrder');
+ };
+
+ // The *old* flexbox
+ // www.w3.org/TR/2009/WD-css3-flexbox-20090723/
+
+ tests['flexbox-legacy'] = function() {
+ return testPropsAll('boxDirection');
+ };
+
+ // On the S60 and BB Storm, getContext exists, but always returns undefined
+ // so we actually have to call getContext() to verify
+ // github.com/Modernizr/Modernizr/issues/issue/97/
+
+ tests['canvas'] = function() {
+ var elem = document.createElement('canvas');
+ return !!(elem.getContext && elem.getContext('2d'));
+ };
+
+ tests['canvastext'] = function() {
+ return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function'));
+ };
+
+ // this test initiates a new webgl context.
+ // webk.it/70117 is tracking a legit feature detect proposal
+
+ tests['webgl'] = function() {
+ try {
+ var canvas = document.createElement('canvas'),
+ ret;
+ ret = !!(window.WebGLRenderingContext && (canvas.getContext('experimental-webgl') || canvas.getContext('webgl')));
+ canvas = undefined;
+ } catch (e){
+ ret = false;
+ }
+ return ret;
+ };
+
+ /*
+ * The Modernizr.touch test only indicates if the browser supports
+ * touch events, which does not necessarily reflect a touchscreen
+ * device, as evidenced by tablets running Windows 7 or, alas,
+ * the Palm Pre / WebOS (touch) phones.
+ *
+ * Additionally, Chrome (desktop) used to lie about its support on this,
+ * but that has since been rectified: crbug.com/36415
+ *
+ * We also test for Firefox 4 Multitouch Support.
+ *
+ * For more info, see: modernizr.github.com/Modernizr/touch.html
+ */
+
+ tests['touch'] = function() {
+ return Modernizr['touch'];
+ };
+
+ /**
+ * geolocation tests for the new Geolocation API specification.
+ * This test is a standards compliant-only test; for more complete
+ * testing, including a Google Gears fallback, please see:
+ * code.google.com/p/geo-location-javascript/
+ * or view a fallback solution using google's geo API:
+ * gist.github.com/366184
+ */
+ tests['geolocation'] = function() {
+ return !!navigator.geolocation;
+ };
+
+ // Per 1.6:
+ // This used to be Modernizr.crosswindowmessaging but the longer
+ // name has been deprecated in favor of a shorter and property-matching one.
+ // The old API is still available in 1.6, but as of 2.0 will throw a warning,
+ // and in the first release thereafter disappear entirely.
+ tests['postmessage'] = function() {
+ return !!window.postMessage;
+ };
+
+
+ // Chrome incognito mode used to throw an exception when using openDatabase
+ // It doesn't anymore.
+ tests['websqldatabase'] = function() {
+ return !!window.openDatabase;
+ };
+
+ // Vendors had inconsistent prefixing with the experimental Indexed DB:
+ // - Webkit's implementation is accessible through webkitIndexedDB
+ // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB
+ // For speed, we don't test the legacy (and beta-only) indexedDB
+ tests['indexedDB'] = function() {
+ return !!testPropsAll("indexedDB",window);
+ };
+
+ // documentMode logic from YUI to filter out IE8 Compat Mode
+ // which false positives.
+ tests['hashchange'] = function() {
+ return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7);
+ };
+
+ // Per 1.6:
+ // This used to be Modernizr.historymanagement but the longer
+ // name has been deprecated in favor of a shorter and property-matching one.
+ // The old API is still available in 1.6, but as of 2.0 will throw a warning,
+ // and in the first release thereafter disappear entirely.
+ tests['history'] = function() {
+ return !!(window.history && history.pushState);
+ };
+
+ tests['draganddrop'] = function() {
+ var div = document.createElement('div');
+ return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
+ };
+
+ // FIXME: Once FF10 is sunsetted, we can drop prefixed MozWebSocket
+ // bugzil.la/695635
+ tests['websockets'] = function() {
+ for ( var i = -1, len = cssomPrefixes.length; ++i < len; ){
+ if ( window[cssomPrefixes[i] + 'WebSocket'] ){
+ return true;
+ }
+ }
+ return 'WebSocket' in window;
+ };
+
+
+ // css-tricks.com/rgba-browser-support/
+ tests['rgba'] = function() {
+ // Set an rgba() color and check the returned value
+
+ setCss('background-color:rgba(150,255,150,.5)');
+
+ return contains(mStyle.backgroundColor, 'rgba');
+ };
+
+ tests['hsla'] = function() {
+ // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally,
+ // except IE9 who retains it as hsla
+
+ setCss('background-color:hsla(120,40%,100%,.5)');
+
+ return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla');
+ };
+
+ tests['multiplebgs'] = function() {
+ // Setting multiple images AND a color on the background shorthand property
+ // and then querying the style.background property value for the number of
+ // occurrences of "url(" is a reliable method for detecting ACTUAL support for this!
+
+ setCss('background:url(https://),url(https://),red url(https://)');
+
+ // If the UA supports multiple backgrounds, there should be three occurrences
+ // of the string "url(" in the return value for elemStyle.background
+
+ return /(url\s*\(.*?){3}/.test(mStyle.background);
+ };
+
+
+ // In testing support for a given CSS property, it's legit to test:
+ // `elem.style[styleName] !== undefined`
+ // If the property is supported it will return an empty string,
+ // if unsupported it will return undefined.
+
+ // We'll take advantage of this quick test and skip setting a style
+ // on our modernizr element, but instead just testing undefined vs
+ // empty string.
+
+
+ tests['backgroundsize'] = function() {
+ return testPropsAll('backgroundSize');
+ };
+
+ tests['borderimage'] = function() {
+ return testPropsAll('borderImage');
+ };
+
+
+ // Super comprehensive table about all the unique implementations of
+ // border-radius: muddledramblings.com/table-of-css3-border-radius-compliance
+
+ tests['borderradius'] = function() {
+ return testPropsAll('borderRadius');
+ };
+
+ // WebOS unfortunately false positives on this test.
+ tests['boxshadow'] = function() {
+ return testPropsAll('boxShadow');
+ };
+
+ // FF3.0 will false positive on this test
+ tests['textshadow'] = function() {
+ return document.createElement('div').style.textShadow === '';
+ };
+
+
+ tests['opacity'] = function() {
+ // Browsers that actually have CSS Opacity implemented have done so
+ // according to spec, which means their return values are within the
+ // range of [0.0,1.0] - including the leading zero.
+
+ setCssAll('opacity:.55');
+
+ // The non-literal . in this regex is intentional:
+ // German Chrome returns this value as 0,55
+ // github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632
+ return /^0.55$/.test(mStyle.opacity);
+ };
+
+
+ // Note, Android < 4 will pass this test, but can only animate
+ // a single property at a time
+ // daneden.me/2011/12/putting-up-with-androids-bullshit/
+ tests['cssanimations'] = function() {
+ return testPropsAll('animationName');
+ };
+
+
+ tests['csscolumns'] = function() {
+ return testPropsAll('columnCount');
+ };
+
+
+ tests['cssgradients'] = function() {
+ /**
+ * For CSS Gradients syntax, please see:
+ * webkit.org/blog/175/introducing-css-gradients/
+ * developer.mozilla.org/en/CSS/-moz-linear-gradient
+ * developer.mozilla.org/en/CSS/-moz-radial-gradient
+ * dev.w3.org/csswg/css3-images/#gradients-
+ */
+
+ var str1 = 'background-image:',
+ str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));',
+ str3 = 'linear-gradient(left top,#9f9, white);';
+
+ setCss(
+ // legacy webkit syntax (FIXME: remove when syntax not in use anymore)
+ (str1 + '-webkit- '.split(' ').join(str2 + str1)
+ // standard syntax // trailing 'background-image:'
+ + prefixes.join(str3 + str1)).slice(0, -str1.length)
+ );
+
+ return contains(mStyle.backgroundImage, 'gradient');
+ };
+
+
+ tests['cssreflections'] = function() {
+ return testPropsAll('boxReflect');
+ };
+
+
+ tests['csstransforms'] = function() {
+ return !!testPropsAll('transform');
+ };
+
+
+ tests['csstransforms3d'] = function() {
+
+ var ret = !!testPropsAll('perspective');
+
+ // Webkit's 3D transforms are passed off to the browser's own graphics renderer.
+ // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in
+ // some conditions. As a result, Webkit typically recognizes the syntax but
+ // will sometimes throw a false positive, thus we must do a more thorough check:
+ if ( ret && 'webkitPerspective' in docElement.style ) {
+
+ // Webkit allows this media query to succeed only if the feature is enabled.
+ // `@media (transform-3d),(-o-transform-3d),(-moz-transform-3d),(-ms-transform-3d),(-webkit-transform-3d),(modernizr){ ... }`
+ ret = Modernizr['csstransforms3d'];
+ }
+ return ret;
+ };
+
+
+ tests['csstransitions'] = function() {
+ return testPropsAll('transition');
+ };
+
+
+ /*>>fontface*/
+ // @font-face detection routine by Diego Perini
+ // javascript.nwbox.com/CSSSupport/
+
+ // false positives in WebOS: github.com/Modernizr/Modernizr/issues/342
+ tests['fontface'] = function() {
+ return Modernizr['fontface'];
+ };
+ /*>>fontface*/
+
+ // CSS generated content detection
+ tests['generatedcontent'] = function() {
+ return Modernizr['generatedcontent'];
+ };
+
+
+
+ // These tests evaluate support of the video/audio elements, as well as
+ // testing what types of content they support.
+ //
+ // We're using the Boolean constructor here, so that we can extend the value
+ // e.g. Modernizr.video // true
+ // Modernizr.video.ogg // 'probably'
+ //
+ // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845
+ // thx to NielsLeenheer and zcorpan
+
+ // Note: in some older browsers, "no" was a return value instead of empty string.
+ // It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2
+ // It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5
+
+ tests['video'] = function() {
+ var elem = document.createElement('video'),
+ bool = false;
+
+ // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224
+ try {
+ if ( bool = !!elem.canPlayType ) {
+ bool = new Boolean(bool);
+ bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,'');
+
+ bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,'');
+
+ bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,'');
+ }
+
+ } catch(e) { }
+
+ return bool;
+ };
+
+ tests['audio'] = function() {
+ var elem = document.createElement('audio'),
+ bool = false;
+
+ try {
+ if ( bool = !!elem.canPlayType ) {
+ bool = new Boolean(bool);
+ bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,'');
+ bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,'');
+
+ // Mimetypes accepted:
+ // developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements
+ // bit.ly/iphoneoscodecs
+ bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,'');
+ bool.m4a = ( elem.canPlayType('audio/x-m4a;') ||
+ elem.canPlayType('audio/aac;')) .replace(/^no$/,'');
+ }
+ } catch(e) { }
+
+ return bool;
+ };
+
+
+ // In FF4, if disabled, window.localStorage should === null.
+
+ // Normally, we could not test that directly and need to do a
+ // `('localStorage' in window) && ` test first because otherwise Firefox will
+ // throw bugzil.la/365772 if cookies are disabled
+
+ // Also in iOS5 Private Browsing mode, attepting to use localStorage.setItem
+ // will throw the exception:
+ // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
+ // Peculiarly, getItem and removeItem calls do not throw.
+
+ // Because we are forced to try/catch this, we'll go aggressive.
+
+ // Just FWIW: IE8 Compat mode supports these features completely:
+ // www.quirksmode.org/dom/html5.html
+ // But IE8 doesn't support either with local files
+
+ tests['localstorage'] = function() {
+ try {
+ localStorage.setItem(mod, mod);
+ localStorage.removeItem(mod);
+ return true;
+ } catch(e) {
+ return false;
+ }
+ };
+
+ tests['sessionstorage'] = function() {
+ try {
+ sessionStorage.setItem(mod, mod);
+ sessionStorage.removeItem(mod);
+ return true;
+ } catch(e) {
+ return false;
+ }
+ };
+
+
+ tests['webworkers'] = function() {
+ return !!window.Worker;
+ };
+
+
+ tests['applicationcache'] = function() {
+ return !!window.applicationCache;
+ };
+
+
+ // Thanks to Erik Dahlstrom
+ tests['svg'] = function() {
+ return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect;
+ };
+
+ // specifically for SVG inline in HTML, not within XHTML
+ // test page: paulirish.com/demo/inline-svg
+ tests['inlinesvg'] = function() {
+ var div = document.createElement('div');
+ div.innerHTML = '<svg/>';
+ return (div.firstChild && div.firstChild.namespaceURI) == ns.svg;
+ };
+
+ // SVG SMIL animation
+ tests['smil'] = function() {
+ return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate')));
+ };
+
+ // This test is only for clip paths in SVG proper, not clip paths on HTML content
+ // demo: srufaculty.sru.edu/david.dailey/svg/newstuff/clipPath4.svg
+
+ // However read the comments to dig into applying SVG clippaths to HTML content here:
+ // github.com/Modernizr/Modernizr/issues/213#issuecomment-1149491
+ tests['svgclippaths'] = function() {
+ return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath')));
+ };
+
+ // input features and input types go directly onto the ret object, bypassing the tests loop.
+ // Hold this guy to execute in a moment.
+ function webforms() {
+ // Run through HTML5's new input attributes to see if the UA understands any.
+ // We're using f which is the <input> element created early on
+ // Mike Taylr has created a comprehensive resource for testing these attributes
+ // when applied to all input types:
+ // miketaylr.com/code/input-type-attr.html
+ // spec: www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary
+
+ // Only input placeholder is tested while textarea's placeholder is not.
+ // Currently Safari 4 and Opera 11 have support only for the input placeholder
+ // Both tests are available in feature-detects/forms-placeholder.js
+ Modernizr['input'] = (function( props ) {
+ for ( var i = 0, len = props.length; i < len; i++ ) {
+ attrs[ props[i] ] = !!(props[i] in inputElem);
+ }
+ if (attrs.list){
+ // safari false positive's on datalist: webk.it/74252
+ // see also github.com/Modernizr/Modernizr/issues/146
+ attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement);
+ }
+ return attrs;
+ })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' '));
+
+ // Run through HTML5's new input types to see if the UA understands any.
+ // This is put behind the tests runloop because it doesn't return a
+ // true/false like all the other tests; instead, it returns an object
+ // containing each input type with its corresponding true/false value
+
+ // Big thanks to @miketaylr for the html5 forms expertise. miketaylr.com/
+ Modernizr['inputtypes'] = (function(props) {
+
+ for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) {
+
+ inputElem.setAttribute('type', inputElemType = props[i]);
+ bool = inputElem.type !== 'text';
+
+ // We first check to see if the type we give it sticks..
+ // If the type does, we feed it a textual value, which shouldn't be valid.
+ // If the value doesn't stick, we know there's input sanitization which infers a custom UI
+ if ( bool ) {
+
+ inputElem.value = smile;
+ inputElem.style.cssText = 'position:absolute;visibility:hidden;';
+
+ if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) {
+
+ docElement.appendChild(inputElem);
+ defaultView = document.defaultView;
+
+ // Safari 2-4 allows the smiley as a value, despite making a slider
+ bool = defaultView.getComputedStyle &&
+ defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' &&
+ // Mobile android web browser has false positive, so must
+ // check the height to see if the widget is actually there.
+ (inputElem.offsetHeight !== 0);
+
+ docElement.removeChild(inputElem);
+
+ } else if ( /^(search|tel)$/.test(inputElemType) ){
+ // Spec doesnt define any special parsing or detectable UI
+ // behaviors so we pass these through as true
+
+ // Interestingly, opera fails the earlier test, so it doesn't
+ // even make it here.
+
+ } else if ( /^(url|email)$/.test(inputElemType) ) {
+ // Real url and email support comes with prebaked validation.
+ bool = inputElem.checkValidity && inputElem.checkValidity() === false;
+
+ } else if ( /^color$/.test(inputElemType) ) {
+ // chuck into DOM and force reflow for Opera bug in 11.00
+ // github.com/Modernizr/Modernizr/issues#issue/159
+ docElement.appendChild(inputElem);
+ docElement.offsetWidth;
+ bool = inputElem.value != smile;
+ docElement.removeChild(inputElem);
+
+ } else {
+ // If the upgraded input compontent rejects the :) text, we got a winner
+ bool = inputElem.value != smile;
+ }
+ }
+
+ inputs[ props[i] ] = !!bool;
+ }
+ return inputs;
+ })('search tel url email datetime date month week time datetime-local number range color'.split(' '));
+ }
+
+
+ // End of test definitions
+ // -----------------------
+
+
+
+ // Run through all tests and detect their support in the current UA.
+ // todo: hypothetically we could be doing an array of tests and use a basic loop here.
+ for ( var feature in tests ) {
+ if ( hasOwnProperty(tests, feature) ) {
+ // run the test, throw the return value into the Modernizr,
+ // then based on that boolean, define an appropriate className
+ // and push it into an array of classes we'll join later.
+ featureName = feature.toLowerCase();
+ Modernizr[featureName] = tests[feature]();
+
+ classes.push((Modernizr[featureName] ? '' : 'no-') + featureName);
+ }
+ }
+
+ // input tests need to run.
+ Modernizr.input || webforms();
+
+
+ /**
+ * addTest allows the user to define their own feature tests
+ * the result will be added onto the Modernizr object,
+ * as well as an appropriate className set on the html element
+ *
+ * @param feature - String naming the feature
+ * @param test - Function returning true if feature is supported, false if not
+ */
+ Modernizr.addTest = function ( feature, test ) {
+ if ( typeof feature == 'object' ) {
+ for ( var key in feature ) {
+ if ( hasOwnProperty( feature, key ) ) {
+ Modernizr.addTest( key, feature[ key ] );
+ }
+ }
+ } else {
+
+ feature = feature.toLowerCase();
+
+ if ( Modernizr[feature] !== undefined ) {
+ // we're going to quit if you're trying to overwrite an existing test
+ // if we were to allow it, we'd do this:
+ // var re = new RegExp("\\b(no-)?" + feature + "\\b");
+ // docElement.className = docElement.className.replace( re, '' );
+ // but, no rly, stuff 'em.
+ return Modernizr;
+ }
+
+ test = typeof test == 'function' ? test() : test;
+
+ docElement.className += ' ' + (test ? '' : 'no-') + feature;
+ Modernizr[feature] = test;
+
+ }
+
+ return Modernizr; // allow chaining.
+ };
+
+
+ // Reset modElem.cssText to nothing to reduce memory footprint.
+ setCss('');
+ modElem = inputElem = null;
+
+ //>>BEGIN IEPP
+ // Enable HTML 5 elements for styling in IE & add HTML5 css
+ /*! HTML5 Shiv v3.4 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed */
+ ;(function(window, document) {
+
+ /** Preset options */
+ var options = window.html5 || {};
+
+ /** Used to skip problem elements */
+ var reSkip = /^<|^(?:button|form|map|select|textarea)$/i;
+
+ /** Detect whether the browser supports default html5 styles */
+ var supportsHtml5Styles;
+
+ /** Detect whether the browser supports unknown elements */
+ var supportsUnknownElements;
+
+ (function() {
+ var a = document.createElement('a');
+
+ a.innerHTML = '<xyz></xyz>';
+
+ //if the hidden property is implemented we can assume, that the browser supports HTML5 Styles
+ supportsHtml5Styles = ('hidden' in a);
+ supportsUnknownElements = a.childNodes.length == 1 || (function() {
+ // assign a false positive if unable to shiv
+ try {
+ (document.createElement)('a');
+ } catch(e) {
+ return true;
+ }
+ var frag = document.createDocumentFragment();
+ return (
+ typeof frag.cloneNode == 'undefined' ||
+ typeof frag.createDocumentFragment == 'undefined' ||
+ typeof frag.createElement == 'undefined'
+ );
+ }());
+
+ }());
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a style sheet with the given CSS text and adds it to the document.
+ * @private
+ * @param {Document} ownerDocument The document.
+ * @param {String} cssText The CSS text.
+ * @returns {StyleSheet} The style element.
+ */
+ function addStyleSheet(ownerDocument, cssText) {
+ var p = ownerDocument.createElement('p'),
+ parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement;
+
+ p.innerHTML = 'x<style>' + cssText + '</style>';
+ return parent.insertBefore(p.lastChild, parent.firstChild);
+ }
+
+ /**
+ * Returns the value of `html5.elements` as an array.
+ * @private
+ * @returns {Array} An array of shived element node names.
+ */
+ function getElements() {
+ var elements = html5.elements;
+ return typeof elements == 'string' ? elements.split(' ') : elements;
+ }
+
+ /**
+ * Shivs the `createElement` and `createDocumentFragment` methods of the document.
+ * @private
+ * @param {Document|DocumentFragment} ownerDocument The document.
+ */
+ function shivMethods(ownerDocument) {
+ var cache = {},
+ docCreateElement = ownerDocument.createElement,
+ docCreateFragment = ownerDocument.createDocumentFragment,
+ frag = docCreateFragment();
+
+ ownerDocument.createElement = function(nodeName) {
+ // Avoid adding some elements to fragments in IE < 9 because
+ // * Attributes like `name` or `type` cannot be set/changed once an element
+ // is inserted into a document/fragment
+ // * Link elements with `src` attributes that are inaccessible, as with
+ // a 403 response, will cause the tab/window to crash
+ // * Script elements appended to fragments will execute when their `src`
+ // or `text` property is set
+ var node = (cache[nodeName] || (cache[nodeName] = docCreateElement(nodeName))).cloneNode();
+ return html5.shivMethods && node.canHaveChildren && !reSkip.test(nodeName) ? frag.appendChild(node) : node;
+ };
+
+ ownerDocument.createDocumentFragment = Function('h,f', 'return function(){' +
+ 'var n=f.cloneNode(),c=n.createElement;' +
+ 'h.shivMethods&&(' +
+ // unroll the `createElement` calls
+ getElements().join().replace(/\w+/g, function(nodeName) {
+ cache[nodeName] = docCreateElement(nodeName);
+ frag.createElement(nodeName);
+ return 'c("' + nodeName + '")';
+ }) +
+ ');return n}'
+ )(html5, frag);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Shivs the given document.
+ * @memberOf html5
+ * @param {Document} ownerDocument The document to shiv.
+ * @returns {Document} The shived document.
+ */
+ function shivDocument(ownerDocument) {
+ var shived;
+ if (ownerDocument.documentShived) {
+ return ownerDocument;
+ }
+ if (html5.shivCSS && !supportsHtml5Styles) {
+ shived = !!addStyleSheet(ownerDocument,
+ // corrects block display not defined in IE6/7/8/9
+ 'article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}' +
+ // corrects audio display not defined in IE6/7/8/9
+ 'audio{display:none}' +
+ // corrects canvas and video display not defined in IE6/7/8/9
+ 'canvas,video{display:inline-block;*display:inline;*zoom:1}' +
+ // corrects 'hidden' attribute and audio[controls] display not present in IE7/8/9
+ '[hidden]{display:none}audio[controls]{display:inline-block;*display:inline;*zoom:1}' +
+ // adds styling not present in IE6/7/8/9
+ 'mark{background:#FF0;color:#000}'
+ );
+ }
+ if (!supportsUnknownElements) {
+ shived = !shivMethods(ownerDocument);
+ }
+ if (shived) {
+ ownerDocument.documentShived = shived;
+ }
+ return ownerDocument;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The `html5` object is exposed so that more elements can be shived and
+ * existing shiving can be detected on iframes.
+ * @type Object
+ * @example
+ *
+ * // options can be changed before the script is included
+ * html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false };
+ */
+ var html5 = {
+
+ /**
+ * An array or space separated string of node names of the elements to shiv.
+ * @memberOf html5
+ * @type Array|String
+ */
+ 'elements': options.elements || 'abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video',
+
+ /**
+ * A flag to indicate that the HTML5 style sheet should be inserted.
+ * @memberOf html5
+ * @type Boolean
+ */
+ 'shivCSS': !(options.shivCSS === false),
+
+ /**
+ * A flag to indicate that the document's `createElement` and `createDocumentFragment`
+ * methods should be overwritten.
+ * @memberOf html5
+ * @type Boolean
+ */
+ 'shivMethods': !(options.shivMethods === false),
+
+ /**
+ * A string to describe the type of `html5` object ("default" or "default print").
+ * @memberOf html5
+ * @type String
+ */
+ 'type': 'default',
+
+ // shivs the document according to the specified `html5` object options
+ 'shivDocument': shivDocument
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ // expose html5
+ window.html5 = html5;
+
+ // shiv the document
+ shivDocument(document);
+
+ }(this, document));
+
+ //>>END IEPP
+
+ // Assign private properties to the return object with prefix
+ Modernizr._version = version;
+
+ // expose these for the plugin API. Look in the source for how to join() them against your input
+ Modernizr._prefixes = prefixes;
+ Modernizr._domPrefixes = domPrefixes;
+ Modernizr._cssomPrefixes = cssomPrefixes;
+
+ // Modernizr.mq tests a given media query, live against the current state of the window
+ // A few important notes:
+ // * If a browser does not support media queries at all (eg. oldIE) the mq() will always return false
+ // * A max-width or orientation query will be evaluated against the current state, which may change later.
+ // * You must specify values. Eg. If you are testing support for the min-width media query use:
+ // Modernizr.mq('(min-width:0)')
+ // usage:
+ // Modernizr.mq('only screen and (max-width:768)')
+ Modernizr.mq = testMediaQuery;
+
+ // Modernizr.hasEvent() detects support for a given event, with an optional element to test on
+ // Modernizr.hasEvent('gesturestart', elem)
+ Modernizr.hasEvent = isEventSupported;
+
+ // Modernizr.testProp() investigates whether a given style property is recognized
+ // Note that the property names must be provided in the camelCase variant.
+ // Modernizr.testProp('pointerEvents')
+ Modernizr.testProp = function(prop){
+ return testProps([prop]);
+ };
+
+ // Modernizr.testAllProps() investigates whether a given style property,
+ // or any of its vendor-prefixed variants, is recognized
+ // Note that the property names must be provided in the camelCase variant.
+ // Modernizr.testAllProps('boxSizing')
+ Modernizr.testAllProps = testPropsAll;
+
+
+
+ // Modernizr.testStyles() allows you to add custom styles to the document and test an element afterwards
+ // Modernizr.testStyles('#modernizr { position:absolute }', function(elem, rule){ ... })
+ Modernizr.testStyles = injectElementWithStyles;
+
+
+ // Modernizr.prefixed() returns the prefixed or nonprefixed property name variant of your input
+ // Modernizr.prefixed('boxSizing') // 'MozBoxSizing'
+
+ // Properties must be passed as dom-style camelcase, rather than `box-sizing` hypentated style.
+ // Return values will also be the camelCase variant, if you need to translate that to hypenated style use:
+ //
+ // str.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-');
+
+ // If you're trying to ascertain which transition end event to bind to, you might do something like...
+ //
+ // var transEndEventNames = {
+ // 'WebkitTransition' : 'webkitTransitionEnd',
+ // 'MozTransition' : 'transitionend',
+ // 'OTransition' : 'oTransitionEnd',
+ // 'msTransition' : 'MsTransitionEnd',
+ // 'transition' : 'transitionend'
+ // },
+ // transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ];
+
+ Modernizr.prefixed = function(prop, obj, elem){
+ if(!obj) {
+ return testPropsAll(prop, 'pfx');
+ } else {
+ // Testing DOM property e.g. Modernizr.prefixed('requestAnimationFrame', window) // 'mozRequestAnimationFrame'
+ return testPropsAll(prop, obj, elem);
+ }
+ };
+
+
+
+ // Remove "no-js" class from <html> element, if it exists:
+ docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') +
+
+ // Add the new classes to the <html> element.
+ (enableClasses ? ' js ' + classes.join(' ') : '');
+
+ return Modernizr;
+
+})(this, this.document);
diff --git a/apps/tal/js/tal.js b/apps/tal/js/tal.js
new file mode 100644
index 00000000000..c4c64694404
--- /dev/null
+++ b/apps/tal/js/tal.js
@@ -0,0 +1,32 @@
+
+$(document).ready(function(){
+ $('#manual nav').keydown(function(event) {
+ if(event.which == 13) {
+ $('#manual ol').click();
+ }
+ });
+ $('#manual nav').click(function(event){
+ var $tgt = $(event.target);
+ if ($tgt.is('li') || $tgt.is('a')) {
+ var item = $tgt.is('li')?$($tgt):($tgt).parent();
+ var section = $('#manual section');
+ var id = item.data('id');
+ item.addClass('active');
+ var oldpage = section.data('id');
+ if(oldpage){
+ $('#manual li[data-id="'+oldpage+'"]').removeClass('active');
+ }
+ $.getJSON(OC.filePath('tal', 'ajax', 'loadpage.php'),{'id':id},function(jsondata){
+ if(jsondata.status == 'success'){
+ $('#manual li[data-id="'+id+'"]').addClass('active');
+ section.replaceWith(jsondata.data.page);
+ section.data('id', id);
+ }
+ else{
+ OC.dialogs.alert(jsondata.data.message, t('core', 'Error'));
+ }
+ });
+ }
+ return false;
+ });
+}); \ No newline at end of file
diff --git a/apps/tal/lib/paths.php b/apps/tal/lib/paths.php
new file mode 100644
index 00000000000..b61d52c0f47
--- /dev/null
+++ b/apps/tal/lib/paths.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+/**
+ * This class provides TAL templates for owncloud.
+ */
+class OC_TALTemplate extends OC_Template {
+} \ No newline at end of file
diff --git a/apps/tal/lib/tall10n.php b/apps/tal/lib/tall10n.php
new file mode 100644
index 00000000000..81ebfe49041
--- /dev/null
+++ b/apps/tal/lib/tall10n.php
@@ -0,0 +1,105 @@
+<?php
+/**
+ * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+//require_once 'PHPTAL/TranslationService.php';
+
+
+class OC_TALL10N extends OC_L10N implements PHPTAL_TranslationService {
+ private $encoding = 'UTF-8';
+ private $vars = array();
+ //private $lang = '';
+ //private $app = '';
+
+ /**
+ * @brief The constructor
+ * @param $app the app requesting l10n
+ * @param $lang default: null Language
+ * @returns OC_L10N-Object
+ *
+ * If language is not set, the constructor tries to find the right
+ * language.
+ */
+ public function __construct($app, $lang = null){
+ //$this->app = $app;
+ //$this->lang = $lang;
+ parent::__construct($app, $lang);
+ }
+
+ /**
+ * Set the target language for translations.
+ * @return string - chosen language
+ */
+ function setLanguage(/*...*/) {
+ $langs = func_get_args();
+ $this->language = $langs[0];
+ }
+
+ /**
+ * PHPTAL will inform translation service what encoding page uses.
+ * Output of translate() must be in this encoding.
+ * NOTE: Currently not used (and probably won't be as we use utf-8 all over?).
+ */
+ function setEncoding($encoding) {
+ $this->encoding = $encoding;
+ }
+
+ /**
+ * Set the domain to use for translations (if different parts of application are translated in different files. This is not for language selection).
+ */
+ function useDomain($domain) {
+ if(!$domain) {
+ return;
+ }
+ error_log('useDomain: '.$domain);
+ $this->app = $domain;
+ $this->init();
+ }
+
+ /**
+ * Set value of a variable used in translation key.
+ *
+ * You should use it to replace all {key}s with values in translated strings.
+ *
+ * @param string $key - name of the variable
+ * @param string $value
+ */
+ public function setVar($key, $value) {
+ error_log('setVar: '.$key.'=>'.$value);
+ $this->vars[$key] = $value;
+ }
+
+ /**
+ * Translate a gettext key and interpolate variables.
+ *
+ * @param string $key - translation key, e.g. "hello {username}!"
+ * @param string $htmlescape - if true, you should HTML-escape translated string. You should never HTML-escape interpolated variables.
+ */
+ function translate($key, $escape=true) {
+ $translations = $this->getTranslations();
+ if (array_key_exists($key, $translations)) {
+ $v = $translations[$key];
+ } else {
+ $v = $key;
+ }
+
+ if ($escape) {
+ $v = htmlspecialchars($v);
+ }
+
+ //while (preg_match('/\{(.*?)\}/sm', $v, $m)) {
+ while (preg_match('/\$\{(.*?)\}/sm', $v, $m)) {
+ list($src, $var) = $m;
+ if (!isset($this->vars[$var])) {
+ $v = str_replace($src, 'undefined', $v);
+ } else {
+ $v = str_replace($src, $this->vars[$var], $v);
+ }
+ }
+ return $v;
+ }
+} \ No newline at end of file
diff --git a/apps/tal/lib/taltemplate.php b/apps/tal/lib/taltemplate.php
new file mode 100644
index 00000000000..523e857e6f2
--- /dev/null
+++ b/apps/tal/lib/taltemplate.php
@@ -0,0 +1,372 @@
+<?php
+/**
+ * kate: replace-tabs off; indent-mode Normal; keep-extra-spaces: off; tab-indents: on;
+ * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+function phptal_tales_remote($exp, $nothrow) {
+ //$exp = trim($exp, ' \t\r\n/');
+ error_log(phptal_tales($exp, $nothrow));
+ return "OCP\Util::linkToRemote(".phptal_tales($exp, $nothrow).")";
+}
+
+function phptal_tales_url($src, $nothrow) {
+ //$exp = trim($exp, ' \t\r\n/');
+ error_log(phptal_tales($src, $nothrow));
+ return "OC_TALTemplate::linkToAbsolute(".phptal_tales($src, $nothrow).")";
+}
+
+function phptal_tales_linkto($src, $nothrow) {
+ return "OC_TALTemplate::linkTo(".phptal_tales($src, $nothrow).")";
+}
+
+function phptal_tales_image($src, $nothrow) {
+ return "OC_TALTemplate::imagePath(".phptal_tales($src, $nothrow).")";
+}
+
+function phptal_tales_config($src, $nothrow) {
+ return "OC_TALTemplate::config(".phptal_tales($src, $nothrow).")";
+}
+
+/**
+ * This class provides TAL templates for owncloud.
+ */
+class OC_TALTemplate extends OC_Template {
+ /**
+ */
+ protected $_engine = null;
+ protected $scripts = Array();
+ protected $styles = Array();
+ protected $_headers = Array();
+ protected $renderas;
+ protected static $app = '';
+
+ public function __construct($app, $name, $renderas = "") {
+ //if(defined('DEBUG') && DEBUG) {
+ ini_set('display_errors', true);
+ //}
+ $this->renderas = $renderas;
+ $this->i18n = new OC_TALL10N($app);
+ $this->setEngine(new PHPTAL());
+ parent::__construct($app, $name, $renderas);
+ //$this->fetchHeadVars();
+ self::$app = $app;
+ //$this->assign('application', $this->app);
+ $this->assign('i18n', $this->i18n);
+ $this->assign('user', OCP\User::getUser());
+ $this->assign('appinfo', OCP\App::getAppInfo($app));
+ $this->assign('appajaxpath', OC::$SERVERROOT.OC_App::getAppPath($app).'/ajax');
+ $this->assign('appjspath', OC::$SERVERROOT.OC_App::getAppPath($app).'/js');
+ $this->assign('apptemplatepath', OC::$SERVERROOT.OC_App::getAppPath($app).'/templates');
+ if($renderas) {
+ $this->assign('maintemplate', OC_App::getAppPath('tal').'/templates/layout.'.$renderas.'.pt');
+ if($renderas == 'user') {
+ $this->assign('requesttoken', OC_Util::callRegister());
+ }
+ }
+ //$this->assign('styles', $this->styles);
+ $this->assign('core_styles', !empty(OC_Util::$core_styles)?'core.css':null);
+ $this->assign('core_scripts', !empty(OC_Util::$core_scripts)?'core.js':null);
+ $request = isset($_REQUEST)?$_REQUEST:array();
+ $request['post'] = isset($_POST)?$_POST:array();
+ $request['get'] = isset($_GET)?$_GET:array();
+ $this->assign('request', $request);
+ $this->assign('server', $_SERVER);
+ $this->assign('webroot', OC::$WEBROOT);
+ $this->assign('theme', OC_Config::getValue('theme'));
+
+ $apps_paths = array();
+ foreach(OC_App::getEnabledApps() as $app){
+ $apps_paths[$app] = OC_App::getAppWebPath($app);
+ }
+ $this->assign( 'apps_paths', str_replace('\\/', '/',json_encode($apps_paths)),false ); // Ugly unescape slashes waiting for better solution
+
+ // Add the js files
+ $jsfiles = OC_TemplateLayout::findJavascriptFiles(OC_Util::$scripts);
+
+ foreach($jsfiles as $info) {
+ $root = $info[0];
+ $web = $info[1];
+ $file = $info[2];
+ $this->scripts[] = $web.'/'.$file;
+ }
+ $this->assign('scripts',$this->scripts);
+
+ // Add the css files
+ $cssfiles = OC_TemplateLayout::findStylesheetFiles(OC_Util::$styles);
+
+ foreach($cssfiles as $info) {
+ $root = $info[0];
+ $web = $info[1];
+ $file = $info[2];
+ $paths = explode('/', $file);
+
+ $in_root = false;
+ foreach(OC::$APPSROOTS as $app_root) {
+ if($root == $app_root['path']) {
+ $in_root = true;
+ break;
+ }
+ }
+
+ if($in_root ) {
+ $app = $paths[0];
+ unset($paths[0]);
+ $path = implode('/', $paths);
+ $this->styles[] = OC_Helper::linkTo($app, $path);
+ }
+ else {
+ $this->styles[] = $web.'/'.$file;
+ }
+ }
+ $this->assign('styles', $this->styles);
+
+ }
+
+ /**
+ * Plug in PHPTAL object into View
+ *
+ * @name setEngine
+ * @access public
+ * @param object PHPTAL $engine
+ */
+ public function setEngine(PHPTAL $engine) {
+ $view = new OC_FilesystemView('/'.OC_User::getUser());
+ if(!$view->file_exists('phptal')) {
+ $view->mkdir('phptal');
+ }
+ $this->_engine = $engine;
+ $this->_engine->setPhpCodeDestination($view->getLocalFile('/phptal/'));
+ $this->_engine->setTemplateRepository($_SERVER['DOCUMENT_ROOT'].OCP\Util::linkTo(self::$app, 'templates'));
+ $this->_engine->set('this', $this);
+ $this->_engine->setOutputMode(PHPTAL::HTML5);
+ $this->_engine->setTranslator($this->i18n);
+ return $this;
+ }
+
+ /**
+ * Forces reparsing of all templates all the time. It should be used only for testing and debugging.
+ * It's useful if you're testing pre filters or changing code of PHPTAL itself.
+ * WARNING: This slows down PHPTAL very much. Never enable this on production servers!
+ */
+ public function setForceReparse() {
+ OCP\Util::writeLog('tal','ForceReparse is enabled!', OCP\Util::WARN);
+ $this->_engine->setForceReparse();
+ }
+
+ /**
+ * Get PHPTAL object from View
+ *
+ * @name getEngine
+ * @access public
+ */
+ public function getEngine() {
+ return $this->_engine;
+ }
+
+ /**
+ * Clone PHPTAL object
+ *
+ * @access public
+ */
+ public function __clone() {
+ $this->_engine = clone $this->_engine;
+ }
+
+ /**
+ * Display template
+ *
+ * @access protected
+ */
+ protected function _run() {
+ $this->_engine->setTemplate(func_get_arg(0));
+ try {
+ echo $this->_engine->execute();
+ } catch (Exception $e) {
+ throw new Exception($e);
+ }
+ }
+
+ /**
+ * @brief check Path For Template with and without $fext
+ * @param $path to check
+ * @param $name of the template file (without suffix)
+ * @param $fext formfactor extension
+ * @return bool true when found
+ *
+ * Will set $this->template and $this->path if there is a template at
+ * the specified $path
+ */
+ protected function checkPathForTemplate($path, $name, $fext) {
+ if ($name =='') return false;
+ $template = null;
+ if( is_file( $path.$name.$fext.'.pt' )){
+ $template = $path.$name.$fext.'.pt';
+ }elseif( is_file( $path.$name.'.pt' )){
+ $template = $path.$name.'.pt';
+ }
+ //error_log('Template: '.$template);
+ if ($template) {
+ $this->template = $template;
+ $this->path = $path;
+ $this->_engine->template = $this->template;
+ $this->_engine->setTemplate($this->template);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @brief Assign variables
+ * @param $key key
+ * @param $value value
+ * @param $sanitizeHTML Ignored, as values are always sanitized unless explicitly specified not to.
+ * @returns true
+ *
+ * This function assigns a variable. It can be accessed via TALES expressions or ${$key} in
+ * the template.
+ *
+ * If the key existed before, it will be overwritten
+ */
+ public function assign( $key, $value, $sanitizeHTML=false ){
+ $this->_engine->set($key, $value);
+ return true;
+ }
+
+ /**
+ * @brief Add a custom element to the header
+ * @param string tag tag name of the element
+ * @param array $attributes array of attributes for the element
+ * @param string $text the text content for the element
+ */
+ public function addHeader( $tag, $attributes, $text=''){
+ $this->_headers[]=array('tag'=>$tag,'attributes'=>$attributes,'text'=>$text);
+ }
+
+ /**
+ * @brief Prints the proceeded template
+ * @returns true/false
+ *
+ * This function proceeds the template and prints its output.
+ */
+ public function printPage(){
+ echo $this->fetchPage();
+ }
+
+ /**
+ * @brief Proceeds the template
+ * @returns content
+ *
+ * This function proceeds the template. If $this->renderas is set, it
+ * will produce a full page.
+ */
+ public function fetchPage(){
+ error_log('renderas: '.$this->renderas);
+ if($this->renderas) {
+ // Add custom headers
+ $this->assign('headers',array_merge($this->_headers, OC_Util::$headers));
+ // Add navigation entry
+ $navigation = OC_App::getNavigation();
+ $this->assign( "navigation", $navigation);
+ $this->assign( "settingsnavigation", OC_App::getSettingsNavigation());
+ if(array_search(OC_APP::getCurrentApp(),array('settings','admin','help'))!==false){
+ $this->assign('bodyid','body-settings');
+ }else{
+ $this->assign('bodyid','body-user');
+ }
+ foreach($navigation as $entry) {
+ if ($entry['active']) {
+ $this->assign( 'application', $entry['name'] );
+ break;
+ }
+ }
+ }
+ //error_log('utilheaders'.print_r(array_merge($this->_headers, OC_Util::$headers), true));
+ return $this->_engine->execute();
+ }
+
+ static function linkTo($src) {
+ //error_log('linkTo '.$src);
+ $parts = is_array($src)?$src:explode('/', rtrim($src, ' \t\r\n/'));
+ if($parts[0] == '') {
+ array_shift($parts);
+ return OCP\Util::linkTo('', implode('/', $parts));
+ } elseif(count($parts) == 1) {
+ return OCP\Util::linkTo('', implode('/', $parts));
+ } elseif(trim($parts[0] == 'core')) {
+ array_shift($parts);
+ return OCP\Util::linkTo('', implode('/', $parts));
+ } else { // This should be an app.
+ return OCP\Util::linkTo(array_shift($parts), implode('/', $parts));
+ }
+ }
+
+ static function linkToAbsolute($src) {
+ //error_log('linkTo '.$src);
+ $parts = is_array($src)?$src:explode('/', rtrim($src, ' \t\r\n/'));
+ if($parts[0] == '') {
+ array_shift($parts);
+ return OCP\Util::linkToAbsolute('', implode('/', $parts));
+ } elseif(count($parts) == 1) {
+ return OCP\Util::linkToAbsolute('', implode('/', $parts));
+ } elseif(trim($parts[0] == 'core')) {
+ array_shift($parts);
+ return OCP\Util::linkToAbsolute('', implode('/', $parts));
+ } else { // This should be an app.
+ return OCP\Util::linkToAbsolute(array_shift($parts), implode('/', $parts));
+ }
+ }
+
+ static function imagePath($src) {
+ //error_log('imagePath '.$src);
+ $parts = is_array($src)?$src:explode('/', rtrim($src, ' \t\r\n/'));
+ if($parts[0] == '') {
+ array_shift($parts);
+ return OCP\Util::imagePath('', implode('/', $parts));
+ } elseif(count($parts) == 1) {
+ return OCP\Util::imagePath('', implode('/', $parts));
+ } elseif(trim($parts[0] == 'core')) {
+ array_shift($parts);
+ return OCP\Util::imagePath('', implode('/', $parts));
+ } else { // This should be an app.
+ return OCP\Util::imagePath(array_shift($parts), implode('/', $parts));
+ }
+ }
+
+ static function config($src) {
+ error_log('pref '.$src);
+ $parts = is_array($src)?$src:explode('/', rtrim($src, ' \t\r\n/'));
+ if(count($parts) < 2) {
+ throw new PHPTAL_Exception('Wrong argument count: config: takes no less than 2 arguments.');
+ } else {
+ switch ($parts[0]) {
+ case 'sys':
+ return OCP\Config::getSystemValue($parts[1]);
+ break;
+ case 'app':
+ if(count($parts) == 2) {
+ return OCP\Config::getAppValue(self::app, $parts[1]);
+ } elseif(count($parts) == 3) {
+ return OCP\Config::getAppValue($parts[1], $parts[2]);
+ } else {
+ throw new PHPTAL_Exception('Wrong argument count: config:$app takes no more than 3 arguments.');
+ }
+ break;
+ case 'user':
+ if(count($parts) == 2) {
+ return OCP\Config::getUserValue(OCP\User::getUser(), self::app, $parts[1]);
+ } elseif(count($parts) == 3) {
+ return OCP\Config::getUserValue(OCP\User::getUser(), $parts[1], $parts[2]);
+ } elseif(count($parts) == 4) {
+ return OCP\Config::getUserValue($parts[1], $parts[2], $parts[3]);
+ } else {
+ throw new PHPTAL_Exception('Wrong argument count: config: takes no more than 4 arguments.');
+ }
+ break;
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/apps/tal/settings.php b/apps/tal/settings.php
new file mode 100644
index 00000000000..a4eeb333446
--- /dev/null
+++ b/apps/tal/settings.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * Copyright (c) 2012 Thomas Tanghus <thomas@tanghus.net>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+
+OCP\JSON::checkLoggedIn();
+OCP\JSON::checkAppEnabled('tal');
+
+$tmpl = new OC_TALTemplate('tal', 'settings');
+return $tmpl->fetchPage();
+
+?>
diff --git a/apps/tal/templates/layout.guest.pt b/apps/tal/templates/layout.guest.pt
new file mode 100644
index 00000000000..884b44a582e
--- /dev/null
+++ b/apps/tal/templates/layout.guest.pt
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html metal:define-macro="page">
+<head>
+ <title>ownCloud</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <link rel="shortcut icon" href="/owncloud/core/img/favicon.png" tal:attributes="href string:${webroot}/core/img/favicon.png" />
+ <link rel="apple-touch-icon-precomposed" href="/owncloud/core/img/favicon-touch.png" tal:attributes="href string:${webroot}/core/img/favicon-touch.png" />
+
+ <link rel="stylesheet" href="core.css" type="text/css" media="screen" tal:condition="php:OC_Util::$core_styles" tal:attributes="href php:OC_Helper::linkToRemote('core.css', false)" />
+
+ <tal:block tal:repeat="style styles">
+ <link rel="stylesheet" href="/owncloud/core/css/styles.css" type="text/css" media="screen" tal:attributes="href style" />
+ </tal:block>
+ <script type="text/javascript">
+ var oc_webroot = '${webroot}';
+ var oc_appswebroot = '${appswebroot}';
+ </script>
+
+ <script type="text/javascript" src="<?php echo OC_Helper::linkToRemote('core.js', false) ?>" tal:condition="php:OC_Util::$core_scripts" tal:attributes="src php:OC_Helper::linkToRemote('core.js', false)"></script>
+
+ <tal:block tal:repeat="script scripts">
+ <script type="text/javascript" tal:attributes="src script"></script>
+ </tal:block>
+
+ <tal:block tal:repeat="header headers">
+ <tal:block tal:replace='structure string:<${header/tag}' /> <tal:block tal:repeat="attribute header/attributes"><tal:block tal:replace='structure string:${repeat/attribute/key}="${attribute}"' /></tal:block> />
+ </tal:block>
+</head>
+
+<body id="body-login">
+ <div id="login">
+ <header>
+ <div id="header">
+ <img src="/owncloud/core/img/logo.png" alt="ownCloud" tal:attributes="href string:${webroot}/core/img/logo.png" />
+ </div>
+ </header>
+ <div id="content" metal:define-slot="content">
+ This is the content.
+ </div>
+ </div>
+ <footer><p class="info"><a href="http://owncloud.org/" i18n:translate="">ownCloud</a> &ndash; web services under your control</p></footer>
+</body>
+</html>
diff --git a/apps/tal/templates/layout.user.pt b/apps/tal/templates/layout.user.pt
new file mode 100644
index 00000000000..a945e1af7a2
--- /dev/null
+++ b/apps/tal/templates/layout.user.pt
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html metal:define-macro="page">
+<head>
+ <title tal:define="pretitle php:(!empty(application)) ? application . ' | ' : null" tal:content="string:${pretitle} ownCloud (${user}@${server/SERVER_NAME})">
+ Scripts and Styles
+ </title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <link rel="shortcut icon" href="/owncloud/core/img/favicon.png" tal:attributes="href string:${webroot}/core/img/favicon.png" />
+ <link rel="apple-touch-icon-precomposed" href="favicon-touch.png" tal:attributes="href string:${webroot}/core/img/favicon-touch.png" />
+
+ <link rel="stylesheet" href="core.css" type="text/css" media="screen" tal:condition="core_styles" tal:attributes="href remote:core_styles" />
+ <tal:block tal:repeat="style styles">
+ <link rel="stylesheet" href="/owncloud/core/css/styles.css" type="text/css" media="screen" tal:attributes="href style" />
+ </tal:block>
+
+ <script type="text/javascript">
+ var oc_webroot = '${webroot}';
+ var oc_appswebroots = ${structure apps_paths};
+ var oc_current_user = '${user}';
+ </script>
+
+ <script type="text/javascript" src="core.js" tal:condition="core_scripts" tal:attributes="src remote:core_scripts"></script>
+ <tal:block tal:repeat="script scripts">
+ <script type="text/javascript" tal:attributes="src script"></script>
+ </tal:block>
+
+ <script type="text/javascript">
+ $(function() {
+ requesttoken = '${requesttoken}';
+ $(document).bind('ajaxSend', function(elm, xhr, s){
+ if(requesttoken) {
+ xhr.setRequestHeader('requesttoken', requesttoken);
+ }
+ });
+ });
+ </script>
+
+ <tal:block tal:repeat="header headers">
+ <tal:block tal:replace='structure string:${header/tag}' /> <tal:block tal:repeat="attribute header/attributes"><tal:block tal:replace='structure string:${repeat/attribute/key}="${attribute}"' /></tal:block> />
+ </tal:block>
+</head>
+<body tal:attributes="id bodyid">
+<header>
+ <div id="header" tal:define="index linkto:string:/index.php">
+ <a id="owncloud" tal:attributes="href index"><img class="svg" tal:attributes="src image:string:logo-wide.svg" src="logo-wide.svg" alt="ownCloud" /></a>
+ <a class="header-right header-action" id="logout" tal:attributes="href string:${index}?logout=true"><img class="svg" tal:attributes="src image:string:/actions/logout.svg" i18n:attributes="alt;title" alt="Log out" title="Log out" src="actions/logout.svg" /></a>
+ <form class="searchbox header-right" action="#" method="post">
+ <input id="searchbox" class="svg" type="search" name="query" tal:attributes="value request/post/query|nothing" value="" autocomplete="off" />
+ </form>
+ </div>
+</header>
+
+<nav i18n:domain="core">
+ <div id="navigation">
+ <ul id="apps" class="svg">
+ <li tal:repeat="entry navigation">
+ <a tal:attributes="style structure string:background-image:url(${entry/icon});href entry/href;class php:entry['active']?'active':null" tal:content="entry/name">Name</a>
+ </li>
+ </ul>
+
+ <ul id="settings" class="svg">
+ <img role="button" tabindex="0" id="expand" class="svg"
+ i18n:attributes="alt Settings" alt="Settings" tal:attributes="src image:string:/actions/settings.svg" src="actions/settings.svg" />
+ <span i18n:translate="">Settings</span>
+ <div id="expanddiv"
+ tal:attributes="class structure php:bodyid EQ 'body-user' ? 'hidden' : nothing">
+ <li tal:repeat="entry settingsnavigation">
+<a tal:attributes="style structure string:background-image:url(${entry/icon});href entry/href;class entry/active|nothing" tal:content="entry/name">Name</a></li>
+ </div>
+ </ul>
+ </div>
+</nav>
+
+<div id="content" metal:define-slot="content">
+ This is the content.
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/apps/tal/templates/macros.util.pt b/apps/tal/templates/macros.util.pt
new file mode 100644
index 00000000000..0e8c590e0e6
--- /dev/null
+++ b/apps/tal/templates/macros.util.pt
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>
+ Utility macros
+ </title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+</head>
+<body tal:attributes="id bodyid">
+<header>
+ <div id="header" tal:define="index php:OCP\Util::linkTo('', 'index.php')">
+ <a id="owncloud" tal:attributes="href index"><img class="svg" tal:attributes="src php:OCP\Util::imagePath('', 'logo-wide.svg')" src="logo-wide.svg" alt="ownCloud" /></a>
+ <form class="searchbox" action="#" method="post">
+ <input id="searchbox" class="svg" type="search" name="query" tal:attributes="value request/post/query|nothing" value="" autocomplete="off" />
+ </form>
+ <a id="logout" tal:attributes="href string:${index}?logout=true"><img class="svg" tal:attributes="src php:OCP\Util::imagePath('', 'actions/logout.svg')" i18n:attributes="alt Log out;title Log out" alt="Log out" title="Log out" src="actions/logout.svg" /></a>
+ </div>
+</header>
+<body>
+<tal:block metal:define-macro="html_select_options">
+ <tal:block condition="options">
+ <tal:block tal:define="options php:isset('combine') AND array_combine(options, options) OR options" repeat="option options">
+ <option tal:content="option" tal:attributes></option>
+ </tal:block>
+ </tal:block>
+</tal:block>
+function html_select_options($options, $selected, $params=array()) {
+ if (!is_array($selected)){
+ $selected=array($selected);
+ }
+ if (isset($params['combine']) && $params['combine']){
+ $options = array_combine($options, $options);
+ }
+ $value_name = $label_name = false;
+ if (isset($params['value'])){
+ $value_name = $params['value'];
+ }
+ if (isset($params['label'])){
+ $label_name = $params['label'];
+ }
+ $html = '';
+ foreach($options as $value => $label){
+ if ($value_name && is_array($label)){
+ $value = $label[$value_name];
+ }
+ if ($label_name && is_array($label)){
+ $label = $label[$label_name];
+ }
+ $select = in_array($value, $selected) ? ' selected="selected"' : '';
+ $html .= '<option value="' . $value . '"' . $select . '>' . $label . '</option>'."\n";
+ }
+ return $html;
+}
+
+</body>
+</html> \ No newline at end of file
diff --git a/apps/tal/templates/manual.pt b/apps/tal/templates/manual.pt
new file mode 100644
index 00000000000..9ad03db30be
--- /dev/null
+++ b/apps/tal/templates/manual.pt
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html metal:use-macro="${maintemplate}/page">
+<head>
+ <title>TAL Page Templates</title>
+</head>
+<body>
+<div id="content" metal:fill-slot="content">
+ <article id="manual" i18n:domain="tal" tal:define="page page|string:intro">
+ <nav>
+ <ol>
+ <li tal:repeat="section sections" tal:attributes="data-id section/id" data-page="intro">
+ <a tal:content="section/title">Introduction</a>
+ </li>
+ </ol>
+ </nav>
+ <section metal:use-macro="sections.pt/${page}">
+ <h1>Manual</h1>
+ rest of the content
+ </section>
+ </article>
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/apps/tal/templates/sections.pt b/apps/tal/templates/sections.pt
new file mode 100644
index 00000000000..9dcda6ebf45
--- /dev/null
+++ b/apps/tal/templates/sections.pt
@@ -0,0 +1,142 @@
+<span metal:use-macro="${id}">
+TRUT
+</span>
+<section data-id="intro" metal:define-macro="intro">
+ <h1>Introduction</h1>
+ <h2>Initializing a template</h2>
+ <strong>Before:</strong>
+ <pre>$output = new OCP\Template('app', 'template', 'user');</pre>
+ <strong>After:</strong>
+ <pre>$tmpl = new OC_TALTemplate('app', 'template', 'user');</pre>
+ <h2>Assigning a variable</h2>
+ <strong>Before:</strong>
+ <pre>$tmpl->assign('myvar', $myvar);</pre>
+ <strong>After:</strong>
+ <pre>$tmpl->assign('myvar', $myvar);</pre>
+ The sharp minds may have noticed that there is no difference ;-)
+ <h2>Linking to an image</h2>
+ <strong>Before:</strong>
+ <pre>
+&lt;img class="svg" src="&lt;?php echo image_path('', 'logo-wide.svg'); ?&gt;" alt="ownCloud" /&gt;
+&lt;img class="svg" src="&lt;?php echo image_path('app', 'someimage.png'); ?&gt;" alt="ownCloud" /&gt;</pre>
+ <strong>After:</strong>
+ <pre>
+&lt;img class="svg" tal:attributes="src image:string:logo-wide.svg" alt="ownCloud" /&gt;
+&lt;img class="svg" tal:attributes="src image:string:app/someimage.png" alt="ownCloud" /&gt;</pre>
+ <h2>Constructing a link</h2>
+ <strong>Before:</strong>
+ <pre>
+&lt;a href="&lt;?php echo link_to('', 'index.php'); ?&gt;"&gt;Home&lt;/a&gt;
+&lt;a href="&lt;?php echo link_to('app', 'index.php'); ?&gt;"&gt;Some app&lt;/a&gt;</pre>
+ <strong>After:</strong>
+ <pre>
+&lt;a tal:attributes="href linkto:string:index.php"&gt;Home&lt;/a&gt;
+&lt;a tal:attributes="href linkto:string:app/index.php"&gt;Some app&lt;/a&gt;</pre>
+ <h2>Link to remote service</h2>
+ <strong>Before:</strong>
+ <pre>
+&lt;link rel="stylesheet" href="&lt;?php echo OC_Helper::linkToRemote('core.css') ?&gt;" type="text/css" media="screen" /&gt;
+&lt;a href="&lt;?php echo OC_Helper::linkToRemote('webdav') ?&gt;"&gt;WebDAV&lt;/a&gt;</pre>
+ <strong>After:</strong>
+ <pre>
+&lt;link rel="stylesheet" type="text/css" media="screen" tal:attributes="href remote:string:core.css" /&gt;
+&lt;a tal:attributes="href remote:string:webdav"&gt;WebDAV&lt;/a&gt;</pre>
+ <p>The latter produces a link to the WebDAV service on the current instance:
+ <a tal:attributes="href remote:string:webdav">WebDAV</a></p>
+ <h2>Accessing configuration</h2>
+ <strong>Before:</strong>
+ <pre>
+ownCloud version: &lt;?php echo OCP\Config::getSystemValue('version'); ?&gt;
+Default quota: &lt;?php echo OCP\Config::getAppValue('files', 'default_quota'); ?&gt;
+Calendar time zone: &lt;?php echo OCP\Config::getUserValue(OCP\User::getUser(), 'calendar', 'timezone'); ?&gt;</pre>
+ <strong>After:</strong>
+ <pre>
+ownCloud version: $${config:string:sys/version}
+Default quota: $${config:string:app/files/default_quota}
+Calendar time zone: $${config:string:user/calendar/timezone}</pre>
+ ownCloud version: ${config:string:sys/version}<br />
+ Default quota: ${config:string:app/files/default_quota}<br />
+ Calendar time zone: ${config:string:user/calendar/timezone}<br />
+ <h2>Translating content</h2>
+ <strong>Before:</strong>
+ <pre>&lt;p&gt;&lt;?php echo $l-&gt;t('This will be translated.'); ?&gt;&lt;p&gt;</pre>
+ <strong>After:</strong>
+ <pre>&lt;p i18n:translate=""&gt;This will be translated.&lt;p&gt;</pre>
+ <a class="readmore" href="http://phptal.org/manual/en/split/i18n-content.html" target="_blank">Read more...</a>
+ <h2>Translating content with variables</h2>
+ <strong>Before:</strong>
+ <p>There is no standardized way to do this currently. I have seen both translation keys using printf formatting
+ and custom interpolation using e.g. curly brackets.</p>
+ <strong>After:</strong>
+ <pre>
+&lt;tal:block i18n:name="username" tal:content="user" /&gt;
+&lt;p i18n:translate=""&gt;Your user name is &#36;{username}.&lt;/p&gt;</pre>
+ <tal:block i18n:name="username" tal:content="user" />
+ <p i18n:translate="">Your user name is ${username}.</p>
+ <p>Or you can wrap it in some markup:</p>
+ <pre>
+&lt;p i18n:translate=""&gt;
+Welcome back &lt;span i18n:name="username" tal:replace="user"/&gt;.
+&lt;/p&gt;</pre>
+ <p i18n:translate="">
+ Welcome back <span i18n:name="username" tal:replace="user"/>.
+ </p>
+ <a class="readmore" href="http://phptal.org/manual/en/split/i18n-name.html" target="_blank">Read more...</a>
+ <h2>Translating attributes.</h2>
+ <strong>Before:</strong>
+ <pre>&lt;img alt="&lt;?php echo $l->t('Log out');?&gt;" title="&lt;?php echo $l-&gt;t('Log out');?&gt;" src="&lt;?php echo image_path('', 'actions/logout.svg'); ?&gt;" /&gt;</pre>
+ <strong>After:</strong>
+ <pre>&lt;img tal:attributes="src image:string:/actions/logout.svg" i18n:attributes="alt;title" alt="Log out" title="Log out" /&gt;</pre>
+ <a href="http://phptal.org/manual/en/split/i18n-attributes.html" target="_blank">See more...</a>
+ <h2>Iterating</h2>
+ <pre>
+$$arr = array('color' => 'red',
+ 'taste' => 'sweet',
+ 'shape' => 'round',
+ 'name' => 'apple');
+ </pre>
+ <strong>Before:</strong>
+ <pre>
+&lt;select size="4"&gt;
+&lt;?php foreach($$arr as $key=&gt;$value) { ?&gt;
+&lt;option value="&lt;php echo $key; &gt;" &gt;&lt;php echo $value; &gt;&lt;/option&gt;
+&lt;/select&gt;
+&lt;?php } ?&gt;</pre>
+ <strong>After:</strong>
+ <pre>
+&lt;select size="4"&gt;
+&lt;option tal:repeat="item arr" tal:attributes="value repeat/item/key" tal:content="item"&gt;&lt;/option&gt;
+&lt;/select&gt;</pre>
+ <form>
+ <select size="4" tal:define="arr php:array('color' => 'red', 'taste' => 'sweet', 'shape' => 'round', 'name' => 'apple')">
+ <option tal:repeat="item arr" tal:attributes="value repeat/item/key" tal:content="item"></option>
+ </select>
+ </form>
+ <a class="readmore" href="http://phptal.org/manual/en/split/tal-repeat.html" target="_blank">Read more...</a>
+ <h2>Initializing a template</h2>
+ <strong>Before:</strong>
+ <pre></pre>
+ <strong>After:</strong>
+ <pre></pre>
+</section>
+<section data-id="example-1" metal:define-macro="example-1">
+ <h1>A simple example</h1>
+ This is actually the main template for this manual.
+ <pre tal:define="dut php:file_get_contents(apptemplatepath . '/manual.pt')" tal:content="dut">
+ </pre>
+</section>
+<section data-id="gotchas" metal:define-macro="gotchas">
+ <h1>Caveats &amp; Gotchas</h1>
+ I know
+</section>
+<section data-id="ref" metal:define-macro="ref">
+ <h1>References</h1>
+ <dl>
+ <dt><a href="http://phptal.org/" target="_blank">PHPTAL</a></dt><dd>The PHP TAL implementation</dd>
+ <dt><a href="http://phptal.org/manual/en/split/tal-namespace.html" target="_blank">TAL</a></dt><dd>Template Attribute Language</dd>
+ <dt><a href="http://phptal.org/manual/en/split/phptales.html" target="_blank">TALES</a></dt><dd>TAL Expression Syntax</dd>
+ <dt><a href="http://phptal.org/manual/en/split/metal.html" target="_blank">METAL</a></dt><dd>Macro Expansion Template Attribute Language</dd>
+ <dt><a href="http://phptal.org/manual/en/split/i18n.html" target="_blank">i18n namespace</a></dt><dd>Internationalization in TAL</dd>
+ <dt><a href="http://phptal.org/wiki/doku.php/zopepagetemplates" target="_blank">Zope Page Templates</a></dt><dd>Links and specifications from the original TAL implementation</dd>
+ </dl>
+</section>
diff --git a/apps/tal/templates/settings.pt b/apps/tal/templates/settings.pt
new file mode 100644
index 00000000000..1ae6eea9e7f
--- /dev/null
+++ b/apps/tal/templates/settings.pt
@@ -0,0 +1,10 @@
+<form id="tal">
+ <fieldset class="personalblock">
+ <legend i18n:translate="">TAL Page Templates for ownCloud</legend>
+ <p i18n:translate="">You can now create templates using TAL. As a matter of fact this section is using the internationalization namespace defined in TAL.<br />
+ Read more about it from the links below</p>
+ <ul>
+ <li><a href="" tal:attributes="href php:OCP\Util::linkTo('tal', 'index.php')" i18n:translate="">HOWTO use it in ownCloud</a></li>
+ </ul>
+ </fieldset>
+</form>
diff --git a/apps/tanghus-journal-2c506aa.tar.gz b/apps/tanghus-journal-2c506aa.tar.gz
new file mode 100644
index 00000000000..faa1ccfe477
--- /dev/null
+++ b/apps/tanghus-journal-2c506aa.tar.gz
Binary files differ
diff --git a/apps/tanghus-tal-7f8befe.tar.gz b/apps/tanghus-tal-7f8befe.tar.gz
new file mode 100644
index 00000000000..fae360b2a90
--- /dev/null
+++ b/apps/tanghus-tal-7f8befe.tar.gz
Binary files differ
diff --git a/apps/tasks/ajax/addtask.php b/apps/tasks/ajax/addtask.php
index 9f35e7f21ec..d98fdbf3888 100644
--- a/apps/tasks/ajax/addtask.php
+++ b/apps/tasks/ajax/addtask.php
@@ -3,6 +3,7 @@
// Init owncloud
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('tasks');
+OCP\JSON::callCheck();
$calendars = OC_Calendar_Calendar::allCalendars(OCP\User::getUser(), true);
$first_calendar = reset($calendars);
@@ -21,7 +22,7 @@ $request['description'] = null;
$vcalendar = OC_Task_App::createVCalendarFromRequest($request);
$id = OC_Calendar_Object::add($cid, $vcalendar->serialize());
-$user_timezone = OCP\Config::getUserValue(OCP\User::getUser(), 'calendar', 'timezone', date_default_timezone_get());
+$user_timezone = OC_Calendar_App::getTimezone();
$task = OC_Task_App::arrayForJSON($id, $vcalendar->VTODO, $user_timezone);
OCP\JSON::success(array('task' => $task));
diff --git a/apps/tasks/ajax/addtaskform.php b/apps/tasks/ajax/addtaskform.php
deleted file mode 100644
index d86232e2da5..00000000000
--- a/apps/tasks/ajax/addtaskform.php
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-
-// Init owncloud
-OCP\JSON::checkLoggedIn();
-OCP\JSON::checkAppEnabled('tasks');
-
-$calendars = OC_Calendar_Calendar::allCalendars(OCP\User::getUser(), true);
-$category_options = OC_Calendar_App::getCategoryOptions();
-$percent_options = range(0, 100, 10);
-$priority_options = OC_Task_App::getPriorityOptions();
-$tmpl = new OCP\Template('tasks','part.addtaskform');
-$tmpl->assign('calendars',$calendars);
-$tmpl->assign('category_options', $category_options);
-$tmpl->assign('percent_options', $percent_options);
-$tmpl->assign('priority_options', $priority_options);
-$tmpl->assign('details', new OC_VObject('VTODO'));
-$tmpl->assign('categories', '');
-$page = $tmpl->fetchPage();
-
-OCP\JSON::success(array('data' => array( 'page' => $page )));
diff --git a/apps/tasks/ajax/delete.php b/apps/tasks/ajax/delete.php
index e29add9b556..cc22c3e3873 100644
--- a/apps/tasks/ajax/delete.php
+++ b/apps/tasks/ajax/delete.php
@@ -23,6 +23,7 @@
// Init owncloud
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('tasks');
+OCP\JSON::callCheck();
$id = $_POST['id'];
$task = OC_Calendar_App::getEventObject( $id );
diff --git a/apps/tasks/ajax/edittask.php b/apps/tasks/ajax/edittask.php
deleted file mode 100644
index edcc8a7cdcd..00000000000
--- a/apps/tasks/ajax/edittask.php
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-
-// Init owncloud
-OCP\JSON::checkLoggedIn();
-OCP\JSON::checkAppEnabled('tasks');
-
-$l10n = new OC_L10N('tasks');
-
-$id = $_POST['id'];
-$vcalendar = OC_Calendar_App::getVCalendar($id);
-
-$errors = OC_Task_App::validateRequest($_POST);
-if (!empty($errors)) {
- OCP\JSON::error(array('data' => array( 'errors' => $errors )));
- exit();
-}
-
-OC_Task_App::updateVCalendarFromRequest($_POST, $vcalendar);
-OC_Calendar_Object::edit($id, $vcalendar->serialize());
-
-$priority_options = OC_Task_App::getPriorityOptions();
-$tmpl = new OCP\Template('tasks','part.details');
-$tmpl->assign('priority_options', $priority_options);
-$tmpl->assign('details', $vcalendar->VTODO);
-$tmpl->assign('id', $id);
-$page = $tmpl->fetchPage();
-
-$user_timezone = OCP\Config::getUserValue(OCP\User::getUser(), 'calendar', 'timezone', date_default_timezone_get());
-$task = OC_Task_App::arrayForJSON($id, $vcalendar->VTODO, $user_timezone);
-
-OCP\JSON::success(array('data' => array( 'id' => $id, 'page' => $page, 'task' => $task )));
diff --git a/apps/tasks/ajax/edittaskform.php b/apps/tasks/ajax/edittaskform.php
deleted file mode 100644
index e5a0a7297c5..00000000000
--- a/apps/tasks/ajax/edittaskform.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-// Init owncloud
-OCP\JSON::checkLoggedIn();
-OCP\JSON::checkAppEnabled('tasks');
-
-$id = $_GET['id'];
-$details = OC_Calendar_App::getVCalendar($id)->VTODO;
-$categories = $details->getAsString('CATEGORIES');
-
-$category_options = OC_Calendar_App::getCategoryOptions();
-$percent_options = range(0, 100, 10);
-$priority_options = OC_Task_App::getPriorityOptions();
-
-$tmpl = new OCP\Template('tasks','part.edittaskform');
-$tmpl->assign('category_options', $category_options);
-$tmpl->assign('percent_options', $percent_options);
-$tmpl->assign('priority_options', $priority_options);
-$tmpl->assign('id',$id);
-$tmpl->assign('details',$details);
-$tmpl->assign('categories', $categories);
-$page = $tmpl->fetchPage();
-
-OCP\JSON::success(array('data' => array( 'page' => $page )));
diff --git a/apps/tasks/ajax/getdetails.php b/apps/tasks/ajax/getdetails.php
deleted file mode 100644
index 4ce469e0c9c..00000000000
--- a/apps/tasks/ajax/getdetails.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-// Init owncloud
-OCP\JSON::checkLoggedIn();
-OCP\JSON::checkAppEnabled('tasks');
-
-$l10n = new OC_L10N('tasks');
-
-$id = $_GET['id'];
-$task = OC_Calendar_Object::find($id);
-$details = OC_VObject::parse($task['calendardata']);
-if (!$details){
- OCP\JSON::error();
- exit;
-}
-
-$priority_options = OC_Task_App::getPriorityOptions();
-$tmpl = new OCP\Template('tasks','part.details');
-$tmpl->assign('priority_options', $priority_options);
-$tmpl->assign('details',$details->VTODO);
-$tmpl->assign('id',$id);
-$page = $tmpl->fetchPage();
-
-OCP\JSON::success(array('data' => array( 'id' => $id, 'page' => $page )));
diff --git a/apps/tasks/ajax/gettasks.php b/apps/tasks/ajax/gettasks.php
index 011730d0a13..b6183d9cb65 100644
--- a/apps/tasks/ajax/gettasks.php
+++ b/apps/tasks/ajax/gettasks.php
@@ -11,7 +11,7 @@ OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('tasks');
$calendars = OC_Calendar_Calendar::allCalendars(OCP\User::getUser(), true);
-$user_timezone = OCP\Config::getUserValue(OCP\User::getUser(), 'calendar', 'timezone', date_default_timezone_get());
+$user_timezone = OC_Calendar_App::getTimezone();
$tasks = array();
foreach( $calendars as $calendar ){
diff --git a/apps/tasks/ajax/update_property.php b/apps/tasks/ajax/update_property.php
index 46521cf6c58..679cfdefe48 100644
--- a/apps/tasks/ajax/update_property.php
+++ b/apps/tasks/ajax/update_property.php
@@ -9,6 +9,7 @@
// Init owncloud
OCP\JSON::checkLoggedIn();
OCP\JSON::checkAppEnabled('tasks');
+OCP\JSON::callCheck();
$id = $_POST['id'];
$property = $_POST['type'];
@@ -38,7 +39,7 @@ switch($property) {
$type = null;
if ($due != 'false') {
try {
- $timezone = OCP\Config::getUserValue(OCP\User::getUser(), 'calendar', 'timezone', date_default_timezone_get());
+ $timezone = OC_Calendar_App::getTimezone();
$timezone = new DateTimeZone($timezone);
$due = new DateTime('@'.$due);
$due->setTimezone($timezone);
@@ -63,6 +64,6 @@ switch($property) {
}
OC_Calendar_Object::edit($id, $vcalendar->serialize());
-$user_timezone = OCP\Config::getUserValue(OCP\User::getUser(), 'calendar', 'timezone', date_default_timezone_get());
+$user_timezone = OC_Calendar_App::getTimezone();
$task_info = OC_Task_App::arrayForJSON($id, $vtodo, $user_timezone);
OCP\JSON::success(array('data' => $task_info));
diff --git a/apps/tasks/index.php b/apps/tasks/index.php
index 4ff304a5607..f1c4d1e765c 100644
--- a/apps/tasks/index.php
+++ b/apps/tasks/index.php
@@ -21,8 +21,8 @@ OCP\Util::addScript('3rdparty/timepicker', 'jquery.ui.timepicker');
OCP\Util::addStyle('3rdparty/timepicker', 'jquery.ui.timepicker');
OCP\Util::addScript('tasks', 'tasks');
OCP\Util::addStyle('tasks', 'style');
-OCP\Util::addScript('contacts','jquery.multi-autocomplete');
-OCP\Util::addScript('','oc-vcategories');
+OCP\Util::addScript('contacts', 'jquery.multi-autocomplete');
+OCP\Util::addScript('', 'oc-vcategories');
OCP\App::setActiveNavigationEntry('tasks_index');
$categories = OC_Calendar_App::getCategoryOptions();
diff --git a/apps/tasks/js/tasks.js b/apps/tasks/js/tasks.js
index bc92965bb0b..de627927507 100644
--- a/apps/tasks/js/tasks.js
+++ b/apps/tasks/js/tasks.js
@@ -469,67 +469,5 @@ $(document).ready(function(){
return false;
});
- $('#tasks_addtaskform input[type="submit"]').live('click',function(){
- $.post('ajax/addtask.php',$('#tasks_addtaskform').serialize(),function(jsondata){
- if(jsondata.status == 'success'){
- $('#task_details').data('id',jsondata.data.id);
- $('#task_details').html(jsondata.data.page);
- $('#tasks_list').append(OC.Tasks.create_task_div(jsondata.data.task));
- }
- else{
- alert(jsondata.data.message);
- }
- }, 'json');
- return false;
- });
-
- $('#tasks_edit').live('click',function(){
- var id = $('#task_details').data('id');
- $.getJSON('ajax/edittaskform.php',{'id':id},function(jsondata){
- if(jsondata.status == 'success'){
- $('#task_details').html(jsondata.data.page);
- $('#task_details #categories').multiple_autocomplete({source: categories});
- }
- else{
- alert(jsondata.data.message);
- }
- });
- return false;
- });
-
- $('#tasks_edittaskform #percent_complete').live('change',function(event){
- if ($(event.target).val() == 100){
- $('#tasks_edittaskform #complete').show();
- }else{
- $('#tasks_edittaskform #complete').hide();
- }
- });
-
- $('#tasks_edittaskform input[type="submit"]').live('click',function(){
- $.post('ajax/edittask.php',$('#tasks_edittaskform').serialize(),function(jsondata){
- $('.error_msg').remove();
- $('.error').removeClass('error');
- if(jsondata.status == 'success'){
- var id = jsondata.data.id;
- $('#task_details').data('id',id);
- $('#task_details').html(jsondata.data.page);
- var task = jsondata.data.task;
- $('#tasks .task[data-id='+id+']')
- .data('task', task)
- .html(OC.Tasks.create_task_div(task).html());
- }
- else{
- var errors = jsondata.data.errors;
- for (k in errors){
- $('#'+k).addClass('error')
- .after('<span class="error_msg">'+errors[k]+'</span>');
- }
- $('.error_msg').effect('highlight', {}, 3000);
- $('.error').effect('highlight', {}, 3000);
- }
- }, 'json');
- return false;
- });
-
OCCategories.app = 'calendar';
});
diff --git a/apps/tasks/lib/app.php b/apps/tasks/lib/app.php
index 1b42968f0be..a97c6b95d1d 100644
--- a/apps/tasks/lib/app.php
+++ b/apps/tasks/lib/app.php
@@ -77,24 +77,24 @@ class OC_Task_App {
public static function validateRequest($request)
{
$errors = array();
- if($request['summary'] == ''){
+ if($request['summary'] == '') {
$errors['summary'] = self::$l10n->t('Empty Summary');
}
try {
- $timezone = OCP\Config::getUserValue(OCP\User::getUser(), "calendar", "timezone", "Europe/London");
+ $timezone = OC_Calendar_App::getTimezone();
$timezone = new DateTimeZone($timezone);
new DateTime($request['due'], $timezone);
} catch (Exception $e) {
$errors['due'] = self::$l10n->t('Invalid date/time');
}
- if ($request['percent_complete'] < 0 || $request['percent_complete'] > 100){
+ if ($request['percent_complete'] < 0 || $request['percent_complete'] > 100) {
$errors['percent_complete'] = self::$l10n->t('Invalid percent complete');
}
- if ($request['percent_complete'] == 100 && !empty($request['completed'])){
+ if ($request['percent_complete'] == 100 && !empty($request['completed'])) {
try {
- $timezone = OCP\Config::getUserValue(OCP\User::getUser(), "calendar", "timezone", "Europe/London");
+ $timezone = OC_Calendar_App::getTimezone();
$timezone = new DateTimeZone($timezone);
new DateTime($request['completed'], $timezone);
} catch (Exception $e) {
@@ -147,7 +147,7 @@ class OC_Task_App {
$vtodo->setString('PRIORITY', $priority);
if ($due) {
- $timezone = OCP\Config::getUserValue(OCP\User::getUser(), 'calendar', 'timezone', date_default_timezone_get());
+ $timezone = OC_Calendar_App::getTimezone();
$timezone = new DateTimeZone($timezone);
$due = new DateTime($due, $timezone);
$vtodo->setDateTime('DUE', $due);
@@ -168,15 +168,15 @@ class OC_Task_App {
$vtodo->__unset('PERCENT-COMPLETE');
}
- if ($percent_complete == 100){
- if (!$completed){
+ if ($percent_complete == 100) {
+ if (!$completed) {
$completed = 'now';
}
} else {
$completed = null;
}
if ($completed) {
- $timezone = OCP\Config::getUserValue(OCP\User::getUser(), 'calendar', 'timezone', date_default_timezone_get());
+ $timezone = OC_Calendar_App::getTimezone();
$timezone = new DateTimeZone($timezone);
$completed = new DateTime($completed, $timezone);
$vtodo->setDateTime('COMPLETED', $completed);
diff --git a/apps/tasks/templates/part.addtaskform.php b/apps/tasks/templates/part.addtaskform.php
deleted file mode 100644
index 0fad5592aa7..00000000000
--- a/apps/tasks/templates/part.addtaskform.php
+++ /dev/null
@@ -1,15 +0,0 @@
-<form id="tasks_addtaskform">
- <?php if(count($_['calendars'])==1): ?>
- <input type="hidden" name="id" value="<?php echo $_['calendars'][0]['id']; ?>">
- <?php else: ?>
- <label for="id"><?php echo $l->t('Calendar'); ?></label>
- <select name="id" size="1">
- <?php foreach($_['calendars'] as $calendar): ?>
- <option value="<?php echo $calendar['id']; ?>"><?php echo $calendar['displayname']; ?></option>
- <?php endforeach; ?>
- </select>
- <br>
- <?php endif; ?>
- <?php echo $this->inc('part.taskform'); ?>
- <input type="submit" name="submit" value="<?php echo $l->t('Create Task'); ?>">
-</form>
diff --git a/apps/tasks/templates/part.details.php b/apps/tasks/templates/part.details.php
deleted file mode 100644
index 89636b6e762..00000000000
--- a/apps/tasks/templates/part.details.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php if(isset($_['details']->SUMMARY)): ?>
-<table>
-<?php
-echo $this->inc('part.property', array('label' => $l->t('Summary'), 'property' => $_['details']->SUMMARY));
-if(isset($_['details']->LOCATION)):
- echo $this->inc('part.property', array('label' => $l->t('Location'), 'property' => $_['details']->LOCATION));
-endif;
-if(isset($_['details']->CATEGORIES)):
- echo $this->inc('part.property', array('label' => $l->t('Categories'), 'property' => $_['details']->CATEGORIES));
-endif;
-if(isset($_['details']->DUE)):
- echo $this->inc('part.property', array('label' => $l->t('Due'), 'property' => $_['details']->DUE[0]));
-endif;
-if(isset($_['details']->PRIORITY)):
- echo $this->inc('part.property', array('label' => $l->t('Priority'), 'property' => $_['details']->PRIORITY[0], 'options' => $_['priority_options']));
-endif;
-if($_['details']->__isset('PERCENT-COMPLETE') || isset($_['details']->COMPLETED)):
-?>
-<tr>
- <th>
- <?php echo $l->t('Complete') ?>
- </th>
- <td>
-<?php if($_['details']->__isset('PERCENT-COMPLETE')):
- echo $_['details']->__get('PERCENT-COMPLETE')->value.' % ';
- endif;
- if(isset($_['details']->COMPLETED)):
- echo $l->t('on '). $l->l('datetime', $_['details']->COMPLETED[0]->getDateTime());
- endif;
- echo '</tr>';
-endif;
-if(isset($_['details']->DESCRIPTION)):
- echo $this->inc('part.property', array('label' => $l->t('Description'), 'property' => $_['details']->DESCRIPTION));
-endif; ?>
-</table>
-<form>
- <input type="button" id="tasks_delete" value="<?php echo $l->t('Delete');?>">
- <input type="button" id="tasks_edit" value="<?php echo $l->t('Edit');?>">
-</form>
-<?php else: ?>
-<?php //var_dump($_['details']); ?>
-<?php endif ?>
diff --git a/apps/tasks/templates/part.edittaskform.php b/apps/tasks/templates/part.edittaskform.php
deleted file mode 100644
index fe123f07ac6..00000000000
--- a/apps/tasks/templates/part.edittaskform.php
+++ /dev/null
@@ -1,5 +0,0 @@
-<form id="tasks_edittaskform">
- <input type="hidden" name="id" value="<?php echo $_['id']; ?>">
- <?php echo $this->inc('part.taskform'); ?>
- <input type="submit" name="submit" value="<?php echo $l->t('Update Task'); ?>">
-</form>
diff --git a/apps/tasks/templates/part.property.php b/apps/tasks/templates/part.property.php
deleted file mode 100644
index 591fd363e6f..00000000000
--- a/apps/tasks/templates/part.property.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<tr>
- <th>
- <?php echo $_['label'] ?>
- </th>
- <td>
- <?php
- switch (get_class($_['property']))
- {
- case 'Sabre_VObject_Element_DateTime':
- echo $l->l('datetime', $_['property']->getDateTime());
- break;
- default:
- $value = $_['property']->value;
- if (isset($_['options']))
- {
- $value = $_['options'][$value];
- }
- echo nl2br($value);
- }
- ?>
- </td>
-</tr>
diff --git a/apps/tasks/templates/part.taskform.php b/apps/tasks/templates/part.taskform.php
deleted file mode 100644
index 0a25ed33c91..00000000000
--- a/apps/tasks/templates/part.taskform.php
+++ /dev/null
@@ -1,36 +0,0 @@
- <label for="summary"><?php echo $l->t('Summary'); ?></label>
- <input type="text" id="summary" name="summary" placeholder="<?php echo $l->t('Summary of the task');?>" value="<?php echo isset($_['details']->SUMMARY) ? $_['details']->SUMMARY[0]->value : '' ?>">
- <br>
- <label for="location"><?php echo $l->t('Location'); ?></label>
- <input type="text" id="location" name="location" placeholder="<?php echo $l->t('Location of the task');?>" value="<?php echo isset($_['details']->LOCATION) ? $_['details']->LOCATION[0]->value : '' ?>">
- <br>
- <label for="categories"><?php echo $l->t('Categories'); ?></label>
- <input id="categories" name="categories" type="text" placeholder="<?php echo $l->t('Separate categories with commas'); ?>" value="<?php echo isset($_['categories']) ? $_['categories'] : '' ?>">
- <a class="action edit" onclick="$(this).tipsy('hide');OCCategories.edit();" title="<?php echo $l->t('Edit categories'); ?>"><img alt="<?php echo $l->t('Edit categories'); ?>" src="<?php echo OCP\image_path('core','actions/rename.svg')?>" class="svg action" style="width: 16px; height: 16px;"></a>
- <br>
- <label for="due"><?php echo $l->t('Due'); ?></label>
- <input type="text" id="due" name="due" placeholder="<?php echo $l->t('Due date') ?>" value="<?php echo isset($_['details']->DUE) ? $l->l('datetime', $_['details']->DUE[0]->getDateTime()) : '' ?>">
- <br>
- <select name="percent_complete" id="percent_complete">
- <?php
- foreach($_['percent_options'] as $percent){
- echo '<option value="' . $percent . '"' . (($_['details']->__get('PERCENT-COMPLETE') && $percent == $_['details']->__get('PERCENT-COMPLETE')->value) ? ' selected="selected"' : '') . '>' . $percent . ' %</option>';
- }
- ?>
- </select>
- <label for="percent_complete"><?php echo $l->t('Complete'); ?></label>
- <span id="complete"<?php echo ($_['details']->__get('PERCENT-COMPLETE') && $_['details']->__get('PERCENT-COMPLETE')->value == 100) ? '' : ' style="display:none;"' ?>><label for="completed"><?php echo $l->t('completed on'); ?></label>
- <input type="text" id="completed" name="completed" value="<?php echo isset($_['details']->COMPLETED) ? $l->l('datetime', $_['details']->COMPLETED[0]->getDateTime()) : '' ?>"></span>
- <br>
- <label for="priority"><?php echo $l->t('Priority'); ?></label>
- <select name="priority">
- <?php
- foreach($_['priority_options'] as $priority => $label){
- echo '<option value="' . $priority . '"' . ((isset($_['details']->PRIORITY) && $priority == $_['details']->PRIORITY->value) ? ' selected="selected"' : '') . '>' . $label . '</option>';
- }
- ?>
- </select>
- <br>
- <label for="description"><?php echo $l->t('Description'); ?></label><br>
- <textarea placeholder="<?php echo $l->t('Description of the task');?>" name="description"><?php echo isset($_['details']->DESCRIPTION) ? $_['details']->DESCRIPTION[0]->value : '' ?></textarea>
- <br>
diff --git a/apps/tasks/templates/part.tasks.php b/apps/tasks/templates/part.tasks.php
deleted file mode 100644
index 50be1cd6bed..00000000000
--- a/apps/tasks/templates/part.tasks.php
+++ /dev/null
@@ -1,3 +0,0 @@
-<?php foreach( $_['tasks'] as $task ): ?>
- <li data-id="<?php echo $task['id']; ?>"><a href="index.php?id=<?php echo $task['id']; ?>"><?php echo $task['name']; ?></a> </li>
-<?php endforeach; ?>
diff --git a/apps/user_external/appinfo/app.php b/apps/user_external/appinfo/app.php
new file mode 100644
index 00000000000..c7408ec30d9
--- /dev/null
+++ b/apps/user_external/appinfo/app.php
@@ -0,0 +1,4 @@
+<?php
+OC::$CLASSPATH['OC_User_IMAP']='apps/user_external/lib/imap.php';
+OC::$CLASSPATH['OC_User_SMB']='apps/user_external/lib/smb.php';
+OC::$CLASSPATH['OC_User_FTP']='apps/user_external/lib/ftp.php';
diff --git a/apps/user_external/appinfo/info.xml b/apps/user_external/appinfo/info.xml
new file mode 100644
index 00000000000..1d1dcee5401
--- /dev/null
+++ b/apps/user_external/appinfo/info.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<info>
+ <id>user_external</id>
+ <name>External user support</name>
+ <description>Use external user authentication methods</description>
+ <licence>AGPL</licence>
+ <author>Robin Appelman</author>
+ <require>4</require>
+ <shipped>true</shipped>
+ <types>
+ <authentication/>
+ </types>
+</info>
diff --git a/apps/user_external/appinfo/version b/apps/user_external/appinfo/version
new file mode 100644
index 00000000000..ceab6e11ece
--- /dev/null
+++ b/apps/user_external/appinfo/version
@@ -0,0 +1 @@
+0.1 \ No newline at end of file
diff --git a/apps/user_external/lib/ftp.php b/apps/user_external/lib/ftp.php
new file mode 100644
index 00000000000..e03e17d2b6a
--- /dev/null
+++ b/apps/user_external/lib/ftp.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+class OC_User_FTP extends OC_User_Backend{
+ private $host;
+ private $secure;
+ private $protocol;
+
+ public function __construct($host,$secure=false){
+ $this->host=$host;
+ $this->secure=$secure;
+ $this->protocol='ftp';
+ if($this->secure){
+ $this->protocol.='s';
+ }
+ $this->protocol.='://';
+ }
+
+ /**
+ * @brief Check if the password is correct
+ * @param $uid The username
+ * @param $password The password
+ * @returns true/false
+ *
+ * Check if the password is correct without logging in the user
+ */
+ public function checkPassword($uid, $password){
+ $url=$this->protocol.$uid.':'.$password.'@'.$this->host.'/';
+ $result=@opendir($url);
+ if(is_resource($result)){
+ return $uid;
+ }else{
+ return false;
+ }
+ }
+
+ public function userExists($uid){
+ return true;
+ }
+}
diff --git a/apps/user_external/lib/imap.php b/apps/user_external/lib/imap.php
new file mode 100644
index 00000000000..584e9804b18
--- /dev/null
+++ b/apps/user_external/lib/imap.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+class OC_User_IMAP extends OC_User_Backend{
+ private $mailbox;
+
+ public function __construct($mailbox){
+ $this->mailbox=$mailbox;
+ }
+
+ /**
+ * @brief Check if the password is correct
+ * @param $uid The username
+ * @param $password The password
+ * @returns true/false
+ *
+ * Check if the password is correct without logging in the user
+ */
+ public function checkPassword($uid, $password){
+ $mbox = @imap_open($this->mailbox, $uid, $password);
+ imap_errors();
+ imap_alerts();
+ if($mbox){
+ imap_close($mbox);
+ return $uid;
+ }else{
+ return false;
+ }
+ }
+
+ public function userExists($uid){
+ return true;
+ }
+}
+
diff --git a/apps/user_external/lib/smb.php b/apps/user_external/lib/smb.php
new file mode 100644
index 00000000000..44d2b7903d8
--- /dev/null
+++ b/apps/user_external/lib/smb.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+class OC_User_SMB extends OC_User_Backend{
+ private $host;
+
+ const smbclient='smbclient';
+ const loginError='NT_STATUS_LOGON_FAILURE';
+
+ public function __construct($host){
+ $this->host=$host;
+ }
+
+ /**
+ * @brief Check if the password is correct
+ * @param $uid The username
+ * @param $password The password
+ * @returns true/false
+ *
+ * Check if the password is correct without logging in the user
+ */
+ public function checkPassword($uid, $password){
+ $uidEscaped=escapeshellarg($uid);
+ $password=escapeshellarg($password);
+ $result=array();
+ $command=self::smbclient.' //'.$this->host.'/dummy -U'.$uidEscaped.'%'.$password;
+ $result=exec($command,$result);
+ if(substr($result,-strlen(self::loginError))==self::loginError){
+ return false;
+ }else{
+ return $uid;
+ }
+ }
+
+ public function userExists($uid){
+ return true;
+ }
+} \ No newline at end of file
diff --git a/apps/user_external/tests/config.php b/apps/user_external/tests/config.php
new file mode 100644
index 00000000000..64ee141d32d
--- /dev/null
+++ b/apps/user_external/tests/config.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+return array(
+ 'imap'=>array(
+ 'run'=>false,
+ 'mailbox'=>'{imap.gmail.com:993/imap/ssl}INBOX', //see http://php.net/manual/en/function.imap-open.php
+ 'user'=>'foo',//valid username/password combination
+ 'password'=>'bar',
+ ),
+ 'smb'=>array(
+ 'run'=>false,
+ 'host'=>'localhost',
+ 'user'=>'test',//valid username/password combination
+ 'password'=>'test',
+ ),
+ 'ftp'=>array(
+ 'run'=>false,
+ 'host'=>'localhost',
+ 'user'=>'test',//valid username/password combination
+ 'password'=>'test',
+ ),
+);
diff --git a/apps/user_external/tests/ftp.php b/apps/user_external/tests/ftp.php
new file mode 100644
index 00000000000..0cf7565f9c6
--- /dev/null
+++ b/apps/user_external/tests/ftp.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+class Test_User_FTP extends UnitTestCase{
+ /**
+ * @var OC_User_IMAP $instance
+ */
+ private $instance;
+
+ private function getConfig(){
+ return include(__DIR__.'/config.php');
+ }
+
+ function skip(){
+ $config=$this->getConfig();
+ $this->skipUnless($config['ftp']['run']);
+ }
+
+ function setUp(){
+ $config=$this->getConfig();
+ $this->instance=new OC_User_FTP($config['ftp']['host']);
+ }
+
+ function testLogin(){
+ $config=$this->getConfig();
+ $this->assertEqual($config['ftp']['user'],$this->instance->checkPassword($config['ftp']['user'],$config['ftp']['password']));
+ $this->assertFalse($this->instance->checkPassword($config['ftp']['user'],$config['ftp']['password'].'foo'));
+ }
+}
diff --git a/apps/user_external/tests/imap.php b/apps/user_external/tests/imap.php
new file mode 100644
index 00000000000..c703b32107f
--- /dev/null
+++ b/apps/user_external/tests/imap.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+class Test_User_Imap extends UnitTestCase{
+ /**
+ * @var OC_User_IMAP $instance
+ */
+ private $instance;
+
+ private function getConfig(){
+ return include(__DIR__.'/config.php');
+ }
+
+ function skip(){
+ $config=$this->getConfig();
+ $this->skipUnless($config['imap']['run']);
+ }
+
+ function setUp(){
+ $config=$this->getConfig();
+ $this->instance=new OC_User_IMAP($config['imap']['mailbox']);
+ }
+
+ function testLogin(){
+ $config=$this->getConfig();
+ $this->assertEqual($config['imap']['user'],$this->instance->checkPassword($config['imap']['user'],$config['imap']['password']));
+ $this->assertFalse($this->instance->checkPassword($config['imap']['user'],$config['imap']['password'].'foo'));
+ }
+}
diff --git a/apps/user_external/tests/smb.php b/apps/user_external/tests/smb.php
new file mode 100644
index 00000000000..1ed7eb934be
--- /dev/null
+++ b/apps/user_external/tests/smb.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+class Test_User_SMB extends UnitTestCase{
+ /**
+ * @var OC_User_IMAP $instance
+ */
+ private $instance;
+
+ private function getConfig(){
+ return include(__DIR__.'/config.php');
+ }
+
+ function skip(){
+ $config=$this->getConfig();
+ $this->skipUnless($config['smb']['run']);
+ }
+
+ function setUp(){
+ $config=$this->getConfig();
+ $this->instance=new OC_User_SMB($config['smb']['host']);
+ }
+
+ function testLogin(){
+ $config=$this->getConfig();
+ $this->assertEqual($config['smb']['user'],$this->instance->checkPassword($config['smb']['user'],$config['smb']['password']));
+ $this->assertFalse($this->instance->checkPassword($config['smb']['user'],$config['smb']['password'].'foo'));
+ }
+}
diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php
index 330574c1d42..3c6da47d71a 100644
--- a/apps/user_ldap/appinfo/app.php
+++ b/apps/user_ldap/appinfo/app.php
@@ -21,15 +21,17 @@
*
*/
-require_once('apps/user_ldap/lib_ldap.php');
-require_once('apps/user_ldap/user_ldap.php');
-require_once('apps/user_ldap/group_ldap.php');
+OCP\App::registerAdmin('user_ldap', 'settings');
-OCP\App::registerAdmin('user_ldap','settings');
+$connector = new OCA\user_ldap\lib\Connection('user_ldap');
+$userBackend = new OCA\user_ldap\USER_LDAP();
+$userBackend->setConnector($connector);
+$groupBackend = new OCA\user_ldap\GROUP_LDAP();
+$groupBackend->setConnector($connector);
// register user backend
-OC_User::useBackend( 'LDAP' );
-OC_Group::useBackend( new OC_GROUP_LDAP() );
+OC_User::useBackend($userBackend);
+OC_Group::useBackend($groupBackend);
// add settings page to navigation
$entry = array(
diff --git a/apps/user_ldap/group_ldap.php b/apps/user_ldap/group_ldap.php
index d438c7d84df..b9f4bdf1990 100644
--- a/apps/user_ldap/group_ldap.php
+++ b/apps/user_ldap/group_ldap.php
@@ -21,24 +21,22 @@
*
*/
-class OC_GROUP_LDAP extends OC_Group_Backend {
-// //group specific settings
- protected $ldapGroupFilter;
- protected $ldapGroupMemberAssocAttr;
- protected $configured = false;
+namespace OCA\user_ldap;
+
+class GROUP_LDAP extends lib\Access implements \OCP\GroupInterface {
+ protected $enabled = false;
protected $_group_user = array();
protected $_user_groups = array();
protected $_group_users = array();
protected $_groups = array();
- public function __construct() {
- $this->ldapGroupFilter = OCP\Config::getAppValue('user_ldap', 'ldap_group_filter', '(objectClass=posixGroup)');
- $this->ldapGroupMemberAssocAttr = OCP\Config::getAppValue('user_ldap', 'ldap_group_member_assoc_attribute', 'uniqueMember');
-
- if(!empty($this->ldapGroupFilter) && !empty($this->ldapGroupMemberAssocAttr)) {
- $this->configured = true;
+ public function setConnector(lib\Connection &$connection) {
+ parent::setConnector($connection);
+ if(empty($this->connection->ldapGroupFilter) || empty($this->connection->ldapGroupMemberAssocAttr)) {
+ $this->enabled = false;
}
+ $this->enabled = true;
}
/**
@@ -50,31 +48,31 @@ class OC_GROUP_LDAP extends OC_Group_Backend {
* Checks whether the user is member of a group or not.
*/
public function inGroup($uid, $gid) {
- if(!$this->configured) {
+ if(!$this->enabled) {
return false;
}
if(isset($this->_group_user[$gid][$uid])) {
return $this->_group_user[$gid][$uid];
}
- $dn_user = OC_LDAP::username2dn($uid);
- $dn_group = OC_LDAP::groupname2dn($gid);
+ $dn_user = $this->username2dn($uid);
+ $dn_group = $this->groupname2dn($gid);
// just in case
if(!$dn_group || !$dn_user) {
return false;
}
//usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
- $members = OC_LDAP::readAttribute($dn_group, $this->ldapGroupMemberAssocAttr);
+ $members = $this->readAttribute($dn_group, $this->connection->ldapGroupMemberAssocAttr);
if(!$members) {
return false;
}
//extra work if we don't get back user DNs
//TODO: this can be done with one LDAP query
- if(strtolower($this->ldapGroupMemberAssocAttr) == 'memberuid') {
+ if(strtolower($this->connection->ldapGroupMemberAssocAttr) == 'memberuid') {
$dns = array();
foreach($members as $mid) {
- $filter = str_replace('%uid', $mid, OC_LDAP::conf('ldapLoginFilter'));
- $ldap_users = OC_LDAP::fetchListOfUsers($filter, 'dn');
+ $filter = str_replace('%uid', $mid, $this->connection->ldapLoginFilter);
+ $ldap_users = $this->fetchListOfUsers($filter, 'dn');
if(count($ldap_users) < 1) {
continue;
}
@@ -96,36 +94,37 @@ class OC_GROUP_LDAP extends OC_Group_Backend {
* if the user exists at all.
*/
public function getUserGroups($uid) {
- if(!$this->configured) {
+ if(!$this->enabled) {
return array();
}
if(isset($this->_user_groups[$uid])) {
return $this->_user_groups[$uid];
}
- $userDN = OC_LDAP::username2dn($uid);
+ $userDN = $this->username2dn($uid);
if(!$userDN) {
$this->_user_groups[$uid] = array();
return array();
}
//uniqueMember takes DN, memberuid the uid, so we need to distinguish
- if((strtolower($this->ldapGroupMemberAssocAttr) == 'uniquemember')
- || (strtolower($this->ldapGroupMemberAssocAttr) == 'member')) {
+ if((strtolower($this->connection->ldapGroupMemberAssocAttr) == 'uniquemember')
+ || (strtolower($this->connection->ldapGroupMemberAssocAttr) == 'member')
+ ) {
$uid = $userDN;
- } else if(strtolower($this->ldapGroupMemberAssocAttr) == 'memberuid') {
- $result = OC_LDAP::readAttribute($userDN, 'uid');
+ } else if(strtolower($this->connection->ldapGroupMemberAssocAttr) == 'memberuid') {
+ $result = $this->readAttribute($userDN, 'uid');
$uid = $result[0];
} else {
// just in case
$uid = $userDN;
}
- $filter = OC_LDAP::combineFilterWithAnd(array(
- $this->ldapGroupFilter,
- $this->ldapGroupMemberAssocAttr.'='.$uid
+ $filter = $this->combineFilterWithAnd(array(
+ $this->connection->ldapGroupFilter,
+ $this->connection->ldapGroupMemberAssocAttr.'='.$uid
));
- $groups = OC_LDAP::fetchListOfGroups($filter, array(OC_LDAP::conf('ldapGroupDisplayName'),'dn'));
- $this->_user_groups[$uid] = array_unique(OC_LDAP::ownCloudGroupNames($groups), SORT_LOCALE_STRING);
+ $groups = $this->fetchListOfGroups($filter, array($this->connection->ldapGroupDisplayName,'dn'));
+ $this->_user_groups[$uid] = array_unique($this->ownCloudGroupNames($groups), SORT_LOCALE_STRING);
return $this->_user_groups[$uid];
}
@@ -135,44 +134,44 @@ class OC_GROUP_LDAP extends OC_Group_Backend {
* @returns array with user ids
*/
public function usersInGroup($gid) {
- if(!$this->configured) {
+ if(!$this->enabled) {
return array();
}
if(isset($this->_group_users[$gid])) {
return $this->_group_users[$gid];
}
- $groupDN = OC_LDAP::groupname2dn($gid);
+ $groupDN = $this->groupname2dn($gid);
if(!$groupDN) {
$this->_group_users[$gid] = array();
return array();
}
- $members = OC_LDAP::readAttribute($groupDN, $this->ldapGroupMemberAssocAttr);
+ $members = $this->readAttribute($groupDN, $this->connection->ldapGroupMemberAssocAttr);
if(!$members) {
$this->_group_users[$gid] = array();
return array();
}
$result = array();
- $isMemberUid = (strtolower($this->ldapGroupMemberAssocAttr) == 'memberuid');
+ $isMemberUid = (strtolower($this->connection->ldapGroupMemberAssocAttr) == 'memberuid');
foreach($members as $member) {
if($isMemberUid) {
- $filter = OCP\Util::mb_str_replace('%uid', $member, OC_LDAP::conf('ldapLoginFilter'), 'UTF-8');
- $ldap_users = OC_LDAP::fetchListOfUsers($filter, 'dn');
+ $filter = \OCP\Util::mb_str_replace('%uid', $member, $this->connection->ldapLoginFilter, 'UTF-8');
+ $ldap_users = $this->fetchListOfUsers($filter, 'dn');
if(count($ldap_users) < 1) {
continue;
}
- $result[] = OC_LDAP::dn2username($ldap_users[0]);
+ $result[] = $this->dn2username($ldap_users[0]);
continue;
} else {
- if($ocname = OC_LDAP::dn2username($member)){
+ if($ocname = $this->dn2username($member)) {
$result[] = $ocname;
}
}
}
if(!$isMemberUid) {
- $result = array_intersect($result, OCP\User::getUsers());
+ $result = array_intersect($result, \OCP\User::getUsers());
}
$this->_group_users[$gid] = array_unique($result, SORT_LOCALE_STRING);
return $this->_group_users[$gid];
@@ -185,12 +184,12 @@ class OC_GROUP_LDAP extends OC_Group_Backend {
* Returns a list with all groups
*/
public function getGroups() {
- if(!$this->configured) {
+ if(!$this->enabled) {
return array();
}
if(empty($this->_groups)) {
- $ldap_groups = OC_LDAP::fetchListOfGroups($this->ldapGroupFilter, array(OC_LDAP::conf('ldapGroupDisplayName'), 'dn'));
- $this->_groups = OC_LDAP::ownCloudGroupNames($ldap_groups);
+ $ldap_groups = $this->fetchListOfGroups($this->connection->ldapGroupFilter, array($this->connection->ldapGroupDisplayName, 'dn'));
+ $this->_groups = $this->ownCloudGroupNames($ldap_groups);
}
return $this->_groups;
}
@@ -203,4 +202,17 @@ class OC_GROUP_LDAP extends OC_Group_Backend {
public function groupExists($gid){
return in_array($gid, $this->getGroups());
}
+
+ /**
+ * @brief Check if backend implements actions
+ * @param $actions bitwise-or'ed actions
+ * @returns boolean
+ *
+ * Returns the supported actions as int to be
+ * compared with OC_USER_BACKEND_CREATE_USER etc.
+ */
+ public function implementsActions($actions) {
+ //always returns false, because possible actions are modifying actions. We do not write to LDAP, at least for now.
+ return false;
+ }
} \ No newline at end of file
diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php
new file mode 100644
index 00000000000..19122b34c7d
--- /dev/null
+++ b/apps/user_ldap/lib/access.php
@@ -0,0 +1,597 @@
+<?php
+
+/**
+ * ownCloud – LDAP Access
+ *
+ * @author Arthur Schiwon
+ * @copyright 2012 Arthur Schiwon blizzz@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/>.
+ *
+ */
+
+namespace OCA\user_ldap\lib;
+
+abstract class Access {
+ protected $connection;
+
+ public function setConnector(Connection &$connection) {
+ $this->connection = $connection;
+ }
+
+ private function checkConnection() {
+ return ($this->connection instanceof Connection);
+ }
+
+ /**
+ * @brief reads a given attribute for an LDAP record identified by a DN
+ * @param $dn the record in question
+ * @param $attr the attribute that shall be retrieved
+ * @returns the values in an array on success, false otherwise
+ *
+ * Reads an attribute from an LDAP entry
+ */
+ public function readAttribute($dn, $attr) {
+ if(!$this->checkConnection()) {
+ \OCP\Util::writeLog('user_ldap', 'No LDAP Connector assigned, access impossible for readAttribute.', \OCP\Util::WARN);
+ return false;
+ }
+ $cr = $this->connection->getConnectionResource();
+ if(!is_resource($cr)) {
+ //LDAP not available
+ return false;
+ }
+ $rr = @ldap_read($cr, $dn, 'objectClass=*', array($attr));
+ if(!is_resource($rr)) {
+ \OCP\Util::writeLog('user_ldap', 'readAttribute failed for DN '.$dn, \OCP\Util::DEBUG);
+ //in case an error occurs , e.g. object does not exist
+ return false;
+ }
+ $er = ldap_first_entry($cr, $rr);
+ //LDAP attributes are not case sensitive
+ $result = \OCP\Util::mb_array_change_key_case(ldap_get_attributes($cr, $er), MB_CASE_LOWER, 'UTF-8');
+ $attr = mb_strtolower($attr, 'UTF-8');
+
+ if(isset($result[$attr]) && $result[$attr]['count'] > 0) {
+ $values = array();
+ for($i=0;$i<$result[$attr]['count'];$i++) {
+ $values[] = $this->resemblesDN($attr) ? $this->sanitizeDN($result[$attr][$i]) : $result[$attr][$i];
+ }
+ return $values;
+ }
+ return false;
+ }
+
+ /**
+ * @brief checks wether the given attribute`s valua is probably a DN
+ * @param $attr the attribute in question
+ * @return if so true, otherwise false
+ */
+ private function resemblesDN($attr) {
+ $resemblingAttributes = array(
+ 'dn',
+ 'uniquemember',
+ 'member'
+ );
+ return in_array($attr, $resemblingAttributes);
+ }
+
+ /**
+ * @brief sanitizes a DN received from the LDAP server
+ * @param $dn the DN in question
+ * @return the sanitized DN
+ */
+ private function sanitizeDN($dn) {
+ //OID sometimes gives back DNs with whitespace after the comma a la "uid=foo, cn=bar, dn=..." We need to tackle this!
+ $dn = preg_replace('/([^\\\]),(\s+)/u', '\1,', $dn);
+
+ //make comparisons and everything work
+ $dn = mb_strtolower($dn, 'UTF-8');
+
+ return $dn;
+ }
+
+ /**
+ * gives back the database table for the query
+ */
+ private function getMapTable($isUser) {
+ if($isUser) {
+ return '*PREFIX*ldap_user_mapping';
+ } else {
+ return '*PREFIX*ldap_group_mapping';
+ }
+ }
+
+ /**
+ * @brief returns the LDAP DN for the given internal ownCloud name of the group
+ * @param $name the ownCloud name in question
+ * @returns string with the LDAP DN on success, otherwise false
+ *
+ * returns the LDAP DN for the given internal ownCloud name of the group
+ */
+ public function groupname2dn($name) {
+ return $this->ocname2dn($name, false);
+ }
+
+ /**
+ * @brief returns the LDAP DN for the given internal ownCloud name of the user
+ * @param $name the ownCloud name in question
+ * @returns string with the LDAP DN on success, otherwise false
+ *
+ * returns the LDAP DN for the given internal ownCloud name of the user
+ */
+ public function username2dn($name) {
+ $dn = $this->ocname2dn($name, true);
+ if($dn) {
+ return $dn;
+ } else {
+ //fallback: user is not mapped
+ $filter = $this->combineFilterWithAnd(array(
+ $this->connection->ldapUserFilter,
+ $this->connection->ldapUserDisplayName . '=' . $name,
+ ));
+ $result = $this->searchUsers($filter, 'dn');
+ if(isset($result[0]['dn'])) {
+ $this->mapComponent($result[0], $name, true);
+ return $result[0];
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @brief returns the LDAP DN for the given internal ownCloud name
+ * @param $name the ownCloud name in question
+ * @param $isUser is it a user? otherwise group
+ * @returns string with the LDAP DN on success, otherwise false
+ *
+ * returns the LDAP DN for the given internal ownCloud name
+ */
+ private function ocname2dn($name, $isUser) {
+ $table = $this->getMapTable($isUser);
+
+ $query = \OCP\DB::prepare('
+ SELECT ldap_dn
+ FROM '.$table.'
+ WHERE owncloud_name = ?
+ ');
+
+ $record = $query->execute(array($name))->fetchOne();
+ return $record;
+ }
+
+ /**
+ * @brief returns the internal ownCloud name for the given LDAP DN of the group
+ * @param $dn the dn of the group object
+ * @param $ldapname optional, the display name of the object
+ * @returns string with with the name to use in ownCloud, false on DN outside of search DN
+ *
+ * returns the internal ownCloud name for the given LDAP DN of the group
+ */
+ public function dn2groupname($dn, $ldapname = null) {
+ if(mb_strripos($dn, $this->connection->ldapBaseGroups, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($this->connection->ldapBaseGroups, 'UTF-8'))) {
+ return false;
+ }
+ return $this->dn2ocname($dn, $ldapname, false);
+ }
+
+ /**
+ * @brief returns the internal ownCloud name for the given LDAP DN of the user
+ * @param $dn the dn of the user object
+ * @param $ldapname optional, the display name of the object
+ * @returns string with with the name to use in ownCloud
+ *
+ * returns the internal ownCloud name for the given LDAP DN of the user, false on DN outside of search DN
+ */
+ public function dn2username($dn, $ldapname = null) {
+ if(mb_strripos($dn, $this->connection->ldapBaseUsers, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen($this->connection->ldapBaseUsers, 'UTF-8'))) {
+ return false;
+ }
+ return $this->dn2ocname($dn, $ldapname, true);
+ }
+
+ /**
+ * @brief returns an internal ownCloud name for the given LDAP DN
+ * @param $dn the dn of the user object
+ * @param $ldapname optional, the display name of the object
+ * @param $isUser optional, wether it is a user object (otherwise group assumed)
+ * @returns string with with the name to use in ownCloud
+ *
+ * returns the internal ownCloud name for the given LDAP DN of the user, false on DN outside of search DN
+ */
+ public function dn2ocname($dn, $ldapname = null, $isUser = true) {
+ $dn = $this->sanitizeDN($dn);
+ $table = $this->getMapTable($isUser);
+ if($isUser) {
+ $nameAttribute = $this->connection->ldapUserDisplayName;
+ } else {
+ $nameAttribute = $this->connection->ldapGroupDisplayName;
+ }
+
+ $query = \OCP\DB::prepare('
+ SELECT owncloud_name
+ FROM '.$table.'
+ WHERE ldap_dn = ?
+ ');
+
+ $component = $query->execute(array($dn))->fetchOne();
+ if($component) {
+ return $component;
+ }
+
+ if(is_null($ldapname)) {
+ $ldapname = $this->readAttribute($dn, $nameAttribute);
+ $ldapname = $ldapname[0];
+ }
+ $ldapname = $this->sanitizeUsername($ldapname);
+
+ //a new user/group! Then let's try to add it. We're shooting into the blue with the user/group name, assuming that in most cases there will not be a conflict. Otherwise an error will occur and we will continue with our second shot.
+ if($this->mapComponent($dn, $ldapname, $isUser)) {
+ return $ldapname;
+ }
+
+ //doh! There is a conflict. We need to distinguish between users/groups. Adding indexes is an idea, but not much of a help for the user. The DN is ugly, but for now the only reasonable way. But we transform it to a readable format and remove the first part to only give the path where this object is located.
+ $oc_name = $this->alternateOwnCloudName($ldapname, $dn);
+ if($this->mapComponent($dn, $oc_name, $isUser)) {
+ return $oc_name;
+ }
+
+ //TODO: do not simple die away!
+ //and this of course should never been thrown :)
+ throw new Exception('LDAP backend: unexpected collision of DN and ownCloud Name.');
+ }
+
+ /**
+ * @brief gives back the user names as they are used ownClod internally
+ * @param $ldapGroups an array with the ldap Users result in style of array ( array ('dn' => foo, 'uid' => bar), ... )
+ * @returns an array with the user names to use in ownCloud
+ *
+ * gives back the user names as they are used ownClod internally
+ */
+ public function ownCloudUserNames($ldapUsers) {
+ return $this->ldap2ownCloudNames($ldapUsers, true);
+ }
+
+ /**
+ * @brief gives back the group names as they are used ownClod internally
+ * @param $ldapGroups an array with the ldap Groups result in style of array ( array ('dn' => foo, 'cn' => bar), ... )
+ * @returns an array with the group names to use in ownCloud
+ *
+ * gives back the group names as they are used ownClod internally
+ */
+ public function ownCloudGroupNames($ldapGroups) {
+ return $this->ldap2ownCloudNames($ldapGroups, false);
+ }
+
+ private function ldap2ownCloudNames($ldapObjects, $isUsers) {
+ if($isUsers) {
+ $knownObjects = $this->mappedUsers();
+ $nameAttribute = $this->connection->ldapUserDisplayName;
+ } else {
+ $knownObjects = $this->mappedGroups();
+ $nameAttribute = $this->connection->ldapGroupDisplayName;
+ }
+ $ownCloudNames = array();
+
+ foreach($ldapObjects as $ldapObject) {
+ $key = \OCP\Util::recursiveArraySearch($knownObjects, $ldapObject['dn']);
+
+ //everything is fine when we know the group
+ if($key !== false) {
+ $ownCloudNames[] = $knownObjects[$key]['owncloud_name'];
+ continue;
+ }
+
+ //a new group! Then let's try to add it. We're shooting into the blue with the group name, assuming that in most cases there will not be a conflict. But first make sure, that the display name contains only allowed characters.
+ $ocname = $this->sanitizeUsername($ldapObject[$nameAttribute]);
+ if($this->mapComponent($ldapObject['dn'], $ocname, $isUsers)) {
+ $ownCloudNames[] = $ocname;
+ continue;
+ }
+
+ //doh! There is a conflict. We need to distinguish between groups. Adding indexes is an idea, but not much of a help for the user. The DN is ugly, but for now the only reasonable way. But we transform it to a readable format and remove the first part to only give the path where this entry is located.
+ $ocname = $this->alternateOwnCloudName($ocname, $ldapObject['dn']);
+ if($this->mapComponent($ldapObject['dn'], $ocname, $isUsers)) {
+ $ownCloudNames[] = $ocname;
+ continue;
+ }
+
+ //TODO: do not simple die away
+ //and this of course should never been thrown :)
+ throw new Exception('LDAP backend: unexpected collision of DN and ownCloud Name.');
+ }
+ return $ownCloudNames;
+ }
+
+ /**
+ * @brief creates a hopefully unique name for owncloud based on the display name and the dn of the LDAP object
+ * @param $name the display name of the object
+ * @param $dn the dn of the object
+ * @returns string with with the name to use in ownCloud
+ *
+ * creates a hopefully unique name for owncloud based on the display name and the dn of the LDAP object
+ */
+ private function alternateOwnCloudName($name, $dn) {
+ $ufn = ldap_dn2ufn($dn);
+ $name = $name . '@' . trim(\OCP\Util::mb_substr_replace($ufn, '', 0, mb_strpos($ufn, ',', 0, 'UTF-8'), 'UTF-8'));
+ $name = $this->sanitizeUsername($name);
+ return $name;
+ }
+
+ /**
+ * @brief retrieves all known groups from the mappings table
+ * @returns array with the results
+ *
+ * retrieves all known groups from the mappings table
+ */
+ private function mappedGroups() {
+ return $this->mappedComponents(false);
+ }
+
+ /**
+ * @brief retrieves all known users from the mappings table
+ * @returns array with the results
+ *
+ * retrieves all known users from the mappings table
+ */
+ private function mappedUsers() {
+ return $this->mappedComponents(true);
+ }
+
+ private function mappedComponents($isUsers) {
+ $table = $this->getMapTable($isUsers);
+
+ $query = \OCP\DB::prepare('
+ SELECT ldap_dn, owncloud_name
+ FROM '. $table
+ );
+
+ return $query->execute()->fetchAll();
+ }
+
+ /**
+ * @brief inserts a new user or group into the mappings table
+ * @param $dn the record in question
+ * @param $ocname the name to use in ownCloud
+ * @param $isUser is it a user or a group?
+ * @returns true on success, false otherwise
+ *
+ * inserts a new user or group into the mappings table
+ */
+ private function mapComponent($dn, $ocname, $isUser = true) {
+ $table = $this->getMapTable($isUser);
+ $dn = $this->sanitizeDN($dn);
+
+ $sqlAdjustment = '';
+ $dbtype = \OCP\Config::getSystemValue('dbtype');
+ if($dbtype == 'mysql') {
+ $sqlAdjustment = 'FROM dual';
+ }
+
+ $insert = \OCP\DB::prepare('
+ INSERT INTO '.$table.' (ldap_dn, owncloud_name)
+ SELECT ?,?
+ '.$sqlAdjustment.'
+ WHERE NOT EXISTS (
+ SELECT 1
+ FROM '.$table.'
+ WHERE ldap_dn = ?
+ OR owncloud_name = ? )
+ ');
+
+ $res = $insert->execute(array($dn, $ocname, $dn, $ocname));
+
+ if(\OCP\DB::isError($res)) {
+ return false;
+ }
+
+ $insRows = $res->numRows();
+
+ if($insRows == 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public function fetchListOfUsers($filter, $attr) {
+ return $this->fetchList($this->searchUsers($filter, $attr), (count($attr) > 1));
+ }
+
+ public function fetchListOfGroups($filter, $attr) {
+ return $this->fetchList($this->searchGroups($filter, $attr), (count($attr) > 1));
+ }
+
+ private function fetchList($list, $manyAttributes) {
+ if(is_array($list)) {
+ if($manyAttributes) {
+ return $list;
+ } else {
+ return array_unique($list, SORT_LOCALE_STRING);
+ }
+ }
+
+ //error cause actually, maybe throw an exception in future.
+ return array();
+ }
+
+ /**
+ * @brief executes an LDAP search, optimized for Users
+ * @param $filter the LDAP filter for the search
+ * @param $attr optional, when a certain attribute shall be filtered out
+ * @returns array with the search result
+ *
+ * Executes an LDAP search
+ */
+ public function searchUsers($filter, $attr = null) {
+ return $this->search($filter, $this->connection->ldapBaseUsers, $attr);
+ }
+
+ /**
+ * @brief executes an LDAP search, optimized for Groups
+ * @param $filter the LDAP filter for the search
+ * @param $attr optional, when a certain attribute shall be filtered out
+ * @returns array with the search result
+ *
+ * Executes an LDAP search
+ */
+ public function searchGroups($filter, $attr = null) {
+ return $this->search($filter, $this->connection->ldapBaseGroups, $attr);
+ }
+
+ /**
+ * @brief executes an LDAP search
+ * @param $filter the LDAP filter for the search
+ * @param $base the LDAP subtree that shall be searched
+ * @param $attr optional, when a certain attribute shall be filtered out
+ * @returns array with the search result
+ *
+ * Executes an LDAP search
+ */
+ private function search($filter, $base, $attr = null) {
+ if(!is_null($attr) && !is_array($attr)) {
+ $attr = array(mb_strtolower($attr, 'UTF-8'));
+ }
+
+ // See if we have a resource
+ $link_resource = $this->connection->getConnectionResource();
+ if(is_resource($link_resource)) {
+ $sr = ldap_search($link_resource, $base, $filter, $attr);
+ $findings = ldap_get_entries($link_resource, $sr );
+
+ // if we're here, probably no connection resource is returned.
+ // to make ownCloud behave nicely, we simply give back an empty array.
+ if(is_null($findings)) {
+ return array();
+ }
+ } else {
+ // Seems like we didn't find any resource.
+ // Return an empty array just like before.
+ \OCP\Util::writeLog('user_ldap', 'Could not search, because resource is missing.', \OCP\Util::DEBUG);
+ return array();
+ }
+
+ if(!is_null($attr)) {
+ $selection = array();
+ $multiarray = false;
+ if(count($attr) > 1) {
+ $multiarray = true;
+ $i = 0;
+ }
+ foreach($findings as $item) {
+ if(!is_array($item)) {
+ continue;
+ }
+ $item = \OCP\Util::mb_array_change_key_case($item, MB_CASE_LOWER, 'UTF-8');
+
+ if($multiarray) {
+ foreach($attr as $key) {
+ $key = mb_strtolower($key, 'UTF-8');
+ if(isset($item[$key])) {
+ if($key != 'dn') {
+ $selection[$i][$key] = $this->resemblesDN($key) ? $this->sanitizeDN($item[$key][0]) : $item[$key][0];
+ } else {
+ $selection[$i][$key] = $this->sanitizeDN($item[$key]);
+ }
+ }
+
+ }
+ $i++;
+ } else {
+ //tribute to case insensitivity
+ $key = mb_strtolower($attr[0], 'UTF-8');
+
+ if(isset($item[$key])) {
+ if($this->resemblesDN($key)) {
+ $selection[] = $this->sanitizeDN($item[$key]);
+ } else {
+ $selection[] = $item[$key];
+ }
+ }
+ }
+ }
+ return $selection;
+ }
+ return $findings;
+ }
+
+ public function sanitizeUsername($name) {
+ if($this->connection->ldapIgnoreNamingRules) {
+ return $name;
+ }
+
+ //REPLACEMENTS
+ $name = \OCP\Util::mb_str_replace(' ', '_', $name, 'UTF-8');
+
+ //every remaining unallowed characters will be removed
+ $name = preg_replace('/[^a-zA-Z0-9_.@-]/u', '', $name);
+
+ return $name;
+ }
+
+ /**
+ * @brief combines the input filters with AND
+ * @param $filters array, the filters to connect
+ * @returns the combined filter
+ *
+ * Combines Filter arguments with AND
+ */
+ public function combineFilterWithAnd($filters) {
+ return $this->combineFilter($filters, '&');
+ }
+
+ /**
+ * @brief combines the input filters with AND
+ * @param $filters array, the filters to connect
+ * @returns the combined filter
+ *
+ * Combines Filter arguments with AND
+ */
+ public function combineFilterWithOr($filters) {
+ return $this->combineFilter($filters, '|');
+ }
+
+ /**
+ * @brief combines the input filters with given operator
+ * @param $filters array, the filters to connect
+ * @param $operator either & or |
+ * @returns the combined filter
+ *
+ * Combines Filter arguments with AND
+ */
+ private function combineFilter($filters, $operator) {
+ $combinedFilter = '('.$operator;
+ foreach($filters as $filter) {
+ if($filter[0] != '(') {
+ $filter = '('.$filter.')';
+ }
+ $combinedFilter.=$filter;
+ }
+ $combinedFilter.=')';
+ return $combinedFilter;
+ }
+
+ public function areCredentialsValid($name, $password) {
+ $testConnection = clone $this->connection;
+ $credentials = array(
+ 'ldapAgentName' => $name,
+ 'ldapAgentPassword' => $password
+ );
+ if(!$testConnection->setConfiguration($credentials)) {
+ return false;
+ }
+ return $testConnection->bind();
+ }
+} \ No newline at end of file
diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php
new file mode 100644
index 00000000000..e54a8e2b241
--- /dev/null
+++ b/apps/user_ldap/lib/connection.php
@@ -0,0 +1,255 @@
+<?php
+
+/**
+ * ownCloud – LDAP Access
+ *
+ * @author Arthur Schiwon
+ * @copyright 2012 Arthur Schiwon blizzz@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/>.
+ *
+ */
+
+namespace OCA\user_ldap\lib;
+
+class Connection {
+ private $ldapConnectionRes = null;
+ private $configID;
+ private $configured = false;
+
+ //cached settings
+ protected $config = array(
+ 'ldapHost' => null,
+ 'ldapPort' => null,
+ 'ldapBase' => null,
+ 'ldapBaseUsers' => null,
+ 'ldapBaseGroups' => null,
+ 'ldapAgentName' => null,
+ 'ldapAgentPassword' => null,
+ 'ldapTLS' => null,
+ 'ldapNoCase' => null,
+ 'ldapIgnoreNamingRules' => null,
+ 'ldapUserDisplayName' => null,
+ 'ldapUserFilter' => null,
+ 'ldapGroupFilter' => null,
+ 'ldapGroupDisplayName' => null,
+ 'ldapLoginFilter' => null,
+ 'ldapQuotaAttribute' => null,
+ 'ldapQuotaDefault' => null,
+ 'ldapEmailAttribute' => null,
+ );
+
+ public function __construct($configID = 'user_ldap') {
+ $this->configID = $configID;
+ }
+
+ public function __destruct() {
+ @ldap_unbind($this->ldapConnectionRes);
+ }
+
+ public function __get($name) {
+ if(!$this->configured) {
+ $this->readConfiguration();
+ }
+
+ if(isset($this->config[$name])) {
+ return $this->config[$name];
+ }
+ }
+
+ /**
+ * @brief initializes the LDAP backend
+ * @param $force read the config settings no matter what
+ *
+ * initializes the LDAP backend
+ */
+ public function init($force = false) {
+ $this->readConfiguration($force);
+ $this->establishConnection();
+ }
+
+ /**
+ * Returns the LDAP handler
+ */
+ public function getConnectionResource() {
+ if(!$this->ldapConnectionRes) {
+ $this->init();
+ }
+ if(is_null($this->ldapConnectionRes)) {
+ \OCP\Util::writeLog('user_ldap', 'Connection could not be established', \OCP\Util::ERROR);
+ }
+ return $this->ldapConnectionRes;
+ }
+
+ /**
+ * Caches the general LDAP configuration.
+ */
+ private function readConfiguration($force = false) {
+ \OCP\Util::writeLog('user_ldap','Checking conf state: isConfigured? '.print_r($this->configured, true).' isForce? '.print_r($force, true).' configID? '.print_r($this->configID, true), \OCP\Util::DEBUG);
+ if((!$this->configured || $force) && !is_null($this->configID)) {
+ \OCP\Util::writeLog('user_ldap','Reading the configuration', \OCP\Util::DEBUG);
+ $this->config['ldapHost'] = \OCP\Config::getAppValue($this->configID, 'ldap_host', '');
+ $this->config['ldapPort'] = \OCP\Config::getAppValue($this->configID, 'ldap_port', 389);
+ $this->config['ldapAgentName'] = \OCP\Config::getAppValue($this->configID, 'ldap_dn','');
+ $this->config['ldapAgentPassword'] = base64_decode(\OCP\Config::getAppValue($this->configID, 'ldap_agent_password',''));
+ $this->config['ldapBase'] = \OCP\Config::getAppValue($this->configID, 'ldap_base', '');
+ $this->config['ldapBaseUsers'] = \OCP\Config::getAppValue($this->configID, 'ldap_base_users',$this->config['ldapBase']);
+ $this->config['ldapBaseGroups'] = \OCP\Config::getAppValue($this->configID, 'ldap_base_groups', $this->config['ldapBase']);
+ $this->config['ldapTLS'] = \OCP\Config::getAppValue($this->configID, 'ldap_tls',0);
+ $this->config['ldapNoCase'] = \OCP\Config::getAppValue($this->configID, 'ldap_nocase', 0);
+ $this->config['ldapUserDisplayName'] = mb_strtolower(\OCP\Config::getAppValue($this->configID, 'ldap_display_name', 'uid'), 'UTF-8');
+ $this->config['ldapUserFilter'] = \OCP\Config::getAppValue($this->configID, 'ldap_userlist_filter','objectClass=person');
+ $this->config['ldapGroupFilter'] = \OCP\Config::getAppValue($this->configID, 'ldap_group_filter','(objectClass=posixGroup)');
+ $this->config['ldapLoginFilter'] = \OCP\Config::getAppValue($this->configID, 'ldap_login_filter', '(uid=%uid)');
+ $this->config['ldapGroupDisplayName'] = mb_strtolower(\OCP\Config::getAppValue($this->configID, 'ldap_group_display_name', 'uid'), 'UTF-8');
+ $this->config['ldapQuotaAttribute'] = \OCP\Config::getAppValue($this->configID, 'ldap_quota_attr', '');
+ $this->config['ldapQuotaDefault'] = \OCP\Config::getAppValue($this->configID, 'ldap_quota_def', '');
+ $this->config['ldapEmailAttribute'] = \OCP\Config::getAppValue($this->configID, 'ldap_email_attr', '');
+ $this->config['ldapGroupMemberAssocAttr'] = \OCP\Config::getAppValue($this->configID, 'ldap_group_member_assoc_attribute', 'uniqueMember');
+ $this->config['ldapIgnoreNamingRules'] = \OCP\Config::getSystemValue('ldapIgnoreNamingRules', false);
+
+ $this->configured = $this->validateConfiguration();
+ }
+ }
+
+ /**
+ * @brief set LDAP configuration with values delivered by an array, not read from configuration
+ * @param $config array that holds the config parameters in an associated array
+ * @param &$setParameters optional; array where the set fields will be given to
+ * @return true if config validates, false otherwise. Check with $setParameters for detailed success on single parameters
+ */
+ public function setConfiguration($config, &$setParameters = null) {
+ if(!is_array($config)) {
+ return false;
+ }
+
+ foreach($config as $parameter => $value) {
+ if(isset($this->config[$parameter])) {
+ $this->config[$parameter] = $value;
+ if(is_array($setParameters)) {
+ $setParameters[] = $parameter;
+ }
+ }
+ }
+
+ $this->configured = $this->validateConfiguration();
+
+ return $this->configured;
+ }
+
+ /**
+ * @brief Validates the user specified configuration
+ * @returns true if configuration seems OK, false otherwise
+ */
+ private function validateConfiguration() {
+ //first step: "soft" checks: settings that are not really necessary, but advisable. If left empty, give an info message
+ if(empty($this->config['ldapBaseUsers'])) {
+ \OCP\Util::writeLog('user_ldap', 'Base tree for Users is empty, using Base DN', \OCP\Util::INFO);
+ $this->config['ldapBaseUsers'] = $this->config['ldapBase'];
+ }
+ if(empty($this->config['ldapBaseGroups'])) {
+ \OCP\Util::writeLog('user_ldap', 'Base tree for Groups is empty, using Base DN', \OCP\Util::INFO);
+ $this->config['ldapBaseGroups'] = $this->config['ldapBase'];
+ }
+ if(empty($this->config['ldapGroupFilter']) && empty($this->config['ldapGroupMemberAssocAttr'])) {
+ \OCP\Util::writeLog('user_ldap', 'No group filter is specified, LDAP group feature will not be used.', \OCP\Util::INFO);
+ }
+
+ //second step: critical checks. If left empty or filled wrong, set as unconfigured and give a warning.
+ $configurationOK = true;
+ if(empty($this->config['ldapHost'])) {
+ \OCP\Util::writeLog('user_ldap', 'No LDAP host given, won`t connect.', \OCP\Util::WARN);
+ $configurationOK = false;
+ }
+ if(empty($this->config['ldapPort'])) {
+ \OCP\Util::writeLog('user_ldap', 'No LDAP Port given, won`t connect.', \OCP\Util::WARN);
+ $configurationOK = false;
+ }
+ if((empty($this->config['ldapAgentName']) && !empty($this->config['ldapAgentPassword']))
+ || (!empty($this->config['ldapAgentName']) && empty($this->config['ldapAgentPassword']))) {
+ \OCP\Util::writeLog('user_ldap', 'Either no password given for the user agent or a password is given, but no LDAP agent; won`t connect.', \OCP\Util::WARN);
+ $configurationOK = false;
+ }
+ //TODO: check if ldapAgentName is in DN form
+ if(empty($this->config['ldapBase']) && (empty($this->config['ldapBaseUsers']) && empty($this->config['ldapBaseGroups']))) {
+ \OCP\Util::writeLog('user_ldap', 'No Base DN given, won`t connect.', \OCP\Util::WARN);
+ $configurationOK = false;
+ }
+ if(empty($this->config['ldapUserDisplayName'])) {
+ \OCP\Util::writeLog('user_ldap', 'No user display name attribute specified, won`t connect.', \OCP\Util::WARN);
+ $configurationOK = false;
+ }
+ if(empty($this->config['ldapGroupDisplayName'])) {
+ \OCP\Util::writeLog('user_ldap', 'No group display name attribute specified, won`t connect.', \OCP\Util::WARN);
+ $configurationOK = false;
+ }
+ if(empty($this->config['ldapLoginFilter'])) {
+ \OCP\Util::writeLog('user_ldap', 'No login filter specified, won`t connect.', \OCP\Util::WARN);
+ $configurationOK = false;
+ }
+ if(mb_strpos($this->config['ldapLoginFilter'], '%uid', 0, 'UTF-8') === false) {
+ \OCP\Util::writeLog('user_ldap', 'Login filter does not contain %uid place holder, won`t connect.', \OCP\Util::WARN);
+ \OCP\Util::writeLog('user_ldap', 'Login filter was ' . $this->config['ldapLoginFilter'], \OCP\Util::DEBUG);
+ $configurationOK = false;
+ }
+
+ return $configurationOK;
+ }
+
+ /**
+ * Connects and Binds to LDAP
+ */
+ private function establishConnection() {
+ static $phpLDAPinstalled = true;
+ if(!$phpLDAPinstalled) {
+ return false;
+ }
+ if(!$this->configured) {
+ \OCP\Util::writeLog('user_ldap', 'Configuration is invalid, cannot connect', \OCP\Util::WARN);
+ return false;
+ }
+ if(!$this->ldapConnectionRes) {
+ if(!function_exists('ldap_connect')) {
+ $phpLDAPinstalled = false;
+ \OCP\Util::writeLog('user_ldap', 'function ldap_connect is not available. Make sure that the PHP ldap module is installed.', \OCP\Util::ERROR);
+
+ return false;
+ }
+ $this->ldapConnectionRes = ldap_connect($this->config['ldapHost'], $this->config['ldapPort']);
+ if(ldap_set_option($this->ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
+ if(ldap_set_option($this->ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
+ if($this->config['ldapTLS']) {
+ ldap_start_tls($this->ldapConnectionRes);
+ }
+ }
+ }
+
+ return $this->bind();
+ }
+ }
+
+ /**
+ * Binds to LDAP
+ */
+ public function bind() {
+ $ldapLogin = @ldap_bind($this->getConnectionResource(), $this->config['ldapAgentName'], $this->config['ldapAgentPassword']);
+ if(!$ldapLogin) {
+ \OCP\Util::writeLog('user_ldap', 'Bind failed: ' . ldap_errno($this->ldapConnectionRes) . ': ' . ldap_error($this->ldapConnectionRes), \OCP\Util::ERROR);
+ $this->ldapConnectionRes = null;
+ return false;
+ }
+ return true;
+ }
+
+} \ No newline at end of file
diff --git a/apps/user_ldap/lib_ldap.php b/apps/user_ldap/lib_ldap.php
deleted file mode 100644
index 08b09304d78..00000000000
--- a/apps/user_ldap/lib_ldap.php
+++ /dev/null
@@ -1,721 +0,0 @@
-<?php
-
-/**
- * ownCloud – LDAP lib
- *
- * @author Arthur Schiwon
- * @copyright 2012 Arthur Schiwon blizzz@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/>.
- *
- */
-
-define('LDAP_GROUP_MEMBER_ASSOC_ATTR','uniqueMember');
-define('LDAP_GROUP_DISPLAY_NAME_ATTR','cn');
-
-//needed to unbind, because we use OC_LDAP only statically
-class OC_LDAP_DESTRUCTOR {
- public function __destruct() {
- OC_LDAP::destruct();
- }
-}
-
-class OC_LDAP {
- static protected $ldapConnectionRes = false;
- static protected $configured = false;
-
- //cached settings
- static protected $ldapHost;
- static protected $ldapPort;
- static protected $ldapBase;
- static protected $ldapBaseUsers;
- static protected $ldapBaseGroups;
- static protected $ldapAgentName;
- static protected $ldapAgentPassword;
- static protected $ldapTLS;
- static protected $ldapNoCase;
- static protected $ldapIgnoreNamingRules;
- // user and group settings, that are needed in both backends
- static protected $ldapUserDisplayName;
- static protected $ldapUserFilter;
- static protected $ldapGroupDisplayName;
- static protected $ldapLoginFilter;
-
- static protected $__d;
-
- /**
- * @brief initializes the LDAP backend
- * @param $force read the config settings no matter what
- *
- * initializes the LDAP backend
- */
- static public function init($force = false) {
- if(is_null(self::$__d)) {
- self::$__d = new OC_LDAP_DESTRUCTOR();
- }
- self::readConfiguration($force);
- self::establishConnection();
- }
-
- static public function destruct() {
- @ldap_unbind(self::$ldapConnectionRes);
- }
-
- /**
- * @brief returns a read-only configuration value
- * @param $key the name of the configuration value
- * @returns the value on success, otherwise null
- *
- * returns a read-only configuration values
- *
- * we cannot work with getters, because it is a static class
- */
- static public function conf($key) {
- if(!self::$configured) {
- self::init();
- }
-
- $availableProperties = array(
- 'ldapUserDisplayName',
- 'ldapGroupDisplayName',
- 'ldapLoginFilter'
- );
-
- if(in_array($key, $availableProperties)) {
- return self::$$key;
- }
-
- return null;
- }
-
- /**
- * gives back the database table for the query
- */
- static private function getMapTable($isUser) {
- if($isUser) {
- return '*PREFIX*ldap_user_mapping';
- } else {
- return '*PREFIX*ldap_group_mapping';
- }
- }
-
- /**
- * @brief returns the LDAP DN for the given internal ownCloud name of the group
- * @param $name the ownCloud name in question
- * @returns string with the LDAP DN on success, otherwise false
- *
- * returns the LDAP DN for the given internal ownCloud name of the group
- */
- static public function groupname2dn($name) {
- return self::ocname2dn($name, false);
- }
-
- /**
- * @brief returns the LDAP DN for the given internal ownCloud name of the user
- * @param $name the ownCloud name in question
- * @returns string with the LDAP DN on success, otherwise false
- *
- * returns the LDAP DN for the given internal ownCloud name of the user
- */
- static public function username2dn($name) {
- $dn = self::ocname2dn($name, true);
- if($dn) {
- return $dn;
- } else {
- //fallback: user is not mapped
- self::init();
- $filter = self::combineFilterWithAnd(array(
- self::$ldapUserFilter,
- self::$ldapUserDisplayName . '=' . $name,
- ));
- $result = self::searchUsers($filter, 'dn');
- if(isset($result[0]['dn'])) {
- self::mapUser($result[0], $name);
- return $result[0];
- }
- }
-
- return false;
- }
-
- static private function ocname2dn($name, $isUser) {
- $table = self::getMapTable($isUser);
-
- $query = OCP\DB::prepare('
- SELECT ldap_dn
- FROM '.$table.'
- WHERE owncloud_name = ?
- ');
-
- $record = $query->execute(array($name))->fetchOne();
- return $record;
- }
-
- /**
- * @brief returns the internal ownCloud name for the given LDAP DN of the group
- * @param $dn the dn of the group object
- * @param $ldapname optional, the display name of the object
- * @returns string with with the name to use in ownCloud, false on DN outside of search DN
- *
- * returns the internal ownCloud name for the given LDAP DN of the group
- */
- static public function dn2groupname($dn, $ldapname = null) {
- if(mb_strripos($dn, self::$ldapBaseGroups, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen(self::$ldapBaseGroups, 'UTF-8'))) {
- return false;
- }
- return self::dn2ocname($dn, $ldapname, false);
- }
-
- /**
- * @brief returns the internal ownCloud name for the given LDAP DN of the user
- * @param $dn the dn of the user object
- * @param $ldapname optional, the display name of the object
- * @returns string with with the name to use in ownCloud
- *
- * returns the internal ownCloud name for the given LDAP DN of the user, false on DN outside of search DN
- */
- static public function dn2username($dn, $ldapname = null) {
- if(mb_strripos($dn, self::$ldapBaseUsers, 0, 'UTF-8') !== (mb_strlen($dn, 'UTF-8')-mb_strlen(self::$ldapBaseUsers, 'UTF-8'))) {
- return false;
- }
- return self::dn2ocname($dn, $ldapname, true);
- }
-
- static public function dn2ocname($dn, $ldapname = null, $isUser = true) {
- $dn = self::sanitizeDN($dn);
- $table = self::getMapTable($isUser);
- if($isUser) {
- $nameAttribute = self::conf('ldapUserDisplayName');
- } else {
- $nameAttribute = self::conf('ldapGroupDisplayName');
- }
-
- $query = OCP\DB::prepare('
- SELECT owncloud_name
- FROM '.$table.'
- WHERE ldap_dn = ?
- ');
-
- $component = $query->execute(array($dn))->fetchOne();
- if($component) {
- return $component;
- }
-
- if(is_null($ldapname)) {
- $ldapname = self::readAttribute($dn, $nameAttribute);
- $ldapname = $ldapname[0];
- }
- $ldapname = self::sanitizeUsername($ldapname);
-
- //a new user/group! Then let's try to add it. We're shooting into the blue with the user/group name, assuming that in most cases there will not be a conflict. Otherwise an error will occur and we will continue with our second shot.
- if(self::mapComponent($dn, $ldapname, $isUser)) {
- return $ldapname;
- }
-
- //doh! There is a conflict. We need to distinguish between users/groups. Adding indexes is an idea, but not much of a help for the user. The DN is ugly, but for now the only reasonable way. But we transform it to a readable format and remove the first part to only give the path where this object is located.
- $oc_name = self::alternateOwnCloudName($ldapname, $dn);
- if(self::mapComponent($dn, $oc_name, $isUser)) {
- return $oc_name;
- }
-
- //and this of course should never been thrown :)
- throw new Exception('LDAP backend: unexpected collision of DN and ownCloud Name.');
- }
-
- /**
- * @brief gives back the user names as they are used ownClod internally
- * @param $ldapGroups an array with the ldap Users result in style of array ( array ('dn' => foo, 'uid' => bar), ... )
- * @returns an array with the user names to use in ownCloud
- *
- * gives back the user names as they are used ownClod internally
- */
- static public function ownCloudUserNames($ldapUsers) {
- return self::ldap2ownCloudNames($ldapUsers, true);
- }
-
- /**
- * @brief gives back the group names as they are used ownClod internally
- * @param $ldapGroups an array with the ldap Groups result in style of array ( array ('dn' => foo, 'cn' => bar), ... )
- * @returns an array with the group names to use in ownCloud
- *
- * gives back the group names as they are used ownClod internally
- */
- static public function ownCloudGroupNames($ldapGroups) {
- return self::ldap2ownCloudNames($ldapGroups, false);
- }
-
- static private function ldap2ownCloudNames($ldapObjects, $isUsers) {
- if($isUsers) {
- $knownObjects = self::mappedUsers();
- $nameAttribute = self::conf('ldapUserDisplayName');
- } else {
- $knownObjects = self::mappedGroups();
- $nameAttribute = self::conf('ldapGroupDisplayName');
- }
- $ownCloudNames = array();
-
- foreach($ldapObjects as $ldapObject) {
- $key = self::recursiveArraySearch($knownObjects, $ldapObject['dn']);
-
- //everything is fine when we know the group
- if($key !== false) {
- $ownCloudNames[] = $knownObjects[$key]['owncloud_name'];
- continue;
- }
-
- //a new group! Then let's try to add it. We're shooting into the blue with the group name, assuming that in most cases there will not be a conflict. But first make sure, that the display name contains only allowed characters.
- $ocname = self::sanitizeUsername($ldapObject[$nameAttribute]);
- if(self::mapComponent($ldapObject['dn'], $ocname, $isUsers)) {
- $ownCloudNames[] = $ocname;
- continue;
- }
-
- //doh! There is a conflict. We need to distinguish between groups. Adding indexes is an idea, but not much of a help for the user. The DN is ugly, but for now the only reasonable way. But we transform it to a readable format and remove the first part to only give the path where this entry is located.
- $ocname = self::alternateOwnCloudName($ocname, $ldapObject['dn']);
- if(self::mapComponent($ldapObject['dn'], $ocname, $isUsers)) {
- $ownCloudNames[] = $ocname;
- continue;
- }
-
- //and this of course should never been thrown :)
- throw new Exception('LDAP backend: unexpected collision of DN and ownCloud Name.');
- }
- return $ownCloudNames;
- }
-
- /**
- * @brief creates a hopefully unique name for owncloud based on the display name and the dn of the LDAP object
- * @param $name the display name of the object
- * @param $dn the dn of the object
- * @returns string with with the name to use in ownCloud
- *
- * creates a hopefully unique name for owncloud based on the display name and the dn of the LDAP object
- */
- static private function alternateOwnCloudName($name, $dn) {
- $ufn = ldap_dn2ufn($dn);
- $name = $name . '@' . trim(OCP\Util::mb_substr_replace($ufn, '', 0, mb_strpos($ufn, ',', 0, 'UTF-8'), 'UTF-8'));
- $name = self::sanitizeUsername($name);
- return $name;
- }
-
- /**
- * @brief retrieves all known groups from the mappings table
- * @returns array with the results
- *
- * retrieves all known groups from the mappings table
- */
- static private function mappedGroups() {
- return self::mappedComponents(false);
- }
-
- /**
- * @brief retrieves all known users from the mappings table
- * @returns array with the results
- *
- * retrieves all known users from the mappings table
- */
- static private function mappedUsers() {
- return self::mappedComponents(true);
- }
-
- static private function mappedComponents($isUsers) {
- $table = self::getMapTable($isUsers);
-
- $query = OCP\DB::prepare('
- SELECT ldap_dn, owncloud_name
- FROM '. $table
- );
-
- return $query->execute()->fetchAll();
- }
-
- /**
- * @brief inserts a new user or group into the mappings table
- * @param $dn the record in question
- * @param $ocname the name to use in ownCloud
- * @param $isUser is it a user or a group?
- * @returns true on success, false otherwise
- *
- * inserts a new user or group into the mappings table
- */
- static private function mapComponent($dn, $ocname, $isUser = true) {
- $table = self::getMapTable($isUser);
- $dn = self::sanitizeDN($dn);
-
- $sqlAdjustment = '';
- $dbtype = OCP\Config::getSystemValue('dbtype');
- if($dbtype == 'mysql') {
- $sqlAdjustment = 'FROM dual';
- }
-
- $insert = OCP\DB::prepare('
- INSERT INTO '.$table.' (ldap_dn, owncloud_name)
- SELECT ?,?
- '.$sqlAdjustment.'
- WHERE NOT EXISTS (
- SELECT 1
- FROM '.$table.'
- WHERE ldap_dn = ?
- OR owncloud_name = ? )
- ');
-
- $res = $insert->execute(array($dn, $ocname, $dn, $ocname));
-
- if(OCP\DB::isError($res)) {
- return false;
- }
-
- $insRows = $res->numRows();
-
- if($insRows == 0) {
- return false;
- }
-
- return true;
- }
-
- static public function fetchListOfUsers($filter, $attr) {
- return self::fetchList(OC_LDAP::searchUsers($filter, $attr), (count($attr) > 1));
- }
-
- static public function fetchListOfGroups($filter, $attr) {
- return self::fetchList(OC_LDAP::searchGroups($filter, $attr), (count($attr) > 1));
- }
-
- static private function fetchList($list, $manyAttributes) {
- if(is_array($list)) {
- if($manyAttributes) {
- return $list;
- } else {
- return array_unique($list, SORT_LOCALE_STRING);
- }
- }
-
- //error cause actually, maybe throw an exception in future.
- return array();
- }
-
- /**
- * @brief reads a given attribute for an LDAP record identified by a DN
- * @param $dn the record in question
- * @param $attr the attribute that shall be retrieved
- * @returns the values in an array on success, false otherwise
- *
- * Reads an attribute from an LDAP entry
- */
- static public function readAttribute($dn, $attr) {
- $cr = self::getConnectionResource();
- $rr = ldap_read($cr, $dn, 'objectClass=*', array($attr));
- $er = ldap_first_entry($cr, $rr);
- //LDAP attributes are not case sensitive
- $result = OCP\Util::mb_array_change_key_case(ldap_get_attributes($cr, $er), MB_CASE_LOWER, 'UTF-8');
- $attr = mb_strtolower($attr, 'UTF-8');
-
- if(isset($result[$attr]) && $result[$attr]['count'] > 0){
- $values = array();
- for($i=0;$i<$result[$attr]['count'];$i++) {
- $values[] = self::resemblesDN($attr) ? self::sanitizeDN($result[$attr][$i]) : $result[$attr][$i];
- }
- return $values;
- }
- return false;
- }
-
- /**
- * @brief executes an LDAP search, optimized for Users
- * @param $filter the LDAP filter for the search
- * @param $attr optional, when a certain attribute shall be filtered out
- * @returns array with the search result
- *
- * Executes an LDAP search
- */
- static public function searchUsers($filter, $attr = null) {
- self::init();
- return self::search($filter, self::$ldapBaseUsers, $attr);
- }
-
- /**
- * @brief executes an LDAP search, optimized for Groups
- * @param $filter the LDAP filter for the search
- * @param $attr optional, when a certain attribute shall be filtered out
- * @returns array with the search result
- *
- * Executes an LDAP search
- */
- static public function searchGroups($filter, $attr = null) {
- self::init();
- return self::search($filter, self::$ldapBaseGroups, $attr);
- }
-
- /**
- * @brief executes an LDAP search
- * @param $filter the LDAP filter for the search
- * @param $base the LDAP subtree that shall be searched
- * @param $attr optional, when a certain attribute shall be filtered out
- * @returns array with the search result
- *
- * Executes an LDAP search
- */
- static private function search($filter, $base, $attr = null) {
- if(!is_null($attr) && !is_array($attr)) {
- $attr = array(mb_strtolower($attr, 'UTF-8'));
- }
-
- // See if we have a resource
- $link_resource = self::getConnectionResource();
- if(is_resource($link_resource)) {
- $sr = ldap_search($link_resource, $base, $filter, $attr);
- $findings = ldap_get_entries($link_resource, $sr );
-
- // if we're here, probably no connection resource is returned.
- // to make ownCloud behave nicely, we simply give back an empty array.
- if(is_null($findings)) {
- return array();
- }
- } else {
- // Seems like we didn't find any resource.
- // Return an empty array just like before.
- return array();
- }
-
- if(!is_null($attr)) {
- $selection = array();
- $multiarray = false;
- if(count($attr) > 1) {
- $multiarray = true;
- $i = 0;
- }
- foreach($findings as $item) {
- if(!is_array($item)) {
- continue;
- }
- $item = OCP\Util::mb_array_change_key_case($item, MB_CASE_LOWER, 'UTF-8');
-
- if($multiarray) {
- foreach($attr as $key) {
- $key = mb_strtolower($key, 'UTF-8');
- if(isset($item[$key])) {
- if($key != 'dn'){
- $selection[$i][$key] = self::resemblesDN($key) ? self::sanitizeDN($item[$key][0]) : $item[$key][0];
- } else {
- $selection[$i][$key] = self::sanitizeDN($item[$key]);
- }
- }
-
- }
- $i++;
- } else {
- //tribute to case insensitivity
- $key = mb_strtolower($attr[0], 'UTF-8');
-
- if(isset($item[$key])) {
- if(self::resemblesDN($key)) {
- $selection[] = self::sanitizeDN($item[$key]);
- } else {
- $selection[] = $item[$key];
- }
- }
- }
-
- }
- return $selection;
- }
-
- return $findings;
- }
-
- static private function resemblesDN($attr) {
- $resemblingAttributes = array(
- 'dn',
- 'uniquemember',
- 'member'
- );
- return in_array($attr, $resemblingAttributes);
- }
-
- static private function sanitizeDN($dn) {
- //OID sometimes gives back DNs with whitespace after the comma a la "uid=foo, cn=bar, dn=..." We need to tackle this!
- $dn = preg_replace('/([^\\\]),(\s+)/u','\1,',$dn);
-
- //make comparisons and everything work
- $dn = mb_strtolower($dn, 'UTF-8');
-
- return $dn;
- }
-
- static private function sanitizeUsername($name) {
- if(self::$ldapIgnoreNamingRules) {
- return $name;
- }
-
- //REPLACEMENTS
- $name = OCP\Util::mb_str_replace(' ', '_', $name, 'UTF-8');
-
- //every remaining unallowed characters will be removed
- $name = preg_replace('/[^a-zA-Z0-9_.@-]/u', '', $name);
-
- return $name;
- }
-
- /**
- * @brief combines the input filters with AND
- * @param $filters array, the filters to connect
- * @returns the combined filter
- *
- * Combines Filter arguments with AND
- */
- static public function combineFilterWithAnd($filters) {
- return self::combineFilter($filters,'&');
- }
-
- /**
- * @brief combines the input filters with AND
- * @param $filters array, the filters to connect
- * @returns the combined filter
- *
- * Combines Filter arguments with AND
- */
- static public function combineFilterWithOr($filters) {
- return self::combineFilter($filters,'|');
- }
-
- /**
- * @brief combines the input filters with given operator
- * @param $filters array, the filters to connect
- * @param $operator either & or |
- * @returns the combined filter
- *
- * Combines Filter arguments with AND
- */
- static private function combineFilter($filters, $operator) {
- $combinedFilter = '('.$operator;
- foreach($filters as $filter) {
- if($filter[0] != '(') {
- $filter = '('.$filter.')';
- }
- $combinedFilter.=$filter;
- }
- $combinedFilter.=')';
- return $combinedFilter;
- }
-
- /**
- * Returns the LDAP handler
- */
- static private function getConnectionResource() {
- if(!self::$ldapConnectionRes) {
- self::init();
- }
- if(is_null(self::$ldapConnectionRes)) {
- OCP\Util::writeLog('ldap', 'Connection could not be established', OCP\Util::INFO);
- }
- return self::$ldapConnectionRes;
- }
-
- /**
- * Caches the general LDAP configuration.
- */
- static private function readConfiguration($force = false) {
- if(!self::$configured || $force) {
- self::$ldapHost = OCP\Config::getAppValue('user_ldap', 'ldap_host', '');
- self::$ldapPort = OCP\Config::getAppValue('user_ldap', 'ldap_port', 389);
- self::$ldapAgentName = OCP\Config::getAppValue('user_ldap', 'ldap_dn','');
- self::$ldapAgentPassword = base64_decode(OCP\Config::getAppValue('user_ldap', 'ldap_agent_password',''));
- self::$ldapBase = OCP\Config::getAppValue('user_ldap', 'ldap_base', '');
- self::$ldapBaseUsers = OCP\Config::getAppValue('user_ldap', 'ldap_base_users',self::$ldapBase);
- self::$ldapBaseGroups = OCP\Config::getAppValue('user_ldap', 'ldap_base_groups', self::$ldapBase);
- self::$ldapTLS = OCP\Config::getAppValue('user_ldap', 'ldap_tls',0);
- self::$ldapNoCase = OCP\Config::getAppValue('user_ldap', 'ldap_nocase', 0);
- self::$ldapUserDisplayName = mb_strtolower(OCP\Config::getAppValue('user_ldap', 'ldap_display_name', 'uid'), 'UTF-8');
- self::$ldapUserFilter = OCP\Config::getAppValue('user_ldap', 'ldap_userlist_filter','objectClass=person');
- self::$ldapLoginFilter = OCP\Config::getAppValue('user_ldap', 'ldap_login_filter', '(uid=%uid)');
- self::$ldapGroupDisplayName = mb_strtolower(OCP\Config::getAppValue('user_ldap', 'ldap_group_display_name', LDAP_GROUP_DISPLAY_NAME_ATTR), 'UTF-8');
- self::$ldapIgnoreNamingRules = OCP\Config::getSystemValue('ldapIgnoreNamingRules', false);
-
- if(empty(self::$ldapBaseUsers)) {
- OCP\Util::writeLog('ldap', 'Base for Users is empty, using Base DN', OCP\Util::INFO);
- self::$ldapBaseUsers = self::$ldapBase;
- }
- if(empty(self::$ldapBaseGroups)) {
- OCP\Util::writeLog('ldap', 'Base for Groups is empty, using Base DN', OCP\Util::INFO);
- self::$ldapBaseGroups = self::$ldapBase;
- }
-
- if(
- !empty(self::$ldapHost)
- && !empty(self::$ldapPort)
- && (
- (!empty(self::$ldapAgentName) && !empty(self::$ldapAgentPassword))
- || ( empty(self::$ldapAgentName) && empty(self::$ldapAgentPassword))
- )
- && !empty(self::$ldapBase)
- && !empty(self::$ldapUserDisplayName)
- )
- {
- self::$configured = true;
- }
- }
- }
-
- /**
- * Connects and Binds to LDAP
- */
- static private function establishConnection() {
- if(!self::$configured) {
- OCP\Util::writeLog('ldap', 'Configuration is invalid, cannot connect', OCP\Util::INFO);
- return false;
- }
- if(!self::$ldapConnectionRes) {
- self::$ldapConnectionRes = ldap_connect(self::$ldapHost, self::$ldapPort);
- if(ldap_set_option(self::$ldapConnectionRes, LDAP_OPT_PROTOCOL_VERSION, 3)) {
- if(ldap_set_option(self::$ldapConnectionRes, LDAP_OPT_REFERRALS, 0)) {
- if(self::$ldapTLS) {
- ldap_start_tls(self::$ldapConnectionRes);
- }
- }
- }
-
- $ldapLogin = @ldap_bind(self::$ldapConnectionRes, self::$ldapAgentName, self::$ldapAgentPassword );
- if(!$ldapLogin) {
- OCP\Util::writeLog('ldap', 'Bind failed: ' . ldap_errno(self::$ldapConnectionRes) . ': ' . ldap_error(self::$ldapConnectionRes), OCP\Util::ERROR);
- self::$ldapConnectionRes = null;
- return false;
- }
- }
- }
-
- static public function areCredentialsValid($name, $password) {
- return @ldap_bind(self::getConnectionResource(), $name, $password);
- }
-
- /**
- * taken from http://www.php.net/manual/en/function.array-search.php#97645
- * TODO: move somewhere, where its better placed since it is not LDAP specific. OC_Helper maybe?
- */
- static public function recursiveArraySearch($haystack, $needle, $index = null) {
- $aIt = new RecursiveArrayIterator($haystack);
- $it = new RecursiveIteratorIterator($aIt);
-
- while($it->valid()) {
- if (((isset($index) AND ($it->key() == $index)) OR (!isset($index))) AND ($it->current() == $needle)) {
- return $aIt->key();
- }
-
- $it->next();
- }
-
- return false;
- }
-
- }
diff --git a/apps/user_ldap/tests/group_ldap.php b/apps/user_ldap/tests/group_ldap.php
index 2be6b46fb23..106459580fa 100644
--- a/apps/user_ldap/tests/group_ldap.php
+++ b/apps/user_ldap/tests/group_ldap.php
@@ -26,8 +26,8 @@ class Test_Group_Ldap extends UnitTestCase {
}
function testSingleBackend(){
- OC_Group::useBackend(new OC_GROUP_LDAP());
- $group_ldap = new OC_GROUP_LDAP();
+ OC_Group::useBackend(new OCA\user_ldap\GROUP_LDAP());
+ $group_ldap = new OCA\user_ldap\GROUP_LDAP();
$this->assertIsA(OC_Group::getGroups(),gettype(array()));
$this->assertIsA($group_ldap->getGroups(),gettype(array()));
diff --git a/apps/user_ldap/user_ldap.php b/apps/user_ldap/user_ldap.php
index b51d9a55cc7..a4a8921d08d 100644
--- a/apps/user_ldap/user_ldap.php
+++ b/apps/user_ldap/user_ldap.php
@@ -23,13 +23,9 @@
*
*/
-class OC_USER_LDAP extends OC_User_Backend {
+namespace OCA\user_ldap;
- // cached settings
- protected $ldapUserFilter;
- protected $ldapQuotaAttribute;
- protected $ldapQuotaDefault;
- protected $ldapEmailAttribute;
+class USER_LDAP extends lib\Access implements \OCP\UserInterface {
// will be retrieved from LDAP server
protected $ldap_dc = false;
@@ -37,39 +33,32 @@ class OC_USER_LDAP extends OC_User_Backend {
// cache getUsers()
protected $_users = null;
- public function __construct() {
- $this->ldapUserFilter = OCP\Config::getAppValue('user_ldap', 'ldap_userlist_filter', '(objectClass=posixAccount)');
- $this->ldapQuotaAttribute = OCP\Config::getAppValue('user_ldap', 'ldap_quota_attr', '');
- $this->ldapQuotaDefault = OCP\Config::getAppValue('user_ldap', 'ldap_quota_def', '');
- $this->ldapEmailAttribute = OCP\Config::getAppValue('user_ldap', 'ldap_email_attr', '');
- }
-
private function updateQuota($dn) {
$quota = null;
- if(!empty($this->ldapQuotaDefault)) {
- $quota = $this->ldapQuotaDefault;
+ if(!empty($this->connection->ldapQuotaDefault)) {
+ $quota = $this->connection->ldapQuotaDefault;
}
- if(!empty($this->ldapQuotaAttribute)) {
- $aQuota = OC_LDAP::readAttribute($dn, $this->ldapQuotaAttribute);
+ if(!empty($this->connection->ldapQuotaAttribute)) {
+ $aQuota = $this->readAttribute($dn, $this->connection->ldapQuotaAttribute);
if($aQuota && (count($aQuota) > 0)) {
$quota = $aQuota[0];
}
}
if(!is_null($quota)) {
- OCP\Config::setUserValue(OC_LDAP::dn2username($dn), 'files', 'quota', OCP\Util::computerFileSize($quota));
+ \OCP\Config::setUserValue($this->dn2username($dn), 'files', 'quota', \OCP\Util::computerFileSize($quota));
}
}
private function updateEmail($dn) {
$email = null;
- if(!empty($this->ldapEmailAttribute)) {
- $aEmail = OC_LDAP::readAttribute($dn, $this->ldapEmailAttribute);
+ if(!empty($this->connection->ldapEmailAttribute)) {
+ $aEmail = $this->readAttribute($dn, $this->connection->ldapEmailAttribute);
if($aEmail && (count($aEmail) > 0)) {
$email = $aEmail[0];
}
- if(!is_null($email)){
- OCP\Config::setUserValue(OC_LDAP::dn2username($dn), 'settings', 'email', $email);
+ if(!is_null($email)) {
+ \OCP\Config::setUserValue($this->dn2username($dn), 'settings', 'email', $email);
}
}
}
@@ -84,15 +73,15 @@ class OC_USER_LDAP extends OC_User_Backend {
*/
public function checkPassword($uid, $password){
//find out dn of the user name
- $filter = OCP\Util::mb_str_replace('%uid', $uid, OC_LDAP::conf('ldapLoginFilter'), 'UTF-8');
- $ldap_users = OC_LDAP::fetchListOfUsers($filter, 'dn');
+ $filter = \OCP\Util::mb_str_replace('%uid', $uid, $this->connection->ldapLoginFilter, 'UTF-8');
+ $ldap_users = $this->fetchListOfUsers($filter, 'dn');
if(count($ldap_users) < 1) {
return false;
}
$dn = $ldap_users[0];
//are the credentials OK?
- if(!OC_LDAP::areCredentialsValid($dn, $password)) {
+ if(!$this->areCredentialsValid($dn, $password)) {
return false;
}
@@ -101,7 +90,7 @@ class OC_USER_LDAP extends OC_User_Backend {
$this->updateEmail($dn);
//give back the display name
- return OC_LDAP::dn2username($dn);
+ return $this->dn2username($dn);
}
/**
@@ -112,8 +101,8 @@ class OC_USER_LDAP extends OC_User_Backend {
*/
public function getUsers(){
if(is_null($this->_users)) {
- $ldap_users = OC_LDAP::fetchListOfUsers($this->ldapUserFilter, array(OC_LDAP::conf('ldapUserDisplayName'), 'dn'));
- $this->_users = OC_LDAP::ownCloudUserNames($ldap_users);
+ $ldap_users = $this->fetchListOfUsers($this->connection->ldapUserFilter, array($this->connection->ldapUserDisplayName, 'dn'));
+ $this->_users = $this->ownCloudUserNames($ldap_users);
}
return $this->_users;
}
@@ -125,13 +114,13 @@ class OC_USER_LDAP extends OC_User_Backend {
*/
public function userExists($uid){
//getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking.
- $dn = OC_LDAP::username2dn($uid);
+ $dn = $this->username2dn($uid);
if(!$dn) {
return false;
}
//if user really still exists, we will be able to read his cn
- $cn = OC_LDAP::readAttribute($dn, 'cn');
+ $cn = $this->readAttribute($dn, 'cn');
if(!$cn || empty($cn)) {
return false;
}
@@ -139,4 +128,27 @@ class OC_USER_LDAP extends OC_User_Backend {
return true;
}
+ /**
+ * @brief delete a user
+ * @param $uid The username of the user to delete
+ * @returns true/false
+ *
+ * Deletes a user
+ */
+ public function deleteUser($uid) {
+ return false;
+ }
+
+ /**
+ * @brief Check if backend implements actions
+ * @param $actions bitwise-or'ed actions
+ * @returns boolean
+ *
+ * Returns the supported actions as int to be
+ * compared with OC_USER_BACKEND_CREATE_USER etc.
+ */
+ public function implementsActions($actions) {
+ return (bool)(OC_USER_BACKEND_CHECK_PASSWORD & $actions);
+ }
+
} \ No newline at end of file
diff --git a/apps/user_migrate/templates/settings.php b/apps/user_migrate/templates/settings.php
index 1718abe9e0f..bce5fb2d7ca 100644
--- a/apps/user_migrate/templates/settings.php
+++ b/apps/user_migrate/templates/settings.php
@@ -12,7 +12,7 @@
<?php } ?>
<legend><strong><?php echo $l->t('Import user account');?></strong></legend>
</p>
- <p><input type="file" id="owncloud_import" name="owncloud_import" style="width:180px;"><label for="owncloud_import"> <?php echo $l->t('ownCloud User Zip');?></label>
+ <p><input type="file" id="owncloud_import" name="owncloud_import" style="width:280px;"><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>
diff --git a/apps/user_openid/user.php b/apps/user_openid/user.php
index d25b95259e0..88571ba618e 100644
--- a/apps/user_openid/user.php
+++ b/apps/user_openid/user.php
@@ -44,4 +44,4 @@ if(!OCP\User::userExists($USERNAME)){
}
$IDENTITY=OCP\Util::linkToAbsolute( "user_openid", "user.php" ).'/'.$USERNAME;
-require_once 'phpmyid.php';
+require_once 'openid/phpmyid.php';
diff --git a/apps/user_openid/user_openid.php b/apps/user_openid/user_openid.php
index 70b193a30b1..19f2f719b06 100644
--- a/apps/user_openid/user_openid.php
+++ b/apps/user_openid/user_openid.php
@@ -21,7 +21,7 @@
*
*/
-require_once('class.openid.v3.php');
+require_once('openid/class.openid.v3.php');
/**
* Class for user OpenId backend
diff --git a/apps/user_webfinger/.htaccess b/apps/user_webfinger/.htaccess
new file mode 100644
index 00000000000..1b13cf788ff
--- /dev/null
+++ b/apps/user_webfinger/.htaccess
@@ -0,0 +1,5 @@
+<IfModule mod_rewrite.c>
+RewriteEngine on
+RewriteRule .* - [env=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+RewriteRule ^host-meta host-meta.php [QSA,L]
+</IfModule>
diff --git a/apps/user_webfinger/appinfo/app.php b/apps/user_webfinger/appinfo/app.php
index 3336af66820..b3d9bbc7f37 100644
--- a/apps/user_webfinger/appinfo/app.php
+++ b/apps/user_webfinger/appinfo/app.php
@@ -1,3 +1 @@
<?php
-OCP\CONFIG::setAppValue('core', 'public_host-meta', '/apps/user_webfinger/host-meta.php');
-OCP\CONFIG::setAppValue('core', 'public_webfinger', '/apps/user_webfinger/webfinger.php');
diff --git a/apps/user_webfinger/appinfo/info.xml b/apps/user_webfinger/appinfo/info.xml
index 4e28814a26c..f74e5d3f93a 100644
--- a/apps/user_webfinger/appinfo/info.xml
+++ b/apps/user_webfinger/appinfo/info.xml
@@ -7,4 +7,8 @@
<author>Michiel de Jong, Florian Hülsmann</author>
<require>4</require>
<shipped>true</shipped>
+ <public>
+ <host-meta>host-meta.php</host-meta>
+ <webfinger>webfinger.php</webfinger>
+ </public>
</info>
diff --git a/apps/user_webfinger/appinfo/version b/apps/user_webfinger/appinfo/version
index 1d71ef97443..a2268e2de44 100644
--- a/apps/user_webfinger/appinfo/version
+++ b/apps/user_webfinger/appinfo/version
@@ -1 +1 @@
-0.3 \ No newline at end of file
+0.3.1 \ No newline at end of file
diff --git a/apps/user_webfinger/host-meta.php b/apps/user_webfinger/host-meta.php
index 32ffb512057..4ac37b1ea09 100644
--- a/apps/user_webfinger/host-meta.php
+++ b/apps/user_webfinger/host-meta.php
@@ -1,10 +1,27 @@
<?php
+
+if(class_exists('OC')){
+ $WEBROOT=OC::$WEBROOT;
+}else{//not called trough remote.php try to guess the webroot the best we can from here
+ // calculate the root directories
+ $SERVERROOT=str_replace("\\",'/',substr(__FILE__,0,-strlen('apps/user_webfinger/host-meta.php')));
+ $WEBROOT=substr($SERVERROOT,strlen(realpath($_SERVER['DOCUMENT_ROOT'])));
+
+ if($WEBROOT!='' and $WEBROOT[0]!=='/'){
+ $WEBROOT='/'.$WEBROOT;
+ }
+}
+
+if(substr($WEBROOT,-1)==='/'){
+ $WEBROOT=substr($WEBROOT,0,-1);
+}
+
$hostMetaHeader = array(
'Access-Control-Allow-Origin' => '*',
'Content-Type' => 'application/xrd+json'
);
$serverName = $_SERVER['SERVER_NAME'];
-$hostMetaContents = '{"links":[{"rel":"lrdd","template":"http'.(isset($_SERVER['HTTPS'])?'s':'').'://'.$serverName.'/public.php?service=webfinger&q={uri}"}]}';
+$hostMetaContents = '{"links":[{"rel":"lrdd","template":"http'.(isset($_SERVER['HTTPS'])?'s':'').'://'.$serverName.$WEBROOT.'/public.php?service=webfinger&q={uri}"}]}';
foreach($hostMetaHeader as $header => $value) {
header($header . ": " . $value);
}
diff --git a/autotest.sh b/autotest.sh
new file mode 100755
index 00000000000..a42c6ab059e
--- /dev/null
+++ b/autotest.sh
@@ -0,0 +1,110 @@
+#!/bin/bash
+#
+# ownCloud
+#
+# @author Thomas Müller
+# @copyright 2012 Thomas Müller thomas.mueller@tmit.eu
+#
+
+DATADIR=data-autotest
+BASEDIR=$PWD
+
+# create autoconfig for sqlite, mysql and (soon) postgresql
+cat > ./tests/autoconfig-sqlite.php <<DELIM
+<?php
+\$AUTOCONFIG = array (
+ 'installed' => false,
+ 'dbtype' => 'sqlite',
+ 'dbtableprefix' => 'oc_',
+ 'adminlogin' => 'admin',
+ 'adminpass' => 'admin',
+ 'directory' => '$BASEDIR/$DATADIR',
+);
+DELIM
+
+cat > ./tests/autoconfig-mysql.php <<DELIM
+<?php
+\$AUTOCONFIG = array (
+ 'installed' => false,
+ 'dbtype' => 'mysql',
+ 'dbtableprefix' => 'oc_',
+ 'adminlogin' => 'admin',
+ 'adminpass' => 'admin',
+ 'directory' => '$BASEDIR/$DATADIR',
+ 'dbuser' => 'oc_autotest',
+ 'dbname' => 'oc_autotest',
+ 'dbhost' => 'localhost',
+ 'dbpass' => 'owncloud',
+);
+DELIM
+
+cat > ./tests/autoconfig-pgsql.php <<DELIM
+<?php
+\$AUTOCONFIG = array (
+ 'installed' => false,
+ 'dbtype' => 'pgsql',
+ 'dbtableprefix' => 'oc_',
+ 'adminlogin' => 'admin',
+ 'adminpass' => 'admin',
+ 'directory' => '$BASEDIR/$DATADIR',
+ 'dbuser' => 'oc_autotest',
+ 'dbname' => 'oc_autotest',
+ 'dbhost' => 'localhost',
+ 'dbpass' => 'owncloud',
+);
+DELIM
+
+function execute_tests {
+ echo "Setup environment for $1 testing ..."
+ # back to root folder
+ cd $BASEDIR
+
+ # revert changes to tests/data
+ git checkout tests/data/*
+
+ # reset data directory
+ rm -rf $DATADIR
+ mkdir $DATADIR
+
+ # remove the old config file
+ rm -rf config/config.php
+
+ # drop database
+ if [ "$1" == "mysql" ] ; then
+ mysql -u oc_autotest -powncloud -e "DROP DATABASE oc_autotest"
+ fi
+ if [ "$1" == "pgsql" ] ; then
+ dropdb -U oc_autotest oc_autotest
+ fi
+
+ # copy autoconfig
+ cp $BASEDIR/tests/autoconfig-$1.php $BASEDIR/config/autoconfig.php
+
+ # trigger installation
+ php -f index.php
+
+ #test execution
+ echo "Testing with $1 ..."
+ cd tests
+ php -f index.php -- xml $1 > autotest-results-$1.xml
+}
+
+#
+# start test execution
+#
+execute_tests "sqlite"
+execute_tests 'mysql'
+execute_tests 'pgsql'
+
+#
+# NOTES on mysql:
+# - CREATE USER 'oc_autotest'@'localhost' IDENTIFIED BY 'owncloud';
+# - grant access permissions: grant all on oc_autotest.* to 'oc_autotest'@'localhost';
+#
+# NOTES on pgsql:
+# - su - postgres
+# - createuser -P (enter username and password and enable superuser)
+# - to enable dropdb I decided to add following line to pg_hba.conf (this is not the safest way but I don't care for the testing machine):
+# local all all trust
+#
+
diff --git a/config/mount.sample.php b/config/mount.sample.php
new file mode 100644
index 00000000000..8943b6a7054
--- /dev/null
+++ b/config/mount.sample.php
@@ -0,0 +1,13 @@
+<?php return array(
+ 'global' => array(
+ '/' => array('class' => 'OC_Filestorage_Local', 'options' => array())
+ ),
+ 'group' => array(
+ 'admin' => array('/$user/files/Admin Stuff' => array('class' => 'OC_Filestorage_Local', 'options' => array()))
+ ),
+ 'user' => array(
+ 'all' => array('/$user/files/Pictures' => array('class' => 'OC_Filestorage_DAV', 'options' => array())),
+ 'MTGap' => array('/$user/files/Test' => array('class' => 'OC_Filestorage_FTP', 'options' => array()))
+ )
+ );
+?> \ No newline at end of file
diff --git a/core/css/styles.css b/core/css/styles.css
index 338518e5b2f..b818caa279f 100644
--- a/core/css/styles.css
+++ b/core/css/styles.css
@@ -139,6 +139,8 @@ li.error { width:640px; margin:4em auto; padding:1em 1em 1em 4em; background:#ff
a.bookmarklet { background-color: #ddd; border:1px solid #ccc; padding: 5px;padding-top: 0px;padding-bottom: 2px; text-decoration: none; margin-top: 5px }
+.exception{color: #000000;}
+.exception textarea{width:95%;height: 200px;background:#ffe;border:0;}
/* ---- DIALOGS ---- */
#dirtree {width: 100%;}
diff --git a/core/js/eventsource.js b/core/js/eventsource.js
index 08259e02cae..e3ad7e3a671 100644
--- a/core/js/eventsource.js
+++ b/core/js/eventsource.js
@@ -40,6 +40,7 @@ OC.EventSource=function(src,data){
dataStr+=name+'='+encodeURIComponent(data[name])+'&';
}
}
+ dataStr+='requesttoken='+OC.EventSource.requesttoken;
if(!this.useFallBack && typeof EventSource !='undefined'){
this.source=new EventSource(src+'?'+dataStr);
this.source.onmessage=function(e){
diff --git a/core/templates/exception.php b/core/templates/exception.php
new file mode 100644
index 00000000000..7f58ce252cf
--- /dev/null
+++ b/core/templates/exception.php
@@ -0,0 +1,30 @@
+<ul>
+ <li class='error'>
+ <details>
+ <summary class="error">We're sorry, but something went terribly wrong.<br></summary>
+ <p class="exception">
+ <?php
+ if($_['showsysinfo'] == true){
+ echo 'If you would like to support ownCloud\'s developers and report this error in our <a href="http://bugs.owncloud.org">Bugtracker</a>, please copy the following informations into the description. <br><br><textarea readonly>';
+ echo 'Message: ' . $_['message'] . "\n";
+ echo 'Error Code: ' . $_['code'] . "\n";
+ echo 'File: ' . $_['file'] . "\n";
+ echo 'Line: ' . $_['line'] . "\n\n";
+ echo 'PHP: ' . $_['sysinfo']['phpversion'] . "\n";
+ echo 'OS: ' . $_['sysinfo']['os'] . "\n";
+ echo 'OS Release: ' . $_['sysinfo']['osrelease'] . "\n";
+ echo 'OS Arch.: ' . $_['sysinfo']['osarchitecture'] . "\n";
+ echo 'PHP-Server-Interface: ' . $_['sysinfo']['phpserverinterface'] . "\n";
+ echo 'Protocol: ' . $_['sysinfo']['serverprotocol'] . "\n";
+ echo 'HTTPS: ' . $_['sysinfo']['https'] . "\n";
+ echo 'Request Method: ' . $_['sysinfo']['requestmethod'] . "\n";
+ echo 'Database: ' . $_['sysinfo']['database'] . "\n";
+ echo '</textarea>';
+ }else{
+ echo 'Your administrator has disabled systeminformations.';
+ }
+ ?>
+ </p>
+ </details>
+ </li>
+</ul> \ No newline at end of file
diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php
index 7e98fdedc2d..dc303ffc1a7 100644
--- a/core/templates/layout.user.php
+++ b/core/templates/layout.user.php
@@ -33,6 +33,7 @@
<script type="text/javascript">
$(function() {
requesttoken = '<?php echo $_['requesttoken']; ?>';
+ OC.EventSource.requesttoken=requesttoken;
$(document).bind('ajaxSend', function(elm, xhr, s){
if(requesttoken) {
xhr.setRequestHeader('requesttoken', requesttoken);
diff --git a/core/templates/login.php b/core/templates/login.php
index 985cf90c2a2..b35c4a33be8 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'])?OC_Util::sanitizeHTML($_POST['user'],ENT_COMPAT,'utf-8').'"':'" autofocus'; ?> autocomplete="off" required />
+ <input type="text" name="user" id="user" value="<?php echo !empty($_POST['user'])?OC_Util::sanitizeHTML($_POST['user'],ENT_COMPAT,'utf-8').'"':'" autofocus'; ?> autocomplete="on" required />
</p>
<p class="infield">
<label for="password" class="infield"><?php echo $l->t( 'Password' ); ?></label>
diff --git a/l10n/de/calendar.po b/l10n/de/calendar.po
index 2e5af84f5a5..d129d8cae07 100644
--- a/l10n/de/calendar.po
+++ b/l10n/de/calendar.po
@@ -8,26 +8,36 @@
# Jan-Christoph Borchardt <JanCBorchardt@fsfe.org>, 2011.
# Jan-Christoph Borchardt <jan@unhosted.org>, 2011.
# Marcel Kühlhorn <susefan93@gmx.de>, 2012.
+# <niko@nik-o-mat.de>, 2012.
# <peddn@web.de>, 2012.
+# <thomas.mueller@tmit.eu>, 2012.
msgid ""
msgstr ""
"Project-Id-Version: ownCloud\n"
"Report-Msgid-Bugs-To: http://bugs.owncloud.org/\n"
-"POT-Creation-Date: 2012-06-06 00:12+0200\n"
-"PO-Revision-Date: 2012-06-05 22:14+0000\n"
-"Last-Translator: icewind <icewind1991@gmail.com>\n"
-"Language-Team: German (http://www.transifex.net/projects/p/owncloud/language/de/)\n"
+"POT-Creation-Date: 2012-07-25 22:14+0200\n"
+"PO-Revision-Date: 2012-07-25 20:14+0000\n"
+"Last-Translator: owncloud_robot <thomas.mueller@tmit.eu>\n"
+"Language-Team: German (http://www.transifex.com/projects/p/owncloud/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
-#: ajax/categories/rescan.php:28
+#: ajax/cache/status.php:19
+msgid "Not all calendars are completely cached"
+msgstr ""
+
+#: ajax/cache/status.php:21
+msgid "Everything seems to be completely cached"
+msgstr ""
+
+#: ajax/categories/rescan.php:29
msgid "No calendars found."
msgstr "Keine Kalender gefunden"
-#: ajax/categories/rescan.php:36
+#: ajax/categories/rescan.php:37
msgid "No events found."
msgstr "Keine Termine gefunden"
@@ -35,300 +45,395 @@ msgstr "Keine Termine gefunden"
msgid "Wrong calendar"
msgstr "Falscher Kalender"
+#: ajax/import/dropimport.php:29 ajax/import/import.php:64
+msgid ""
+"The file contained either no events or all events are already saved in your "
+"calendar."
+msgstr ""
+
+#: ajax/import/dropimport.php:31 ajax/import/import.php:67
+msgid "events has been saved in the new calendar"
+msgstr ""
+
+#: ajax/import/import.php:56
+msgid "Import failed"
+msgstr "Import fehlgeschlagen"
+
+#: ajax/import/import.php:69
+msgid "events has been saved in your calendar"
+msgstr ""
+
#: ajax/settings/guesstimezone.php:25
msgid "New Timezone:"
msgstr "Neue Zeitzone:"
-#: ajax/settings/settimezone.php:22
+#: ajax/settings/settimezone.php:23
msgid "Timezone changed"
msgstr "Zeitzone geändert"
-#: ajax/settings/settimezone.php:24
+#: ajax/settings/settimezone.php:25
msgid "Invalid request"
msgstr "Fehlerhafte Anfrage"
-#: appinfo/app.php:19 templates/calendar.php:15
-#: templates/part.eventform.php:33 templates/part.showevent.php:31
+#: appinfo/app.php:35 templates/calendar.php:15
+#: templates/part.eventform.php:33 templates/part.showevent.php:33
#: templates/settings.php:12
msgid "Calendar"
msgstr "Kalender"
-#: js/calendar.js:93
-msgid "Deletion failed"
-msgstr ""
-
#: js/calendar.js:828
msgid "ddd"
-msgstr ""
+msgstr "ddd"
#: js/calendar.js:829
msgid "ddd M/d"
-msgstr ""
+msgstr "ddd d.M"
#: js/calendar.js:830
msgid "dddd M/d"
-msgstr ""
+msgstr "dddd d.M"
#: js/calendar.js:833
msgid "MMMM yyyy"
-msgstr ""
+msgstr "MMMM yyyy"
#: js/calendar.js:835
msgid "MMM d[ yyyy]{ '&#8212;'[ MMM] d yyyy}"
-msgstr "ddd d MMMM[ yyyy]{ -[ddd d] MMMM yyyy}"
+msgstr "MMM d[ yyyy]{ '&#8212;'[ MMM] d yyyy}"
#: js/calendar.js:837
msgid "dddd, MMM d, yyyy"
-msgstr ""
+msgstr "dddd, d. MMM yyyy"
-#: lib/app.php:125
+#: lib/app.php:121
msgid "Birthday"
msgstr "Geburtstag"
-#: lib/app.php:126
+#: lib/app.php:122
msgid "Business"
msgstr "Geschäftlich"
-#: lib/app.php:127
+#: lib/app.php:123
msgid "Call"
msgstr "Anruf"
-#: lib/app.php:128
+#: lib/app.php:124
msgid "Clients"
msgstr "Kunden"
-#: lib/app.php:129
+#: lib/app.php:125
msgid "Deliverer"
msgstr "Lieferant"
-#: lib/app.php:130
+#: lib/app.php:126
msgid "Holidays"
msgstr "Urlaub"
-#: lib/app.php:131
+#: lib/app.php:127
msgid "Ideas"
msgstr "Ideen"
-#: lib/app.php:132
+#: lib/app.php:128
msgid "Journey"
msgstr "Reise"
-#: lib/app.php:133
+#: lib/app.php:129
msgid "Jubilee"
msgstr "Jubiläum"
-#: lib/app.php:134
+#: lib/app.php:130
msgid "Meeting"
msgstr "Treffen"
-#: lib/app.php:135
+#: lib/app.php:131
msgid "Other"
msgstr "Anderes"
-#: lib/app.php:136
+#: lib/app.php:132
msgid "Personal"
msgstr "Persönlich"
-#: lib/app.php:137
+#: lib/app.php:133
msgid "Projects"
msgstr "Projekte"
-#: lib/app.php:138
+#: lib/app.php:134
msgid "Questions"
msgstr "Fragen"
-#: lib/app.php:139
+#: lib/app.php:135
msgid "Work"
msgstr "Arbeit"
-#: lib/app.php:380
+#: lib/app.php:351 lib/app.php:361
+msgid "by"
+msgstr "von"
+
+#: lib/app.php:359 lib/app.php:399
msgid "unnamed"
msgstr "unbenannt"
-#: lib/object.php:330
+#: lib/import.php:184 templates/calendar.php:12
+#: templates/part.choosecalendar.php:22
+msgid "New Calendar"
+msgstr "Neuer Kalender"
+
+#: lib/object.php:372
msgid "Does not repeat"
msgstr "einmalig"
-#: lib/object.php:331
+#: lib/object.php:373
msgid "Daily"
msgstr "täglich"
-#: lib/object.php:332
+#: lib/object.php:374
msgid "Weekly"
msgstr "wöchentlich"
-#: lib/object.php:333
+#: lib/object.php:375
msgid "Every Weekday"
msgstr "jeden Wochentag"
-#: lib/object.php:334
+#: lib/object.php:376
msgid "Bi-Weekly"
msgstr "jede zweite Woche"
-#: lib/object.php:335
+#: lib/object.php:377
msgid "Monthly"
msgstr "monatlich"
-#: lib/object.php:336
+#: lib/object.php:378
msgid "Yearly"
msgstr "jährlich"
-#: lib/object.php:343
+#: lib/object.php:388
msgid "never"
msgstr "niemals"
-#: lib/object.php:344
+#: lib/object.php:389
msgid "by occurrences"
msgstr "nach Terminen"
-#: lib/object.php:345
+#: lib/object.php:390
msgid "by date"
msgstr "nach Datum"
-#: lib/object.php:352
+#: lib/object.php:400
msgid "by monthday"
msgstr "an einem Monatstag"
-#: lib/object.php:353
+#: lib/object.php:401
msgid "by weekday"
msgstr "an einem Wochentag"
-#: lib/object.php:360 templates/settings.php:42
+#: lib/object.php:411 templates/calendar.php:5 templates/settings.php:42
msgid "Monday"
msgstr "Montag"
-#: lib/object.php:361
+#: lib/object.php:412 templates/calendar.php:5
msgid "Tuesday"
msgstr "Dienstag"
-#: lib/object.php:362
+#: lib/object.php:413 templates/calendar.php:5
msgid "Wednesday"
msgstr "Mittwoch"
-#: lib/object.php:363
+#: lib/object.php:414 templates/calendar.php:5
msgid "Thursday"
msgstr "Donnerstag"
-#: lib/object.php:364
+#: lib/object.php:415 templates/calendar.php:5
msgid "Friday"
msgstr "Freitag"
-#: lib/object.php:365
+#: lib/object.php:416 templates/calendar.php:5
msgid "Saturday"
msgstr "Samstag"
-#: lib/object.php:366 templates/settings.php:43
+#: lib/object.php:417 templates/calendar.php:5 templates/settings.php:43
msgid "Sunday"
msgstr "Sonntag"
-#: lib/object.php:373
+#: lib/object.php:427
msgid "events week of month"
msgstr "Woche des Monats vom Termin"
-#: lib/object.php:374
+#: lib/object.php:428
msgid "first"
msgstr "erste"
-#: lib/object.php:375
+#: lib/object.php:429
msgid "second"
msgstr "zweite"
-#: lib/object.php:376
+#: lib/object.php:430
msgid "third"
msgstr "dritte"
-#: lib/object.php:377
+#: lib/object.php:431
msgid "fourth"
msgstr "vierte"
-#: lib/object.php:378
+#: lib/object.php:432
msgid "fifth"
msgstr "fünfte"
-#: lib/object.php:379
+#: lib/object.php:433
msgid "last"
msgstr "letzte"
-#: lib/object.php:401
+#: lib/object.php:467 templates/calendar.php:7
msgid "January"
msgstr "Januar"
-#: lib/object.php:402
+#: lib/object.php:468 templates/calendar.php:7
msgid "February"
msgstr "Februar"
-#: lib/object.php:403
+#: lib/object.php:469 templates/calendar.php:7
msgid "March"
msgstr "März"
-#: lib/object.php:404
+#: lib/object.php:470 templates/calendar.php:7
msgid "April"
msgstr "April"
-#: lib/object.php:405
+#: lib/object.php:471 templates/calendar.php:7
msgid "May"
msgstr "Mai"
-#: lib/object.php:406
+#: lib/object.php:472 templates/calendar.php:7
msgid "June"
msgstr "Juni"
-#: lib/object.php:407
+#: lib/object.php:473 templates/calendar.php:7
msgid "July"
msgstr "Juli"
-#: lib/object.php:408
+#: lib/object.php:474 templates/calendar.php:7
msgid "August"
msgstr "August"
-#: lib/object.php:409
+#: lib/object.php:475 templates/calendar.php:7
msgid "September"
msgstr "September"
-#: lib/object.php:410
+#: lib/object.php:476 templates/calendar.php:7
msgid "October"
msgstr "Oktober"
-#: lib/object.php:411
+#: lib/object.php:477 templates/calendar.php:7
msgid "November"
msgstr "November"
-#: lib/object.php:412
+#: lib/object.php:478 templates/calendar.php:7
msgid "December"
msgstr "Dezember"
-#: lib/object.php:418
+#: lib/object.php:488
msgid "by events date"
msgstr "nach Tag des Termins"
-#: lib/object.php:419
+#: lib/object.php:489
msgid "by yearday(s)"
msgstr "nach Tag des Jahres"
-#: lib/object.php:420
+#: lib/object.php:490
msgid "by weeknumber(s)"
msgstr "nach Wochennummer"
-#: lib/object.php:421
+#: lib/object.php:491
msgid "by day and month"
msgstr "nach Tag und Monat"
-#: lib/search.php:32 lib/search.php:34 lib/search.php:37
+#: lib/search.php:35 lib/search.php:37 lib/search.php:40
msgid "Date"
msgstr "Datum"
-#: lib/search.php:40
+#: lib/search.php:43
msgid "Cal."
msgstr "Kal."
+#: templates/calendar.php:6
+msgid "Sun."
+msgstr "So"
+
+#: templates/calendar.php:6
+msgid "Mon."
+msgstr "Mo"
+
+#: templates/calendar.php:6
+msgid "Tue."
+msgstr "Di"
+
+#: templates/calendar.php:6
+msgid "Wed."
+msgstr "Mi"
+
+#: templates/calendar.php:6
+msgid "Thu."
+msgstr "Do"
+
+#: templates/calendar.php:6
+msgid "Fri."
+msgstr "Fr"
+
+#: templates/calendar.php:6
+msgid "Sat."
+msgstr "Sa"
+
+#: templates/calendar.php:8
+msgid "Jan."
+msgstr "Jan."
+
+#: templates/calendar.php:8
+msgid "Feb."
+msgstr "Feb."
+
+#: templates/calendar.php:8
+msgid "Mar."
+msgstr "Mär."
+
+#: templates/calendar.php:8
+msgid "Apr."
+msgstr "Apr."
+
+#: templates/calendar.php:8
+msgid "May."
+msgstr "Mai"
+
+#: templates/calendar.php:8
+msgid "Jun."
+msgstr "Jun."
+
+#: templates/calendar.php:8
+msgid "Jul."
+msgstr "Jul."
+
+#: templates/calendar.php:8
+msgid "Aug."
+msgstr "Aug."
+
+#: templates/calendar.php:8
+msgid "Sep."
+msgstr "Sep."
+
+#: templates/calendar.php:8
+msgid "Oct."
+msgstr "Okt."
+
+#: templates/calendar.php:8
+msgid "Nov."
+msgstr "Nov."
+
+#: templates/calendar.php:8
+msgid "Dec."
+msgstr "Dez."
+
#: templates/calendar.php:11
msgid "All day"
msgstr "Ganztags"
-#: templates/calendar.php:12 templates/part.choosecalendar.php:22
-msgid "New Calendar"
-msgstr "Neuer Kalender"
-
#: templates/calendar.php:13
msgid "Missing fields"
msgstr "fehlende Felder"
@@ -362,27 +467,27 @@ msgstr "Der Termin hört auf, bevor er angefangen hat."
msgid "There was a database fail"
msgstr "Datenbankfehler"
-#: templates/calendar.php:40
+#: templates/calendar.php:38
msgid "Week"
msgstr "Woche"
-#: templates/calendar.php:41
+#: templates/calendar.php:39
msgid "Month"
msgstr "Monat"
-#: templates/calendar.php:42
+#: templates/calendar.php:40
msgid "List"
msgstr "Liste"
-#: templates/calendar.php:48
+#: templates/calendar.php:44
msgid "Today"
msgstr "Heute"
-#: templates/calendar.php:49
+#: templates/calendar.php:45
msgid "Calendars"
msgstr "Kalender"
-#: templates/calendar.php:67
+#: templates/calendar.php:59
msgid "There was a fail, while parsing the file."
msgstr "Fehler beim Einlesen der Datei."
@@ -395,7 +500,7 @@ msgid "Your calendars"
msgstr "Deine Kalender"
#: templates/part.choosecalendar.php:27
-#: templates/part.choosecalendar.rowfields.php:5
+#: templates/part.choosecalendar.rowfields.php:11
msgid "CalDav Link"
msgstr "CalDAV-Link"
@@ -407,19 +512,19 @@ msgstr "geteilte Kalender"
msgid "No shared calendars"
msgstr "Keine geteilten Kalender"
-#: templates/part.choosecalendar.rowfields.php:4
+#: templates/part.choosecalendar.rowfields.php:8
msgid "Share Calendar"
msgstr "Kalender teilen"
-#: templates/part.choosecalendar.rowfields.php:6
+#: templates/part.choosecalendar.rowfields.php:14
msgid "Download"
msgstr "Herunterladen"
-#: templates/part.choosecalendar.rowfields.php:7
+#: templates/part.choosecalendar.rowfields.php:17
msgid "Edit"
msgstr "Bearbeiten"
-#: templates/part.choosecalendar.rowfields.php:8
+#: templates/part.choosecalendar.rowfields.php:20
#: templates/part.editevent.php:9
msgid "Delete"
msgstr "Löschen"
@@ -505,23 +610,23 @@ msgstr "Kategorien mit Kommas trennen"
msgid "Edit categories"
msgstr "Kategorien ändern"
-#: templates/part.eventform.php:56 templates/part.showevent.php:55
+#: templates/part.eventform.php:56 templates/part.showevent.php:52
msgid "All Day Event"
msgstr "Ganztägiges Ereignis"
-#: templates/part.eventform.php:60 templates/part.showevent.php:59
+#: templates/part.eventform.php:60 templates/part.showevent.php:56
msgid "From"
msgstr "von"
-#: templates/part.eventform.php:68 templates/part.showevent.php:67
+#: templates/part.eventform.php:68 templates/part.showevent.php:64
msgid "To"
msgstr "bis"
-#: templates/part.eventform.php:76 templates/part.showevent.php:75
+#: templates/part.eventform.php:76 templates/part.showevent.php:72
msgid "Advanced options"
msgstr "Erweiterte Optionen"
-#: templates/part.eventform.php:81 templates/part.showevent.php:80
+#: templates/part.eventform.php:81 templates/part.showevent.php:77
msgid "Location"
msgstr "Ort"
@@ -529,7 +634,7 @@ msgstr "Ort"
msgid "Location of the Event"
msgstr "Ort"
-#: templates/part.eventform.php:89 templates/part.showevent.php:88
+#: templates/part.eventform.php:89 templates/part.showevent.php:85
msgid "Description"
msgstr "Beschreibung"
@@ -537,84 +642,86 @@ msgstr "Beschreibung"
msgid "Description of the Event"
msgstr "Beschreibung"
-#: templates/part.eventform.php:100 templates/part.showevent.php:98
+#: templates/part.eventform.php:100 templates/part.showevent.php:95
msgid "Repeat"
msgstr "wiederholen"
-#: templates/part.eventform.php:107 templates/part.showevent.php:105
+#: templates/part.eventform.php:107 templates/part.showevent.php:102
msgid "Advanced"
msgstr "Erweitert"
-#: templates/part.eventform.php:151 templates/part.showevent.php:149
+#: templates/part.eventform.php:151 templates/part.showevent.php:146
msgid "Select weekdays"
msgstr "Wochentage auswählen"
#: templates/part.eventform.php:164 templates/part.eventform.php:177
-#: templates/part.showevent.php:162 templates/part.showevent.php:175
+#: templates/part.showevent.php:159 templates/part.showevent.php:172
msgid "Select days"
msgstr "Tage auswählen"
-#: templates/part.eventform.php:169 templates/part.showevent.php:167
+#: templates/part.eventform.php:169 templates/part.showevent.php:164
msgid "and the events day of year."
msgstr "und den Tag des Jahres vom Termin"
-#: templates/part.eventform.php:182 templates/part.showevent.php:180
+#: templates/part.eventform.php:182 templates/part.showevent.php:177
msgid "and the events day of month."
msgstr "und den Tag des Monats vom Termin"
-#: templates/part.eventform.php:190 templates/part.showevent.php:188
+#: templates/part.eventform.php:190 templates/part.showevent.php:185
msgid "Select months"
msgstr "Monate auswählen"
-#: templates/part.eventform.php:203 templates/part.showevent.php:201
+#: templates/part.eventform.php:203 templates/part.showevent.php:198
msgid "Select weeks"
msgstr "Wochen auswählen"
-#: templates/part.eventform.php:208 templates/part.showevent.php:206
+#: templates/part.eventform.php:208 templates/part.showevent.php:203
msgid "and the events week of year."
msgstr "und den Tag des Jahres vom Termin"
-#: templates/part.eventform.php:214 templates/part.showevent.php:212
+#: templates/part.eventform.php:214 templates/part.showevent.php:209
msgid "Interval"
msgstr "Intervall"
-#: templates/part.eventform.php:220 templates/part.showevent.php:218
+#: templates/part.eventform.php:220 templates/part.showevent.php:215
msgid "End"
msgstr "Ende"
-#: templates/part.eventform.php:233 templates/part.showevent.php:231
+#: templates/part.eventform.php:233 templates/part.showevent.php:228
msgid "occurrences"
msgstr "Termine"
-#: templates/part.import.php:1
+#: templates/part.import.php:14
+msgid "create a new calendar"
+msgstr "Neuen Kalender anlegen"
+
+#: templates/part.import.php:17
msgid "Import a calendar file"
msgstr "Kalenderdatei Importieren"
-#: templates/part.import.php:6
-msgid "Please choose the calendar"
-msgstr "Bitte wählen Sie den Kalender."
-
-#: templates/part.import.php:10
-msgid "create a new calendar"
-msgstr "Neuen Kalender anlegen"
+#: templates/part.import.php:24
+msgid "Please choose a calendar"
+msgstr ""
-#: templates/part.import.php:15
+#: templates/part.import.php:36
msgid "Name of new calendar"
msgstr "Kalendername"
-#: templates/part.import.php:17
-msgid "Import"
-msgstr "Importieren"
+#: templates/part.import.php:44
+msgid "Take an available name!"
+msgstr ""
-#: templates/part.import.php:20
-msgid "Importing calendar"
-msgstr "Kalender wird importiert."
+#: templates/part.import.php:45
+msgid ""
+"A Calendar with this name already exists. If you continue anyhow, these "
+"calendars will be merged."
+msgstr ""
-#: templates/part.import.php:23
-msgid "Calendar imported successfully"
-msgstr "Kalender erfolgreich importiert"
+#: templates/part.import.php:47
+msgid "Import"
+msgstr "Importieren"
-#: templates/part.import.php:24
+#: templates/part.import.php:56
msgid "Close Dialog"
msgstr "Dialog schließen"
@@ -630,15 +737,11 @@ msgstr "Termin öffnen"
msgid "No categories selected"
msgstr "Keine Kategorie ausgewählt"
-#: templates/part.showevent.php:25
-msgid "Select category"
-msgstr "Kategorie auswählen"
-
#: templates/part.showevent.php:37
msgid "of"
msgstr "von"
-#: templates/part.showevent.php:62 templates/part.showevent.php:70
+#: templates/part.showevent.php:59 templates/part.showevent.php:67
msgid "at"
msgstr "um"
@@ -666,17 +769,41 @@ msgstr "12h"
msgid "First day of the week"
msgstr "erster Wochentag"
-#: templates/settings.php:49
-msgid "Calendar CalDAV syncing address:"
-msgstr "Kalender CalDAV Synchronisationsadresse:"
+#: templates/settings.php:47
+msgid "Cache"
+msgstr ""
+
+#: templates/settings.php:48
+msgid "Clear cache for repeating events"
+msgstr ""
+
+#: templates/settings.php:53
+msgid "Calendar CalDAV syncing addresses"
+msgstr ""
+
+#: templates/settings.php:53
+msgid "more info"
+msgstr ""
+
+#: templates/settings.php:55
+msgid "Primary address (Kontact et al)"
+msgstr ""
+
+#: templates/settings.php:57
+msgid "iOS/OS X"
+msgstr ""
+
+#: templates/settings.php:59
+msgid "Read only iCalendar link(s)"
+msgstr ""
#: templates/share.dropdown.php:20
msgid "Users"
-msgstr "Nutzer"
+msgstr "Benutzer"
#: templates/share.dropdown.php:21
msgid "select users"
-msgstr "Nutzer auswählen"
+msgstr "Benutzer auswählen"
#: templates/share.dropdown.php:36 templates/share.dropdown.php:62
msgid "Editable"
diff --git a/l10n/de/gallery.po b/l10n/de/gallery.po
index 50151756004..ec9656fa93f 100644
--- a/l10n/de/gallery.po
+++ b/l10n/de/gallery.po
@@ -6,75 +6,41 @@
# <admin@s-goecker.de>, 2012.
# Bartek <bart.p.pl@gmail.com>, 2012.
# Marcel Kühlhorn <susefan93@gmx.de>, 2012.
+# <niko@nik-o-mat.de>, 2012.
+# <thomas.mueller@tmit.eu>, 2012.
msgid ""
msgstr ""
"Project-Id-Version: ownCloud\n"
"Report-Msgid-Bugs-To: http://bugs.owncloud.org/\n"
-"POT-Creation-Date: 2012-06-06 00:12+0200\n"
-"PO-Revision-Date: 2012-06-05 22:15+0000\n"
-"Last-Translator: icewind <icewind1991@gmail.com>\n"
-"Language-Team: German (http://www.transifex.net/projects/p/owncloud/language/de/)\n"
+"POT-Creation-Date: 2012-07-25 22:14+0200\n"
+"PO-Revision-Date: 2012-07-25 20:05+0000\n"
+"Last-Translator: owncloud_robot <thomas.mueller@tmit.eu>\n"
+"Language-Team: German (http://www.transifex.com/projects/p/owncloud/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
-#: appinfo/app.php:37
+#: appinfo/app.php:39
msgid "Pictures"
msgstr "Bilder"
-#: js/album_cover.js:44
+#: js/pictures.js:12
msgid "Share gallery"
-msgstr ""
+msgstr "Galerie teilen"
-#: js/album_cover.js:64 js/album_cover.js:100 js/album_cover.js:133
+#: js/pictures.js:32
msgid "Error: "
-msgstr ""
+msgstr "Fehler:"
-#: js/album_cover.js:64 js/album_cover.js:100
+#: js/pictures.js:32
msgid "Internal error"
-msgstr ""
-
-#: js/album_cover.js:114
-msgid "Scanning root"
-msgstr ""
-
-#: js/album_cover.js:115
-msgid "Default order"
-msgstr ""
-
-#: js/album_cover.js:116
-msgid "Ascending"
-msgstr ""
-
-#: js/album_cover.js:116
-msgid "Descending"
-msgstr ""
-
-#: js/album_cover.js:117 templates/index.php:19
-msgid "Settings"
-msgstr "Einstellungen"
-
-#: js/album_cover.js:122
-msgid "Scanning root cannot be empty"
-msgstr ""
-
-#: js/album_cover.js:122 js/album_cover.js:133
-msgid "Error"
-msgstr ""
-
-#: templates/index.php:16
-msgid "Rescan"
-msgstr "Erneut Scannen"
-
-#: templates/index.php:17
-msgid "Stop"
-msgstr "Stopp"
+msgstr "Interner Fehler"
-#: templates/index.php:18
-msgid "Share"
-msgstr "Teilen"
+#: templates/index.php:27
+msgid "Slideshow"
+msgstr "Slideshow"
#: templates/view_album.php:19
msgid "Back"
diff --git a/l10n/templates/bookmarks.pot b/l10n/templates/bookmarks.pot
index a55aacd8701..3a9362d978e 100644
--- a/l10n/templates/bookmarks.pot
+++ b/l10n/templates/bookmarks.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-06-06 00:12+0200\n"
+"POT-Creation-Date: 2012-07-25 22:14+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -21,7 +21,7 @@ msgstr ""
msgid "Bookmarks"
msgstr ""
-#: bookmarksHelper.php:98
+#: bookmarksHelper.php:99
msgid "unnamed"
msgstr ""
diff --git a/l10n/templates/calendar.pot b/l10n/templates/calendar.pot
index bab10467584..c96214c4654 100644
--- a/l10n/templates/calendar.pot
+++ b/l10n/templates/calendar.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-06-06 00:12+0200\n"
+"POT-Creation-Date: 2012-07-25 22:14+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,11 +17,19 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
-#: ajax/categories/rescan.php:28
+#: ajax/cache/status.php:19
+msgid "Not all calendars are completely cached"
+msgstr ""
+
+#: ajax/cache/status.php:21
+msgid "Everything seems to be completely cached"
+msgstr ""
+
+#: ajax/categories/rescan.php:29
msgid "No calendars found."
msgstr ""
-#: ajax/categories/rescan.php:36
+#: ajax/categories/rescan.php:37
msgid "No events found."
msgstr ""
@@ -29,28 +37,42 @@ msgstr ""
msgid "Wrong calendar"
msgstr ""
+#: ajax/import/dropimport.php:29 ajax/import/import.php:64
+msgid ""
+"The file contained either no events or all events are already saved in your "
+"calendar."
+msgstr ""
+
+#: ajax/import/dropimport.php:31 ajax/import/import.php:67
+msgid "events has been saved in the new calendar"
+msgstr ""
+
+#: ajax/import/import.php:56
+msgid "Import failed"
+msgstr ""
+
+#: ajax/import/import.php:69
+msgid "events has been saved in your calendar"
+msgstr ""
+
#: ajax/settings/guesstimezone.php:25
msgid "New Timezone:"
msgstr ""
-#: ajax/settings/settimezone.php:22
+#: ajax/settings/settimezone.php:23
msgid "Timezone changed"
msgstr ""
-#: ajax/settings/settimezone.php:24
+#: ajax/settings/settimezone.php:25
msgid "Invalid request"
msgstr ""
-#: appinfo/app.php:19 templates/calendar.php:15
-#: templates/part.eventform.php:33 templates/part.showevent.php:31
+#: appinfo/app.php:35 templates/calendar.php:15
+#: templates/part.eventform.php:33 templates/part.showevent.php:33
#: templates/settings.php:12
msgid "Calendar"
msgstr ""
-#: js/calendar.js:93
-msgid "Deletion failed"
-msgstr ""
-
#: js/calendar.js:828
msgid "ddd"
msgstr ""
@@ -75,252 +97,333 @@ msgstr ""
msgid "dddd, MMM d, yyyy"
msgstr ""
-#: lib/app.php:125
+#: lib/app.php:121
msgid "Birthday"
msgstr ""
-#: lib/app.php:126
+#: lib/app.php:122
msgid "Business"
msgstr ""
-#: lib/app.php:127
+#: lib/app.php:123
msgid "Call"
msgstr ""
-#: lib/app.php:128
+#: lib/app.php:124
msgid "Clients"
msgstr ""
-#: lib/app.php:129
+#: lib/app.php:125
msgid "Deliverer"
msgstr ""
-#: lib/app.php:130
+#: lib/app.php:126
msgid "Holidays"
msgstr ""
-#: lib/app.php:131
+#: lib/app.php:127
msgid "Ideas"
msgstr ""
-#: lib/app.php:132
+#: lib/app.php:128
msgid "Journey"
msgstr ""
-#: lib/app.php:133
+#: lib/app.php:129
msgid "Jubilee"
msgstr ""
-#: lib/app.php:134
+#: lib/app.php:130
msgid "Meeting"
msgstr ""
-#: lib/app.php:135
+#: lib/app.php:131
msgid "Other"
msgstr ""
-#: lib/app.php:136
+#: lib/app.php:132
msgid "Personal"
msgstr ""
-#: lib/app.php:137
+#: lib/app.php:133
msgid "Projects"
msgstr ""
-#: lib/app.php:138
+#: lib/app.php:134
msgid "Questions"
msgstr ""
-#: lib/app.php:139
+#: lib/app.php:135
msgid "Work"
msgstr ""
-#: lib/app.php:380
+#: lib/app.php:351 lib/app.php:361
+msgid "by"
+msgstr ""
+
+#: lib/app.php:359 lib/app.php:399
msgid "unnamed"
msgstr ""
-#: lib/object.php:330
+#: lib/import.php:184 templates/calendar.php:12
+#: templates/part.choosecalendar.php:22
+msgid "New Calendar"
+msgstr ""
+
+#: lib/object.php:372
msgid "Does not repeat"
msgstr ""
-#: lib/object.php:331
+#: lib/object.php:373
msgid "Daily"
msgstr ""
-#: lib/object.php:332
+#: lib/object.php:374
msgid "Weekly"
msgstr ""
-#: lib/object.php:333
+#: lib/object.php:375
msgid "Every Weekday"
msgstr ""
-#: lib/object.php:334
+#: lib/object.php:376
msgid "Bi-Weekly"
msgstr ""
-#: lib/object.php:335
+#: lib/object.php:377
msgid "Monthly"
msgstr ""
-#: lib/object.php:336
+#: lib/object.php:378
msgid "Yearly"
msgstr ""
-#: lib/object.php:343
+#: lib/object.php:388
msgid "never"
msgstr ""
-#: lib/object.php:344
+#: lib/object.php:389
msgid "by occurrences"
msgstr ""
-#: lib/object.php:345
+#: lib/object.php:390
msgid "by date"
msgstr ""
-#: lib/object.php:352
+#: lib/object.php:400
msgid "by monthday"
msgstr ""
-#: lib/object.php:353
+#: lib/object.php:401
msgid "by weekday"
msgstr ""
-#: lib/object.php:360 templates/settings.php:42
+#: lib/object.php:411 templates/calendar.php:5 templates/settings.php:42
msgid "Monday"
msgstr ""
-#: lib/object.php:361
+#: lib/object.php:412 templates/calendar.php:5
msgid "Tuesday"
msgstr ""
-#: lib/object.php:362
+#: lib/object.php:413 templates/calendar.php:5
msgid "Wednesday"
msgstr ""
-#: lib/object.php:363
+#: lib/object.php:414 templates/calendar.php:5
msgid "Thursday"
msgstr ""
-#: lib/object.php:364
+#: lib/object.php:415 templates/calendar.php:5
msgid "Friday"
msgstr ""
-#: lib/object.php:365
+#: lib/object.php:416 templates/calendar.php:5
msgid "Saturday"
msgstr ""
-#: lib/object.php:366 templates/settings.php:43
+#: lib/object.php:417 templates/calendar.php:5 templates/settings.php:43
msgid "Sunday"
msgstr ""
-#: lib/object.php:373
+#: lib/object.php:427
msgid "events week of month"
msgstr ""
-#: lib/object.php:374
+#: lib/object.php:428
msgid "first"
msgstr ""
-#: lib/object.php:375
+#: lib/object.php:429
msgid "second"
msgstr ""
-#: lib/object.php:376
+#: lib/object.php:430
msgid "third"
msgstr ""
-#: lib/object.php:377
+#: lib/object.php:431
msgid "fourth"
msgstr ""
-#: lib/object.php:378
+#: lib/object.php:432
msgid "fifth"
msgstr ""
-#: lib/object.php:379
+#: lib/object.php:433
msgid "last"
msgstr ""
-#: lib/object.php:401
+#: lib/object.php:467 templates/calendar.php:7
msgid "January"
msgstr ""
-#: lib/object.php:402
+#: lib/object.php:468 templates/calendar.php:7
msgid "February"
msgstr ""
-#: lib/object.php:403
+#: lib/object.php:469 templates/calendar.php:7
msgid "March"
msgstr ""
-#: lib/object.php:404
+#: lib/object.php:470 templates/calendar.php:7
msgid "April"
msgstr ""
-#: lib/object.php:405
+#: lib/object.php:471 templates/calendar.php:7
msgid "May"
msgstr ""
-#: lib/object.php:406
+#: lib/object.php:472 templates/calendar.php:7
msgid "June"
msgstr ""
-#: lib/object.php:407
+#: lib/object.php:473 templates/calendar.php:7
msgid "July"
msgstr ""
-#: lib/object.php:408
+#: lib/object.php:474 templates/calendar.php:7
msgid "August"
msgstr ""
-#: lib/object.php:409
+#: lib/object.php:475 templates/calendar.php:7
msgid "September"
msgstr ""
-#: lib/object.php:410
+#: lib/object.php:476 templates/calendar.php:7
msgid "October"
msgstr ""
-#: lib/object.php:411
+#: lib/object.php:477 templates/calendar.php:7
msgid "November"
msgstr ""
-#: lib/object.php:412
+#: lib/object.php:478 templates/calendar.php:7
msgid "December"
msgstr ""
-#: lib/object.php:418
+#: lib/object.php:488
msgid "by events date"
msgstr ""
-#: lib/object.php:419
+#: lib/object.php:489
msgid "by yearday(s)"
msgstr ""
-#: lib/object.php:420
+#: lib/object.php:490
msgid "by weeknumber(s)"
msgstr ""
-#: lib/object.php:421
+#: lib/object.php:491
msgid "by day and month"
msgstr ""
-#: lib/search.php:32 lib/search.php:34 lib/search.php:37
+#: lib/search.php:35 lib/search.php:37 lib/search.php:40
msgid "Date"
msgstr ""
-#: lib/search.php:40
+#: lib/search.php:43
msgid "Cal."
msgstr ""
-#: templates/calendar.php:11
-msgid "All day"
+#: templates/calendar.php:6
+msgid "Sun."
msgstr ""
-#: templates/calendar.php:12 templates/part.choosecalendar.php:22
-msgid "New Calendar"
+#: templates/calendar.php:6
+msgid "Mon."
+msgstr ""
+
+#: templates/calendar.php:6
+msgid "Tue."
+msgstr ""
+
+#: templates/calendar.php:6
+msgid "Wed."
+msgstr ""
+
+#: templates/calendar.php:6
+msgid "Thu."
+msgstr ""
+
+#: templates/calendar.php:6
+msgid "Fri."
+msgstr ""
+
+#: templates/calendar.php:6
+msgid "Sat."
+msgstr ""
+
+#: templates/calendar.php:8
+msgid "Jan."
+msgstr ""
+
+#: templates/calendar.php:8
+msgid "Feb."
+msgstr ""
+
+#: templates/calendar.php:8
+msgid "Mar."
+msgstr ""
+
+#: templates/calendar.php:8
+msgid "Apr."
+msgstr ""
+
+#: templates/calendar.php:8
+msgid "May."
+msgstr ""
+
+#: templates/calendar.php:8
+msgid "Jun."
+msgstr ""
+
+#: templates/calendar.php:8
+msgid "Jul."
+msgstr ""
+
+#: templates/calendar.php:8
+msgid "Aug."
+msgstr ""
+
+#: templates/calendar.php:8
+msgid "Sep."
+msgstr ""
+
+#: templates/calendar.php:8
+msgid "Oct."
+msgstr ""
+
+#: templates/calendar.php:8
+msgid "Nov."
+msgstr ""
+
+#: templates/calendar.php:8
+msgid "Dec."
+msgstr ""
+
+#: templates/calendar.php:11
+msgid "All day"
msgstr ""
#: templates/calendar.php:13
@@ -356,27 +459,27 @@ msgstr ""
msgid "There was a database fail"
msgstr ""
-#: templates/calendar.php:40
+#: templates/calendar.php:38
msgid "Week"
msgstr ""
-#: templates/calendar.php:41
+#: templates/calendar.php:39
msgid "Month"
msgstr ""
-#: templates/calendar.php:42
+#: templates/calendar.php:40
msgid "List"
msgstr ""
-#: templates/calendar.php:48
+#: templates/calendar.php:44
msgid "Today"
msgstr ""
-#: templates/calendar.php:49
+#: templates/calendar.php:45
msgid "Calendars"
msgstr ""
-#: templates/calendar.php:67
+#: templates/calendar.php:59
msgid "There was a fail, while parsing the file."
msgstr ""
@@ -389,7 +492,7 @@ msgid "Your calendars"
msgstr ""
#: templates/part.choosecalendar.php:27
-#: templates/part.choosecalendar.rowfields.php:5
+#: templates/part.choosecalendar.rowfields.php:11
msgid "CalDav Link"
msgstr ""
@@ -401,19 +504,19 @@ msgstr ""
msgid "No shared calendars"
msgstr ""
-#: templates/part.choosecalendar.rowfields.php:4
+#: templates/part.choosecalendar.rowfields.php:8
msgid "Share Calendar"
msgstr ""
-#: templates/part.choosecalendar.rowfields.php:6
+#: templates/part.choosecalendar.rowfields.php:14
msgid "Download"
msgstr ""
-#: templates/part.choosecalendar.rowfields.php:7
+#: templates/part.choosecalendar.rowfields.php:17
msgid "Edit"
msgstr ""
-#: templates/part.choosecalendar.rowfields.php:8
+#: templates/part.choosecalendar.rowfields.php:20
#: templates/part.editevent.php:9
msgid "Delete"
msgstr ""
@@ -499,23 +602,23 @@ msgstr ""
msgid "Edit categories"
msgstr ""
-#: templates/part.eventform.php:56 templates/part.showevent.php:55
+#: templates/part.eventform.php:56 templates/part.showevent.php:52
msgid "All Day Event"
msgstr ""
-#: templates/part.eventform.php:60 templates/part.showevent.php:59
+#: templates/part.eventform.php:60 templates/part.showevent.php:56
msgid "From"
msgstr ""
-#: templates/part.eventform.php:68 templates/part.showevent.php:67
+#: templates/part.eventform.php:68 templates/part.showevent.php:64
msgid "To"
msgstr ""
-#: templates/part.eventform.php:76 templates/part.showevent.php:75
+#: templates/part.eventform.php:76 templates/part.showevent.php:72
msgid "Advanced options"
msgstr ""
-#: templates/part.eventform.php:81 templates/part.showevent.php:80
+#: templates/part.eventform.php:81 templates/part.showevent.php:77
msgid "Location"
msgstr ""
@@ -523,7 +626,7 @@ msgstr ""
msgid "Location of the Event"
msgstr ""
-#: templates/part.eventform.php:89 templates/part.showevent.php:88
+#: templates/part.eventform.php:89 templates/part.showevent.php:85
msgid "Description"
msgstr ""
@@ -531,84 +634,86 @@ msgstr ""
msgid "Description of the Event"
msgstr ""
-#: templates/part.eventform.php:100 templates/part.showevent.php:98
+#: templates/part.eventform.php:100 templates/part.showevent.php:95
msgid "Repeat"
msgstr ""
-#: templates/part.eventform.php:107 templates/part.showevent.php:105
+#: templates/part.eventform.php:107 templates/part.showevent.php:102
msgid "Advanced"
msgstr ""
-#: templates/part.eventform.php:151 templates/part.showevent.php:149
+#: templates/part.eventform.php:151 templates/part.showevent.php:146
msgid "Select weekdays"
msgstr ""
#: templates/part.eventform.php:164 templates/part.eventform.php:177
-#: templates/part.showevent.php:162 templates/part.showevent.php:175
+#: templates/part.showevent.php:159 templates/part.showevent.php:172
msgid "Select days"
msgstr ""
-#: templates/part.eventform.php:169 templates/part.showevent.php:167
+#: templates/part.eventform.php:169 templates/part.showevent.php:164
msgid "and the events day of year."
msgstr ""
-#: templates/part.eventform.php:182 templates/part.showevent.php:180
+#: templates/part.eventform.php:182 templates/part.showevent.php:177
msgid "and the events day of month."
msgstr ""
-#: templates/part.eventform.php:190 templates/part.showevent.php:188
+#: templates/part.eventform.php:190 templates/part.showevent.php:185
msgid "Select months"
msgstr ""
-#: templates/part.eventform.php:203 templates/part.showevent.php:201
+#: templates/part.eventform.php:203 templates/part.showevent.php:198
msgid "Select weeks"
msgstr ""
-#: templates/part.eventform.php:208 templates/part.showevent.php:206
+#: templates/part.eventform.php:208 templates/part.showevent.php:203
msgid "and the events week of year."
msgstr ""
-#: templates/part.eventform.php:214 templates/part.showevent.php:212
+#: templates/part.eventform.php:214 templates/part.showevent.php:209
msgid "Interval"
msgstr ""
-#: templates/part.eventform.php:220 templates/part.showevent.php:218
+#: templates/part.eventform.php:220 templates/part.showevent.php:215
msgid "End"
msgstr ""
-#: templates/part.eventform.php:233 templates/part.showevent.php:231
+#: templates/part.eventform.php:233 templates/part.showevent.php:228
msgid "occurrences"
msgstr ""
-#: templates/part.import.php:1
-msgid "Import a calendar file"
+#: templates/part.import.php:14
+msgid "create a new calendar"
msgstr ""
-#: templates/part.import.php:6
-msgid "Please choose the calendar"
+#: templates/part.import.php:17
+msgid "Import a calendar file"
msgstr ""
-#: templates/part.import.php:10
-msgid "create a new calendar"
+#: templates/part.import.php:24
+msgid "Please choose a calendar"
msgstr ""
-#: templates/part.import.php:15
+#: templates/part.import.php:36
msgid "Name of new calendar"
msgstr ""
-#: templates/part.import.php:17
-msgid "Import"
+#: templates/part.import.php:44
+msgid "Take an available name!"
msgstr ""
-#: templates/part.import.php:20
-msgid "Importing calendar"
+#: templates/part.import.php:45
+msgid ""
+"A Calendar with this name already exists. If you continue anyhow, these "
+"calendars will be merged."
msgstr ""
-#: templates/part.import.php:23
-msgid "Calendar imported successfully"
+#: templates/part.import.php:47
+msgid "Import"
msgstr ""
-#: templates/part.import.php:24
+#: templates/part.import.php:56
msgid "Close Dialog"
msgstr ""
@@ -624,15 +729,11 @@ msgstr ""
msgid "No categories selected"
msgstr ""
-#: templates/part.showevent.php:25
-msgid "Select category"
-msgstr ""
-
#: templates/part.showevent.php:37
msgid "of"
msgstr ""
-#: templates/part.showevent.php:62 templates/part.showevent.php:70
+#: templates/part.showevent.php:59 templates/part.showevent.php:67
msgid "at"
msgstr ""
@@ -660,8 +761,32 @@ msgstr ""
msgid "First day of the week"
msgstr ""
-#: templates/settings.php:49
-msgid "Calendar CalDAV syncing address:"
+#: templates/settings.php:47
+msgid "Cache"
+msgstr ""
+
+#: templates/settings.php:48
+msgid "Clear cache for repeating events"
+msgstr ""
+
+#: templates/settings.php:53
+msgid "Calendar CalDAV syncing addresses"
+msgstr ""
+
+#: templates/settings.php:53
+msgid "more info"
+msgstr ""
+
+#: templates/settings.php:55
+msgid "Primary address (Kontact et al)"
+msgstr ""
+
+#: templates/settings.php:57
+msgid "iOS/OS X"
+msgstr ""
+
+#: templates/settings.php:59
+msgid "Read only iCalendar link(s)"
msgstr ""
#: templates/share.dropdown.php:20
diff --git a/l10n/templates/contacts.pot b/l10n/templates/contacts.pot
index 15859a85e53..993fd3f877e 100644
--- a/l10n/templates/contacts.pot
+++ b/l10n/templates/contacts.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-06-06 00:12+0200\n"
+"POT-Creation-Date: 2012-07-25 22:14+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,91 +17,87 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
-#: ajax/activation.php:19 ajax/updateaddressbook.php:32
+#: ajax/activation.php:24 ajax/updateaddressbook.php:29
msgid "Error (de)activating addressbook."
msgstr ""
-#: ajax/addcontact.php:59
+#: ajax/addcontact.php:47
msgid "There was an error adding the contact."
msgstr ""
-#: ajax/addproperty.php:40
+#: ajax/addproperty.php:39 ajax/saveproperty.php:34
+msgid "element name is not set."
+msgstr ""
+
+#: ajax/addproperty.php:42 ajax/deletecard.php:30 ajax/saveproperty.php:37
+msgid "id is not set."
+msgstr ""
+
+#: ajax/addproperty.php:46
+msgid "Could not parse contact: "
+msgstr ""
+
+#: ajax/addproperty.php:56
msgid "Cannot add empty property."
msgstr ""
-#: ajax/addproperty.php:52
+#: ajax/addproperty.php:67
msgid "At least one of the address fields has to be filled out."
msgstr ""
-#: ajax/addproperty.php:62
+#: ajax/addproperty.php:76
msgid "Trying to add duplicate property: "
msgstr ""
-#: ajax/addproperty.php:120
-msgid "Error adding contact property."
+#: ajax/addproperty.php:144
+msgid "Error adding contact property: "
msgstr ""
-#: ajax/categories/categoriesfor.php:15
+#: ajax/categories/categoriesfor.php:17
msgid "No ID provided"
msgstr ""
-#: ajax/categories/categoriesfor.php:27
+#: ajax/categories/categoriesfor.php:34
msgid "Error setting checksum."
msgstr ""
-#: ajax/categories/delete.php:29
+#: ajax/categories/delete.php:19
msgid "No categories selected for deletion."
msgstr ""
-#: ajax/categories/delete.php:36 ajax/categories/rescan.php:28
+#: ajax/categories/delete.php:26
msgid "No address books found."
msgstr ""
-#: ajax/categories/delete.php:44 ajax/categories/rescan.php:36
+#: ajax/categories/delete.php:34
msgid "No contacts found."
msgstr ""
-#: ajax/contactdetails.php:37
+#: ajax/contactdetails.php:31
msgid "Missing ID"
msgstr ""
-#: ajax/contactdetails.php:41
+#: ajax/contactdetails.php:36
msgid "Error parsing VCard for ID: \""
msgstr ""
-#: ajax/createaddressbook.php:18
-msgid "Cannot add addressbook with an empty name."
-msgstr ""
-
-#: ajax/createaddressbook.php:24
-msgid "Error adding addressbook."
-msgstr ""
-
-#: ajax/createaddressbook.php:30
-msgid "Error activating addressbook."
-msgstr ""
-
-#: ajax/currentphoto.php:34 ajax/oc_photo.php:37 ajax/uploadphoto.php:41
+#: ajax/currentphoto.php:30 ajax/oc_photo.php:28 ajax/uploadphoto.php:36
#: ajax/uploadphoto.php:68
msgid "No contact ID was submitted."
msgstr ""
-#: ajax/currentphoto.php:40
+#: ajax/currentphoto.php:36
msgid "Error reading contact photo."
msgstr ""
-#: ajax/currentphoto.php:52
+#: ajax/currentphoto.php:48
msgid "Error saving temporary file."
msgstr ""
-#: ajax/currentphoto.php:55
+#: ajax/currentphoto.php:51
msgid "The loading photo is not valid."
msgstr ""
-#: ajax/deletecard.php:37 ajax/saveproperty.php:58
-msgid "id is not set."
-msgstr ""
-
#: ajax/deleteproperty.php:36
msgid "Information about vCard is incorrect. Please reload the page."
msgstr ""
@@ -110,117 +106,109 @@ msgstr ""
msgid "Error deleting contact property."
msgstr ""
-#: ajax/editname.php:37
+#: ajax/editname.php:31
msgid "Contact ID is missing."
msgstr ""
-#: ajax/loadphoto.php:44
-msgid "Missing contact id."
-msgstr ""
-
-#: ajax/oc_photo.php:41
+#: ajax/oc_photo.php:32
msgid "No photo path was submitted."
msgstr ""
-#: ajax/oc_photo.php:48
+#: ajax/oc_photo.php:39
msgid "File doesn't exist:"
msgstr ""
-#: ajax/oc_photo.php:54 ajax/oc_photo.php:57
+#: ajax/oc_photo.php:44 ajax/oc_photo.php:47
msgid "Error loading image."
msgstr ""
-#: ajax/savecrop.php:68
+#: ajax/savecrop.php:67
msgid "Error getting contact object."
msgstr ""
-#: ajax/savecrop.php:75
+#: ajax/savecrop.php:76
msgid "Error getting PHOTO property."
msgstr ""
-#: ajax/savecrop.php:88
+#: ajax/savecrop.php:93
msgid "Error saving contact."
msgstr ""
-#: ajax/savecrop.php:98
+#: ajax/savecrop.php:103
msgid "Error resizing image"
msgstr ""
-#: ajax/savecrop.php:101
+#: ajax/savecrop.php:106
msgid "Error cropping image"
msgstr ""
-#: ajax/savecrop.php:104
+#: ajax/savecrop.php:109
msgid "Error creating temporary image"
msgstr ""
-#: ajax/savecrop.php:107
+#: ajax/savecrop.php:112
msgid "Error finding image: "
msgstr ""
-#: ajax/saveproperty.php:55
-msgid "element name is not set."
-msgstr ""
-
-#: ajax/saveproperty.php:61
+#: ajax/saveproperty.php:40
msgid "checksum is not set."
msgstr ""
-#: ajax/saveproperty.php:78
+#: ajax/saveproperty.php:59
msgid "Information about vCard is incorrect. Please reload the page: "
msgstr ""
-#: ajax/saveproperty.php:83
+#: ajax/saveproperty.php:64
msgid "Something went FUBAR. "
msgstr ""
-#: ajax/saveproperty.php:150
+#: ajax/saveproperty.php:133
msgid "Error updating contact property."
msgstr ""
-#: ajax/updateaddressbook.php:20
+#: ajax/updateaddressbook.php:21
msgid "Cannot update addressbook with an empty name."
msgstr ""
-#: ajax/updateaddressbook.php:26
+#: ajax/updateaddressbook.php:25
msgid "Error updating addressbook."
msgstr ""
-#: ajax/uploadimport.php:46 ajax/uploadimport.php:76
+#: ajax/uploadimport.php:44 ajax/uploadimport.php:76
msgid "Error uploading contacts to storage."
msgstr ""
-#: ajax/uploadimport.php:59 ajax/uploadphoto.php:77
+#: ajax/uploadimport.php:61 ajax/uploadphoto.php:77
msgid "There is no error, the file uploaded with success"
msgstr ""
-#: ajax/uploadimport.php:60 ajax/uploadphoto.php:78
+#: ajax/uploadimport.php:62 ajax/uploadphoto.php:78
msgid "The uploaded file exceeds the upload_max_filesize directive in php.ini"
msgstr ""
-#: ajax/uploadimport.php:61 ajax/uploadphoto.php:79
+#: ajax/uploadimport.php:63 ajax/uploadphoto.php:79
msgid ""
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in "
"the HTML form"
msgstr ""
-#: ajax/uploadimport.php:62 ajax/uploadphoto.php:80
+#: ajax/uploadimport.php:64 ajax/uploadphoto.php:80
msgid "The uploaded file was only partially uploaded"
msgstr ""
-#: ajax/uploadimport.php:63 ajax/uploadphoto.php:81
+#: ajax/uploadimport.php:65 ajax/uploadphoto.php:81
msgid "No file was uploaded"
msgstr ""
-#: ajax/uploadimport.php:64 ajax/uploadphoto.php:82
+#: ajax/uploadimport.php:66 ajax/uploadphoto.php:82
msgid "Missing a temporary folder"
msgstr ""
-#: ajax/uploadphoto.php:59 ajax/uploadphoto.php:102
+#: ajax/uploadphoto.php:59 ajax/uploadphoto.php:109
msgid "Couldn't save temporary image: "
msgstr ""
-#: ajax/uploadphoto.php:62 ajax/uploadphoto.php:105
+#: ajax/uploadphoto.php:62 ajax/uploadphoto.php:112
msgid "Couldn't load temporary image: "
msgstr ""
@@ -228,113 +216,75 @@ msgstr ""
msgid "No file was uploaded. Unknown error"
msgstr ""
-#: appinfo/app.php:17 templates/settings.php:3
+#: appinfo/app.php:19 templates/settings.php:3
msgid "Contacts"
msgstr ""
-#: js/contacts.js:24
+#: js/contacts.js:53
msgid "Sorry, this functionality has not been implemented yet"
msgstr ""
-#: js/contacts.js:24
+#: js/contacts.js:53
msgid "Not implemented"
msgstr ""
-#: js/contacts.js:29
+#: js/contacts.js:58
msgid "Couldn't get a valid address."
msgstr ""
-#: js/contacts.js:29 js/contacts.js:334 js/contacts.js:341 js/contacts.js:355
-#: js/contacts.js:393 js/contacts.js:399 js/contacts.js:565 js/contacts.js:605
-#: js/contacts.js:631 js/contacts.js:668 js/contacts.js:747 js/contacts.js:753
-#: js/contacts.js:765 js/contacts.js:799 js/contacts.js:1056
-#: js/contacts.js:1064 js/contacts.js:1073 js/contacts.js:1130
-#: js/contacts.js:1146 js/contacts.js:1161 js/contacts.js:1173
-#: js/contacts.js:1196 js/contacts.js:1449 js/contacts.js:1457
-#: js/contacts.js:1483 js/contacts.js:1494 js/contacts.js:1509
-#: js/contacts.js:1526 js/contacts.js:1596 js/contacts.js:1644
-#: js/contacts.js:1654 js/contacts.js:1657
+#: js/contacts.js:58 js/contacts.js:347 js/contacts.js:363 js/contacts.js:376
+#: js/contacts.js:651 js/contacts.js:691 js/contacts.js:717 js/contacts.js:754
+#: js/contacts.js:826 js/contacts.js:832 js/contacts.js:844 js/contacts.js:878
+#: js/contacts.js:1141 js/contacts.js:1149 js/contacts.js:1158
+#: js/contacts.js:1193 js/contacts.js:1225 js/contacts.js:1237
+#: js/contacts.js:1260 js/contacts.js:1522
msgid "Error"
msgstr ""
-#: js/contacts.js:364
-msgid "Are you sure you want to delete this contact?"
+#: js/contacts.js:389 lib/search.php:15
+msgid "Contact"
+msgstr ""
+
+#: js/contacts.js:389
+msgid "New"
msgstr ""
-#: js/contacts.js:364
-msgid "Warning"
+#: js/contacts.js:389
+msgid "New Contact"
msgstr ""
-#: js/contacts.js:605
+#: js/contacts.js:691
msgid "This property has to be non-empty."
msgstr ""
-#: js/contacts.js:631
+#: js/contacts.js:717
msgid "Couldn't serialize elements."
msgstr ""
-#: js/contacts.js:747 js/contacts.js:765
+#: js/contacts.js:826 js/contacts.js:844
msgid ""
"'deleteProperty' called without type argument. Please report at bugs."
"owncloud.org"
msgstr ""
-#: js/contacts.js:781
+#: js/contacts.js:860
msgid "Edit name"
msgstr ""
-#: js/contacts.js:1056
+#: js/contacts.js:1141
msgid "No files selected for upload."
msgstr ""
-#: js/contacts.js:1064 js/contacts.js:1449 js/contacts.js:1634
+#: js/contacts.js:1149
msgid ""
"The file you are trying to upload exceed the maximum size for file uploads "
"on this server."
msgstr ""
-#: js/contacts.js:1119
-msgid "Select photo"
-msgstr ""
-
-#: js/contacts.js:1257 js/contacts.js:1290
+#: js/contacts.js:1314 js/contacts.js:1348
msgid "Select type"
msgstr ""
-#: js/contacts.js:1305 templates/part.importaddressbook.php:25
-msgid "Drop a VCF file to import contacts."
-msgstr ""
-
-#: js/contacts.js:1475
-msgid "Import done. Success/Failure: "
-msgstr ""
-
-#: js/contacts.js:1476
-msgid "OK"
-msgstr ""
-
-#: js/contacts.js:1494
-msgid "Displayname cannot be empty."
-msgstr ""
-
-#: js/contacts.js:1634
-msgid "Upload too large"
-msgstr ""
-
-#: js/contacts.js:1638
-msgid "Only image files can be used as profile picture."
-msgstr ""
-
-#: js/contacts.js:1638
-msgid "Wrong file type"
-msgstr ""
-
-#: js/contacts.js:1644
-msgid ""
-"Your browser doesn't support AJAX upload. Please click on the profile "
-"picture to select a photo to upload."
-msgstr ""
-
#: js/loader.js:49
msgid "Result: "
msgstr ""
@@ -347,91 +297,196 @@ msgstr ""
msgid " failed."
msgstr ""
-#: lib/app.php:30
+#: lib/app.php:29
msgid "Addressbook not found."
msgstr ""
-#: lib/app.php:34
+#: lib/app.php:33
msgid "This is not your addressbook."
msgstr ""
-#: lib/app.php:45
+#: lib/app.php:44
msgid "Contact could not be found."
msgstr ""
-#: lib/app.php:101 templates/part.contact.php:109
+#: lib/app.php:100 templates/part.contact.php:116
msgid "Address"
msgstr ""
-#: lib/app.php:102
+#: lib/app.php:101
msgid "Telephone"
msgstr ""
-#: lib/app.php:103 templates/part.contact.php:108
+#: lib/app.php:102 templates/part.contact.php:115
msgid "Email"
msgstr ""
-#: lib/app.php:104 templates/part.contact.php:33 templates/part.contact.php:34
-#: templates/part.contact.php:104
+#: lib/app.php:103 templates/part.contact.php:38 templates/part.contact.php:39
+#: templates/part.contact.php:111
msgid "Organization"
msgstr ""
-#: lib/app.php:116 lib/app.php:123 lib/app.php:133
+#: lib/app.php:115 lib/app.php:122 lib/app.php:132 lib/app.php:183
msgid "Work"
msgstr ""
-#: lib/app.php:117 lib/app.php:121 lib/app.php:134
+#: lib/app.php:116 lib/app.php:120 lib/app.php:133
msgid "Home"
msgstr ""
-#: lib/app.php:122
+#: lib/app.php:121
msgid "Mobile"
msgstr ""
-#: lib/app.php:124
+#: lib/app.php:123
msgid "Text"
msgstr ""
-#: lib/app.php:125
+#: lib/app.php:124
msgid "Voice"
msgstr ""
-#: lib/app.php:126
+#: lib/app.php:125
msgid "Message"
msgstr ""
-#: lib/app.php:127
+#: lib/app.php:126
msgid "Fax"
msgstr ""
-#: lib/app.php:128
+#: lib/app.php:127
msgid "Video"
msgstr ""
-#: lib/app.php:129
+#: lib/app.php:128
msgid "Pager"
msgstr ""
-#: lib/app.php:135
+#: lib/app.php:134
msgid "Internet"
msgstr ""
-#: lib/hooks.php:79
-msgid "{name}'s Birthday"
+#: lib/app.php:169 templates/part.contact.php:44
+#: templates/part.contact.php:113
+msgid "Birthday"
msgstr ""
-#: lib/search.php:22
-msgid "Contact"
+#: lib/app.php:170
+msgid "Business"
+msgstr ""
+
+#: lib/app.php:171
+msgid "Call"
+msgstr ""
+
+#: lib/app.php:172
+msgid "Clients"
msgstr ""
-#: templates/index.php:13
+#: lib/app.php:173
+msgid "Deliverer"
+msgstr ""
+
+#: lib/app.php:174
+msgid "Holidays"
+msgstr ""
+
+#: lib/app.php:175
+msgid "Ideas"
+msgstr ""
+
+#: lib/app.php:176
+msgid "Journey"
+msgstr ""
+
+#: lib/app.php:177
+msgid "Jubilee"
+msgstr ""
+
+#: lib/app.php:178
+msgid "Meeting"
+msgstr ""
+
+#: lib/app.php:179
+msgid "Other"
+msgstr ""
+
+#: lib/app.php:180
+msgid "Personal"
+msgstr ""
+
+#: lib/app.php:181
+msgid "Projects"
+msgstr ""
+
+#: lib/app.php:182
+msgid "Questions"
+msgstr ""
+
+#: lib/hooks.php:102
+msgid "{name}'s Birthday"
+msgstr ""
+
+#: templates/index.php:15
msgid "Add Contact"
msgstr ""
-#: templates/index.php:14
+#: templates/index.php:16 templates/index.php:18 templates/part.import.php:17
+msgid "Import"
+msgstr ""
+
+#: templates/index.php:20
msgid "Addressbooks"
msgstr ""
+#: templates/index.php:37 templates/part.import.php:24
+msgid "Close"
+msgstr ""
+
+#: templates/index.php:39
+msgid "Keyboard shortcuts"
+msgstr ""
+
+#: templates/index.php:41
+msgid "Navigation"
+msgstr ""
+
+#: templates/index.php:44
+msgid "Next contact in list"
+msgstr ""
+
+#: templates/index.php:46
+msgid "Previous contact in list"
+msgstr ""
+
+#: templates/index.php:48
+msgid "Expand/collapse current addressbook"
+msgstr ""
+
+#: templates/index.php:50
+msgid "Next/previous addressbook"
+msgstr ""
+
+#: templates/index.php:54
+msgid "Actions"
+msgstr ""
+
+#: templates/index.php:57
+msgid "Refresh contacts list"
+msgstr ""
+
+#: templates/index.php:59
+msgid "Add new contact"
+msgstr ""
+
+#: templates/index.php:61
+msgid "Add new addressbook"
+msgstr ""
+
+#: templates/index.php:63
+msgid "Delete current contact"
+msgstr ""
+
#: templates/part.chooseaddressbook.php:1
msgid "Configure Address Books"
msgstr ""
@@ -440,11 +495,7 @@ msgstr ""
msgid "New Address Book"
msgstr ""
-#: templates/part.chooseaddressbook.php:17
-msgid "Import from VCF"
-msgstr ""
-
-#: templates/part.chooseaddressbook.php:22
+#: templates/part.chooseaddressbook.php:21
#: templates/part.chooseaddressbook.rowfields.php:8
msgid "CardDav Link"
msgstr ""
@@ -458,184 +509,193 @@ msgid "Edit"
msgstr ""
#: templates/part.chooseaddressbook.rowfields.php:17
-#: templates/part.contact.php:34 templates/part.contact.php:36
-#: templates/part.contact.php:38 templates/part.contact.php:42
+#: templates/part.contact.php:39 templates/part.contact.php:41
+#: templates/part.contact.php:43 templates/part.contact.php:45
+#: templates/part.contact.php:49
msgid "Delete"
msgstr ""
-#: templates/part.contact.php:12
-msgid "Download contact"
+#: templates/part.contact.php:16
+msgid "Drop photo to upload"
msgstr ""
-#: templates/part.contact.php:13
-msgid "Delete contact"
+#: templates/part.contact.php:18
+msgid "Delete current photo"
msgstr ""
#: templates/part.contact.php:19
-msgid "Drop photo to upload"
+msgid "Edit current photo"
msgstr ""
-#: templates/part.contact.php:29
+#: templates/part.contact.php:20
+msgid "Upload new photo"
+msgstr ""
+
+#: templates/part.contact.php:21
+msgid "Select photo from ownCloud"
+msgstr ""
+
+#: templates/part.contact.php:34
msgid "Format custom, Short name, Full name, Reverse or Reverse with comma"
msgstr ""
-#: templates/part.contact.php:30
+#: templates/part.contact.php:35
msgid "Edit name details"
msgstr ""
-#: templates/part.contact.php:35 templates/part.contact.php:105
+#: templates/part.contact.php:40 templates/part.contact.php:112
msgid "Nickname"
msgstr ""
-#: templates/part.contact.php:36
+#: templates/part.contact.php:41
msgid "Enter nickname"
msgstr ""
-#: templates/part.contact.php:37 templates/part.contact.php:106
-msgid "Birthday"
+#: templates/part.contact.php:42 templates/part.contact.php:118
+msgid "Web site"
msgstr ""
-#: templates/part.contact.php:38
+#: templates/part.contact.php:43
+msgid "http://www.somesite.com"
+msgstr ""
+
+#: templates/part.contact.php:43
+msgid "Go to web site"
+msgstr ""
+
+#: templates/part.contact.php:45
msgid "dd-mm-yyyy"
msgstr ""
-#: templates/part.contact.php:39 templates/part.contact.php:111
+#: templates/part.contact.php:46 templates/part.contact.php:119
msgid "Groups"
msgstr ""
-#: templates/part.contact.php:41
+#: templates/part.contact.php:48
msgid "Separate groups with commas"
msgstr ""
-#: templates/part.contact.php:42
+#: templates/part.contact.php:49
msgid "Edit groups"
msgstr ""
-#: templates/part.contact.php:55 templates/part.contact.php:69
+#: templates/part.contact.php:62 templates/part.contact.php:76
msgid "Preferred"
msgstr ""
-#: templates/part.contact.php:56
+#: templates/part.contact.php:63
msgid "Please specify a valid email address."
msgstr ""
-#: templates/part.contact.php:56
+#: templates/part.contact.php:63
msgid "Enter email address"
msgstr ""
-#: templates/part.contact.php:60
+#: templates/part.contact.php:67
msgid "Mail to address"
msgstr ""
-#: templates/part.contact.php:61
+#: templates/part.contact.php:68
msgid "Delete email address"
msgstr ""
-#: templates/part.contact.php:70
+#: templates/part.contact.php:77
msgid "Enter phone number"
msgstr ""
-#: templates/part.contact.php:74
+#: templates/part.contact.php:81
msgid "Delete phone number"
msgstr ""
-#: templates/part.contact.php:84
+#: templates/part.contact.php:91
msgid "View on map"
msgstr ""
-#: templates/part.contact.php:84
+#: templates/part.contact.php:91
msgid "Edit address details"
msgstr ""
-#: templates/part.contact.php:95
+#: templates/part.contact.php:102
msgid "Add notes here."
msgstr ""
-#: templates/part.contact.php:101
+#: templates/part.contact.php:109
msgid "Add field"
msgstr ""
-#: templates/part.contact.php:103
-msgid "Profile picture"
-msgstr ""
-
-#: templates/part.contact.php:107
+#: templates/part.contact.php:114
msgid "Phone"
msgstr ""
-#: templates/part.contact.php:110
+#: templates/part.contact.php:117
msgid "Note"
msgstr ""
-#: templates/part.contactphoto.php:8
-msgid "Delete current photo"
-msgstr ""
-
-#: templates/part.contactphoto.php:9
-msgid "Edit current photo"
-msgstr ""
-
-#: templates/part.contactphoto.php:10
-msgid "Upload new photo"
+#: templates/part.contact.php:122
+msgid "Download contact"
msgstr ""
-#: templates/part.contactphoto.php:11
-msgid "Select photo from ownCloud"
+#: templates/part.contact.php:123
+msgid "Delete contact"
msgstr ""
-#: templates/part.cropphoto.php:64
+#: templates/part.cropphoto.php:65
msgid "The temporary image has been removed from cache."
msgstr ""
-#: templates/part.edit_address_dialog.php:9
+#: templates/part.edit_address_dialog.php:6
msgid "Edit address"
msgstr ""
-#: templates/part.edit_address_dialog.php:14
+#: templates/part.edit_address_dialog.php:10
msgid "Type"
msgstr ""
-#: templates/part.edit_address_dialog.php:22
-#: templates/part.edit_address_dialog.php:25
+#: templates/part.edit_address_dialog.php:18
+#: templates/part.edit_address_dialog.php:21
msgid "PO Box"
msgstr ""
-#: templates/part.edit_address_dialog.php:29
-#: templates/part.edit_address_dialog.php:32
+#: templates/part.edit_address_dialog.php:24
+msgid "Street address"
+msgstr ""
+
+#: templates/part.edit_address_dialog.php:27
+msgid "Street and number"
+msgstr ""
+
+#: templates/part.edit_address_dialog.php:30
msgid "Extended"
msgstr ""
-#: templates/part.edit_address_dialog.php:35
-#: templates/part.edit_address_dialog.php:38
-msgid "Street"
+#: templates/part.edit_address_dialog.php:33
+msgid "Apartment number etc."
msgstr ""
-#: templates/part.edit_address_dialog.php:41
-#: templates/part.edit_address_dialog.php:44
+#: templates/part.edit_address_dialog.php:36
+#: templates/part.edit_address_dialog.php:39
msgid "City"
msgstr ""
-#: templates/part.edit_address_dialog.php:47
-#: templates/part.edit_address_dialog.php:50
+#: templates/part.edit_address_dialog.php:42
msgid "Region"
msgstr ""
-#: templates/part.edit_address_dialog.php:53
-#: templates/part.edit_address_dialog.php:56
-msgid "Zipcode"
+#: templates/part.edit_address_dialog.php:45
+msgid "E.g. state or province"
msgstr ""
-#: templates/part.edit_address_dialog.php:59
-#: templates/part.edit_address_dialog.php:62
-msgid "Country"
+#: templates/part.edit_address_dialog.php:48
+msgid "Zipcode"
msgstr ""
-#: templates/part.edit_categories_dialog.php:4
-msgid "Edit categories"
+#: templates/part.edit_address_dialog.php:51
+msgid "Postal code"
msgstr ""
-#: templates/part.edit_categories_dialog.php:14
-msgid "Add"
+#: templates/part.edit_address_dialog.php:54
+#: templates/part.edit_address_dialog.php:57
+msgid "Country"
msgstr ""
#: templates/part.edit_name_dialog.php:16
@@ -743,7 +803,6 @@ msgid "Submit"
msgstr ""
#: templates/part.editaddressbook.php:30
-#: templates/part.importaddressbook.php:34
msgid "Cancel"
msgstr ""
@@ -763,32 +822,10 @@ msgstr ""
msgid "Name of new addressbook"
msgstr ""
-#: templates/part.import.php:17
-msgid "Import"
-msgstr ""
-
#: templates/part.import.php:20
msgid "Importing contacts"
msgstr ""
-#: templates/part.import.php:24
-msgid "Close"
-msgstr ""
-
-#: templates/part.importaddressbook.php:12
-msgid ""
-"Currently this import function doesn't work while encryption is enabled.<br /"
-">Please upload your VCF file with the file manager and click on it to import."
-msgstr ""
-
-#: templates/part.importaddressbook.php:16
-msgid "Select address book to import to:"
-msgstr ""
-
-#: templates/part.importaddressbook.php:26
-msgid "Select from HD"
-msgstr ""
-
#: templates/part.no_contacts.php:2
msgid "You have no contacts in your addressbook."
msgstr ""
@@ -801,6 +838,18 @@ msgstr ""
msgid "Configure addressbooks"
msgstr ""
+#: templates/part.selectaddressbook.php:1
+msgid "Select Address Books"
+msgstr ""
+
+#: templates/part.selectaddressbook.php:20
+msgid "Enter name"
+msgstr ""
+
+#: templates/part.selectaddressbook.php:22
+msgid "Enter description"
+msgstr ""
+
#: templates/settings.php:4
msgid "CardDAV syncing addresses"
msgstr ""
@@ -816,3 +865,7 @@ msgstr ""
#: templates/settings.php:8
msgid "iOS/OS X"
msgstr ""
+
+#: templates/settings.php:10
+msgid "Read only vCard directory link(s)"
+msgstr ""
diff --git a/l10n/templates/core.pot b/l10n/templates/core.pot
index 46ee521dfa0..4f0a33760af 100644
--- a/l10n/templates/core.pot
+++ b/l10n/templates/core.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-06-06 00:12+0200\n"
+"POT-Creation-Date: 2012-07-25 22:14+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -33,51 +33,51 @@ msgstr ""
msgid "ui-datepicker-group';if(i[1]>1)switch(G){case 0:y+="
msgstr ""
-#: js/js.js:520
+#: js/js.js:519
msgid "January"
msgstr ""
-#: js/js.js:520
+#: js/js.js:519
msgid "February"
msgstr ""
-#: js/js.js:520
+#: js/js.js:519
msgid "March"
msgstr ""
-#: js/js.js:520
+#: js/js.js:519
msgid "April"
msgstr ""
-#: js/js.js:520
+#: js/js.js:519
msgid "May"
msgstr ""
-#: js/js.js:520
+#: js/js.js:519
msgid "June"
msgstr ""
-#: js/js.js:521
+#: js/js.js:520
msgid "July"
msgstr ""
-#: js/js.js:521
+#: js/js.js:520
msgid "August"
msgstr ""
-#: js/js.js:521
+#: js/js.js:520
msgid "September"
msgstr ""
-#: js/js.js:521
+#: js/js.js:520
msgid "October"
msgstr ""
-#: js/js.js:521
+#: js/js.js:520
msgid "November"
msgstr ""
-#: js/js.js:521
+#: js/js.js:520
msgid "December"
msgstr ""
@@ -106,10 +106,6 @@ msgid "Error"
msgstr ""
#: lostpassword/index.php:26
-msgid "Owncloud password reset"
-msgstr ""
-
-#: lostpassword/index.php:27
msgid "ownCloud password reset"
msgstr ""
@@ -239,11 +235,11 @@ msgstr ""
msgid "web services under your control"
msgstr ""
-#: templates/layout.user.php:41
+#: templates/layout.user.php:49
msgid "Log out"
msgstr ""
-#: templates/layout.user.php:53 templates/layout.user.php:54
+#: templates/layout.user.php:64 templates/layout.user.php:65
msgid "Settings"
msgstr ""
diff --git a/l10n/templates/files.pot b/l10n/templates/files.pot
index d38e5942c2e..d3f9a451e48 100644
--- a/l10n/templates/files.pot
+++ b/l10n/templates/files.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-06-06 00:12+0200\n"
+"POT-Creation-Date: 2012-07-25 22:14+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,33 +17,33 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
-#: ajax/upload.php:19
+#: ajax/upload.php:20
msgid "There is no error, the file uploaded with success"
msgstr ""
-#: ajax/upload.php:20
+#: ajax/upload.php:21
msgid "The uploaded file exceeds the upload_max_filesize directive in php.ini"
msgstr ""
-#: ajax/upload.php:21
+#: ajax/upload.php:22
msgid ""
"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in "
"the HTML form"
msgstr ""
-#: ajax/upload.php:22
+#: ajax/upload.php:23
msgid "The uploaded file was only partially uploaded"
msgstr ""
-#: ajax/upload.php:23
+#: ajax/upload.php:24
msgid "No file was uploaded"
msgstr ""
-#: ajax/upload.php:24
+#: ajax/upload.php:25
msgid "Missing a temporary folder"
msgstr ""
-#: ajax/upload.php:25
+#: ajax/upload.php:26
msgid "Failed to write to disk"
msgstr ""
@@ -51,6 +51,14 @@ msgstr ""
msgid "Files"
msgstr ""
+#: js/fileactions.js:95
+msgid "Unshare"
+msgstr ""
+
+#: js/fileactions.js:97 templates/index.php:56
+msgid "Delete"
+msgstr ""
+
#: js/filelist.js:186
msgid "undo deletion"
msgstr ""
@@ -79,27 +87,27 @@ msgstr ""
msgid "Invalid name, '/' is not allowed."
msgstr ""
-#: js/files.js:626 templates/index.php:55
+#: js/files.js:631 templates/index.php:55
msgid "Size"
msgstr ""
-#: js/files.js:627 templates/index.php:56
+#: js/files.js:632 templates/index.php:56
msgid "Modified"
msgstr ""
-#: js/files.js:654
+#: js/files.js:659
msgid "folder"
msgstr ""
-#: js/files.js:656
+#: js/files.js:661
msgid "folders"
msgstr ""
-#: js/files.js:664
+#: js/files.js:669
msgid "file"
msgstr ""
-#: js/files.js:666
+#: js/files.js:671
msgid "files"
msgstr ""
@@ -171,10 +179,6 @@ msgstr ""
msgid "Download"
msgstr ""
-#: templates/index.php:56
-msgid "Delete"
-msgstr ""
-
#: templates/index.php:64
msgid "Upload too large"
msgstr ""
diff --git a/l10n/templates/gallery.pot b/l10n/templates/gallery.pot
index 0cf2a7db970..658db4f6e41 100644
--- a/l10n/templates/gallery.pot
+++ b/l10n/templates/gallery.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-06-06 00:12+0200\n"
+"POT-Creation-Date: 2012-07-25 22:14+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,60 +17,24 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
-#: appinfo/app.php:37
+#: appinfo/app.php:39
msgid "Pictures"
msgstr ""
-#: js/album_cover.js:44
+#: js/pictures.js:12
msgid "Share gallery"
msgstr ""
-#: js/album_cover.js:64 js/album_cover.js:100 js/album_cover.js:133
+#: js/pictures.js:32
msgid "Error: "
msgstr ""
-#: js/album_cover.js:64 js/album_cover.js:100
+#: js/pictures.js:32
msgid "Internal error"
msgstr ""
-#: js/album_cover.js:114
-msgid "Scanning root"
-msgstr ""
-
-#: js/album_cover.js:115
-msgid "Default order"
-msgstr ""
-
-#: js/album_cover.js:116
-msgid "Ascending"
-msgstr ""
-
-#: js/album_cover.js:116
-msgid "Descending"
-msgstr ""
-
-#: js/album_cover.js:117 templates/index.php:19
-msgid "Settings"
-msgstr ""
-
-#: js/album_cover.js:122
-msgid "Scanning root cannot be empty"
-msgstr ""
-
-#: js/album_cover.js:122 js/album_cover.js:133
-msgid "Error"
-msgstr ""
-
-#: templates/index.php:16
-msgid "Rescan"
-msgstr ""
-
-#: templates/index.php:17
-msgid "Stop"
-msgstr ""
-
-#: templates/index.php:18
-msgid "Share"
+#: templates/index.php:27
+msgid "Slideshow"
msgstr ""
#: templates/view_album.php:19
diff --git a/l10n/templates/media.pot b/l10n/templates/media.pot
index b722f87d435..adbef7481b4 100644
--- a/l10n/templates/media.pot
+++ b/l10n/templates/media.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-06-06 00:12+0200\n"
+"POT-Creation-Date: 2012-07-25 22:14+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
-#: appinfo/app.php:32 templates/player.php:8
+#: appinfo/app.php:45 templates/player.php:8
msgid "Music"
msgstr ""
diff --git a/l10n/templates/settings.pot b/l10n/templates/settings.pot
index 85b693c5c01..5b9ddb82627 100644
--- a/l10n/templates/settings.pot
+++ b/l10n/templates/settings.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-06-06 00:12+0200\n"
+"POT-Creation-Date: 2012-07-25 22:14+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -25,15 +25,15 @@ msgstr ""
msgid "Invalid email"
msgstr ""
-#: ajax/openid.php:15
+#: ajax/openid.php:16
msgid "OpenID Changed"
msgstr ""
-#: ajax/openid.php:17 ajax/setlanguage.php:19 ajax/setlanguage.php:22
+#: ajax/openid.php:18 ajax/setlanguage.php:20 ajax/setlanguage.php:23
msgid "Invalid request"
msgstr ""
-#: ajax/setlanguage.php:17
+#: ajax/setlanguage.php:18
msgid "Language changed"
msgstr ""
@@ -49,35 +49,39 @@ msgstr ""
msgid "Saving..."
msgstr ""
-#: personal.php:40 personal.php:41
+#: personal.php:41 personal.php:42
msgid "__language_name__"
msgstr ""
-#: templates/admin.php:13
+#: templates/admin.php:14
+msgid "Security Warning"
+msgstr ""
+
+#: templates/admin.php:28
msgid "Log"
msgstr ""
-#: templates/admin.php:40
+#: templates/admin.php:55
msgid "More"
msgstr ""
-#: templates/apps.php:8
+#: templates/apps.php:10
msgid "Add your App"
msgstr ""
-#: templates/apps.php:22
+#: templates/apps.php:24
msgid "Select an App"
msgstr ""
-#: templates/apps.php:25
+#: templates/apps.php:27
msgid "See application page at apps.owncloud.com"
msgstr ""
-#: templates/apps.php:26
+#: templates/apps.php:28
msgid "-licensed"
msgstr ""
-#: templates/apps.php:26
+#: templates/apps.php:28
msgid "by"
msgstr ""
@@ -169,34 +173,34 @@ msgstr ""
msgid "use this address to connect to your ownCloud in your file manager"
msgstr ""
-#: templates/users.php:15 templates/users.php:44
+#: templates/users.php:15 templates/users.php:60
msgid "Name"
msgstr ""
-#: templates/users.php:16 templates/users.php:45
+#: templates/users.php:17 templates/users.php:61
msgid "Password"
msgstr ""
-#: templates/users.php:17 templates/users.php:46 templates/users.php:60
+#: templates/users.php:19 templates/users.php:62 templates/users.php:78
msgid "Groups"
msgstr ""
-#: templates/users.php:22
+#: templates/users.php:25
msgid "Create"
msgstr ""
-#: templates/users.php:25
+#: templates/users.php:28
msgid "Default Quota"
msgstr ""
-#: templates/users.php:35 templates/users.php:74
+#: templates/users.php:47 templates/users.php:103
msgid "Other"
msgstr ""
-#: templates/users.php:47
+#: templates/users.php:63
msgid "Quota"
msgstr ""
-#: templates/users.php:80
+#: templates/users.php:110
msgid "Delete"
msgstr ""
diff --git a/lib/MDB2/Driver/sqlite3.php b/lib/MDB2/Driver/sqlite3.php
index 25927aff637..6bfccadad9a 100644
--- a/lib/MDB2/Driver/sqlite3.php
+++ b/lib/MDB2/Driver/sqlite3.php
@@ -1221,7 +1221,7 @@ class MDB2_Statement_sqlite3 extends MDB2_Statement_Common
return $affected_rows;
}
- $result =& $this->db->_wrapResult($result, $this->result_types,
+ $result = $this->db->_wrapResult($result, $this->result_types,
$result_class, $result_wrap_class, $this->limit, $this->offset);
$this->db->debug($this->query, 'execute', array('is_manip' => $this->is_manip, 'when' => 'post', 'result' => $result));
return $result;
diff --git a/lib/app.php b/lib/app.php
index 4c2c43ec26b..56132c08671 100755
--- a/lib/app.php
+++ b/lib/app.php
@@ -27,7 +27,6 @@
* upgrading and removing apps.
*/
class OC_App{
- static private $init = false;
static private $activeapp = '';
static private $navigation = array();
static private $settingsForms = array();
@@ -196,7 +195,7 @@ class OC_App{
// check if the app is compatible with this version of ownCloud
$info=OC_App::getAppInfo($app);
$version=OC_Util::getVersion();
- if(!isset($info['require']) or ($version[0]>$info['require'])){
+ if(!isset($info['require']) or ($version[0]>$info['require'])){
OC_Log::write('core','App "'.$info['name'].'" can\'t be installed because it is not compatible with this version of ownCloud',OC_Log::ERROR);
return false;
}else{
@@ -332,8 +331,8 @@ class OC_App{
}
/**
- * Get the path where to install apps
- */
+ * Get the path where to install apps
+ */
public static function getInstallPath() {
if(OC_Config::getValue('appstoreenabled', true)==false) {
return false;
@@ -608,7 +607,7 @@ class OC_App{
//set remote/public handelers
$appData=self::getAppInfo($appid);
foreach($appData['remote'] as $name=>$path){
- OCP\CONFIG::setAppValue('core', 'remote_'.$name, $path);
+ OCP\CONFIG::setAppValue('core', 'remote_'.$name, $appid.'/'.$path);
}
foreach($appData['public'] as $name=>$path){
OCP\CONFIG::setAppValue('core', 'public_'.$name, $appid.'/'.$path);
diff --git a/lib/archive/zip.php b/lib/archive/zip.php
index 6631a649b16..b2d6674d639 100644
--- a/lib/archive/zip.php
+++ b/lib/archive/zip.php
@@ -11,7 +11,6 @@ class OC_Archive_ZIP extends OC_Archive{
* @var ZipArchive zip
*/
private $zip=null;
- private $success=false;
private $path;
function __construct($source){
@@ -74,8 +73,7 @@ class OC_Archive_ZIP extends OC_Archive{
* @return int
*/
function mtime($path){
- $stat=$this->zip->statName($path);
- return $stat['mtime'];
+ return filemtime($this->path);
}
/**
* get the files in a folder
diff --git a/lib/base.php b/lib/base.php
index fe69ad70c0f..888dc265d64 100644
--- a/lib/base.php
+++ b/lib/base.php
@@ -83,6 +83,9 @@ class OC{
elseif(strpos($className,'OCP\\')===0){
require_once 'public/'.strtolower(str_replace('\\','/',substr($className,3)) . '.php');
}
+ elseif(strpos($className,'OCA\\')===0){
+ require_once 'apps/'.strtolower(str_replace('\\','/',substr($className,3)) . '.php');
+ }
elseif(strpos($className,'Sabre_')===0) {
require_once str_replace('_','/',$className) . '.php';
}
@@ -345,7 +348,7 @@ class OC{
$_SESSION['user_id'] = '';
}
- OC_User::useBackend( OC_Config::getValue( "userbackend", "database" ));
+ OC_User::useBackend(new OC_User_Database());
OC_Group::useBackend(new OC_Group_Database());
// Load Apps
diff --git a/lib/cache.php b/lib/cache.php
index 1f269174fad..55f189a5da8 100644
--- a/lib/cache.php
+++ b/lib/cache.php
@@ -7,48 +7,96 @@
*/
class OC_Cache {
+ /**
+ * @var OC_Cache $user_cache
+ */
static protected $user_cache;
+ /**
+ * @var OC_Cache $global_cache
+ */
static protected $global_cache;
+ /**
+ * @var OC_Cache $global_cache_fast
+ */
+ static protected $global_cache_fast;
+ /**
+ * @var OC_Cache $user_cache_fast
+ */
+ static protected $user_cache_fast;
+ static protected $isFast=null;
- static public function getGlobalCache() {
+ /**
+ * get the global cache
+ * @return OC_Cache
+ */
+ static public function getGlobalCache($fast=false) {
if (!self::$global_cache) {
- $fast_cache = null;
- if (!$fast_cache && function_exists('xcache_set')) {
- $fast_cache = new OC_Cache_XCache(true);
+ self::$global_cache_fast = null;
+ if (!self::$global_cache_fast && function_exists('xcache_set')) {
+ self::$global_cache_fast = new OC_Cache_XCache(true);
}
- if (!$fast_cache && function_exists('apc_store')) {
- $fast_cache = new OC_Cache_APC(true);
+ if (!self::$global_cache_fast && function_exists('apc_store')) {
+ self::$global_cache_fast = new OC_Cache_APC(true);
}
+
self::$global_cache = new OC_Cache_FileGlobal();
- if ($fast_cache) {
- self::$global_cache = new OC_Cache_Broker($fast_cache, self::$global_cache);
+ if (self::$global_cache_fast) {
+ self::$global_cache = new OC_Cache_Broker(self::$global_cache_fast, self::$global_cache);
+ }
+ }
+ if($fast){
+ if(self::$global_cache_fast){
+ return self::$global_cache_fast;
+ }else{
+ return false;
}
}
return self::$global_cache;
}
- static public function getUserCache() {
+ /**
+ * get the user cache
+ * @return OC_Cache
+ */
+ static public function getUserCache($fast=false) {
if (!self::$user_cache) {
- $fast_cache = null;
- if (!$fast_cache && function_exists('xcache_set')) {
- $fast_cache = new OC_Cache_XCache();
+ self::$user_cache_fast = null;
+ if (!self::$user_cache_fast && function_exists('xcache_set')) {
+ self::$user_cache_fast = new OC_Cache_XCache();
}
- if (!$fast_cache && function_exists('apc_store')) {
- $fast_cache = new OC_Cache_APC();
+ if (!self::$user_cache_fast && function_exists('apc_store')) {
+ self::$user_cache_fast = new OC_Cache_APC();
}
+
self::$user_cache = new OC_Cache_File();
- if ($fast_cache) {
- self::$user_cache = new OC_Cache_Broker($fast_cache, self::$user_cache);
+ if (self::$user_cache_fast) {
+ self::$user_cache = new OC_Cache_Broker(self::$user_cache_fast, self::$user_cache);
+ }
+ }
+
+ if($fast){
+ if(self::$user_cache_fast){
+ return self::$user_cache_fast;
+ }else{
+ return false;
}
}
return self::$user_cache;
}
+ /**
+ * get a value from the user cache
+ * @return mixed
+ */
static public function get($key) {
$user_cache = self::getUserCache();
return $user_cache->get($key);
}
+ /**
+ * set a value in the user cache
+ * @return bool
+ */
static public function set($key, $value, $ttl=0) {
if (empty($key)) {
return false;
@@ -57,19 +105,43 @@ class OC_Cache {
return $user_cache->set($key, $value, $ttl);
}
+ /**
+ * check if a value is set in the user cache
+ * @return bool
+ */
static public function hasKey($key) {
$user_cache = self::getUserCache();
return $user_cache->hasKey($key);
}
+ /**
+ * remove an item from the user cache
+ * @return bool
+ */
static public function remove($key) {
$user_cache = self::getUserCache();
return $user_cache->remove($key);
}
- static public function clear() {
+ /**
+ * clear the user cache of all entries starting with a prefix
+ * @param string prefix (optional)
+ * @return bool
+ */
+ static public function clear($prefix='') {
$user_cache = self::getUserCache();
- return $user_cache->clear();
+ return $user_cache->clear($prefix);
+ }
+
+ /**
+ * check if a fast memory based cache is available
+ * @return true
+ */
+ static public function isFast() {
+ if(is_null(self::$isFast)){
+ self::$isFast=function_exists('xcache_set') || function_exists('apc_store');
+ }
+ return self::$isFast;
}
}
diff --git a/lib/cache/apc.php b/lib/cache/apc.php
index 6cf47d0c158..c192fe2f196 100644
--- a/lib/cache/apc.php
+++ b/lib/cache/apc.php
@@ -43,14 +43,15 @@ class OC_Cache_APC {
return apc_delete($this->getNamespace().$key);
}
- public function clear(){
- $ns = $this->getNamespace();
+ public function clear($prefix=''){
+ $ns = $this->getNamespace().$prefix;
$cache = apc_cache_info('user');
foreach($cache['cache_list'] as $entry) {
if (strpos($entry['info'], $ns) === 0) {
apc_delete($entry['info']);
}
}
+ return true;
}
}
if(!function_exists('apc_exists')) {
diff --git a/lib/cache/broker.php b/lib/cache/broker.php
index 931d0dd407e..c2aceabaf53 100644
--- a/lib/cache/broker.php
+++ b/lib/cache/broker.php
@@ -46,8 +46,8 @@ class OC_Cache_Broker {
return $this->slow_cache->remove($key);
}
- public function clear(){
- $this->fast_cache->clear();
- $this->slow_cache->clear();
+ public function clear($prefix=''){
+ $this->fast_cache->clear($prefix);
+ $this->slow_cache->clear($prefix);
}
}
diff --git a/lib/cache/file.php b/lib/cache/file.php
index 0b7d3e30508..562c3d17167 100644
--- a/lib/cache/file.php
+++ b/lib/cache/file.php
@@ -62,15 +62,16 @@ class OC_Cache_File{
return $storage->unlink($key);
}
- public function clear(){
+ public function clear($prefix=''){
$storage = $this->getStorage();
if($storage and $storage->is_dir('/')){
$dh=$storage->opendir('/');
while($file=readdir($dh)){
- if($file!='.' and $file!='..'){
+ if($file!='.' and $file!='..' and ($prefix==='' || strpos($file, $prefix) === 0)){
$storage->unlink('/'.$file);
}
}
}
+ return true;
}
}
diff --git a/lib/cache/xcache.php b/lib/cache/xcache.php
index bd55cee8f6b..951f9b47545 100644
--- a/lib/cache/xcache.php
+++ b/lib/cache/xcache.php
@@ -43,7 +43,8 @@ class OC_Cache_XCache {
return xcache_unset($this->getNamespace().$key);
}
- public function clear(){
- return xcache_unset_by_prefix($this->getNamespace());
+ public function clear($prefix=''){
+ xcache_unset_by_prefix($this->getNamespace().$prefix);
+ return true;
}
}
diff --git a/lib/connector/sabre/auth.php b/lib/connector/sabre/auth.php
index ee680391626..99f696e3a07 100644
--- a/lib/connector/sabre/auth.php
+++ b/lib/connector/sabre/auth.php
@@ -31,13 +31,18 @@ class OC_Connector_Sabre_Auth extends Sabre_DAV_Auth_Backend_AbstractBasic {
* @return bool
*/
protected function validateUserPass($username, $password){
- OC_Util::setUpFS();//login hooks may need early access to the filesystem
- if(OC_User::login($username,$password)){
- OC_Util::setUpFS($username);
+ if (OC_User::isLoggedIn()) {
+ OC_Util::setupFS($username);
return true;
- }
- else{
- return false;
+ } else {
+ OC_Util::setUpFS();//login hooks may need early access to the filesystem
+ if(OC_User::login($username,$password)){
+ OC_Util::setUpFS($username);
+ return true;
+ }
+ else{
+ return false;
+ }
}
}
}
diff --git a/lib/connector/sabre/client.php b/lib/connector/sabre/client.php
index 87f9d59b3ae..7e8f21264f9 100644
--- a/lib/connector/sabre/client.php
+++ b/lib/connector/sabre/client.php
@@ -68,18 +68,13 @@ class OC_Connector_Sabre_Client extends Sabre_DAV_Client {
// Automatically follow redirects
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
- CURLOPT_SSL_VERIFYPEER => true,
- //CURLOPT_SSL_VERIFYPEER => false,
);
-
+
if($this->trustedCertificates) {
$curlSettings[CURLOPT_CAINFO] = $this->trustedCertificates;
}
-
+
switch ($method) {
- case 'PUT':
- $curlSettings[CURLOPT_PUT] = true;
- break;
case 'HEAD' :
// do not read body with HEAD requests (this is neccessary because cURL does not ignore the body with HEAD
@@ -110,8 +105,15 @@ class OC_Connector_Sabre_Client extends Sabre_DAV_Client {
$curlSettings[CURLOPT_PROXY] = $this->proxy;
}
- if ($this->userName) {
- $curlSettings[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC | CURLAUTH_DIGEST;
+ if ($this->userName && $this->authType) {
+ $curlType = 0;
+ if ($this->authType & self::AUTH_BASIC) {
+ $curlType |= CURLAUTH_BASIC;
+ }
+ if ($this->authType & self::AUTH_DIGEST) {
+ $curlType |= CURLAUTH_DIGEST;
+ }
+ $curlSettings[CURLOPT_HTTPAUTH] = $curlType;
$curlSettings[CURLOPT_USERPWD] = $this->userName . ':' . $this->password;
}
@@ -167,5 +169,5 @@ class OC_Connector_Sabre_Client extends Sabre_DAV_Client {
return $response;
- }
+ }
} \ No newline at end of file
diff --git a/lib/connector/sabre/directory.php b/lib/connector/sabre/directory.php
index b75bb5c50f5..0842fc4fc65 100644
--- a/lib/connector/sabre/directory.php
+++ b/lib/connector/sabre/directory.php
@@ -26,17 +26,33 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa
/**
* Creates a new file in the directory
*
- * data is a readable stream resource
+ * Data will either be supplied as a stream resource, or in certain cases
+ * as a string. Keep in mind that you may have to support either.
+ *
+ * After succesful creation of the file, you may choose to return the ETag
+ * of the new file here.
+ *
+ * The returned ETag must be surrounded by double-quotes (The quotes should
+ * be part of the actual string).
+ *
+ * If you cannot accurately determine the ETag, you should not return it.
+ * If you don't store the file exactly as-is (you're transforming it
+ * somehow) you should also not return an ETag.
+ *
+ * This means that if a subsequent GET to this new file does not exactly
+ * return the same contents of what was submitted here, you are strongly
+ * recommended to omit the ETag.
*
* @param string $name Name of the file
- * @param resource $data Initial payload
- * @return void
+ * @param resource|string $data Initial payload
+ * @return null|string
*/
public function createFile($name, $data = null) {
$newPath = $this->path . '/' . $name;
OC_Filesystem::file_put_contents($newPath,$data);
+ return OC_Connector_Sabre_Node::getETagPropertyForFile($newPath);
}
/**
@@ -146,7 +162,7 @@ class OC_Connector_Sabre_Directory extends OC_Connector_Sabre_Node implements Sa
* @return array
*/
public function getQuotaInfo() {
- $rootInfo=OC_FileCache::get('');
+ $rootInfo=OC_FileCache_Cached::get('');
return array(
$rootInfo['size'],
OC_Filesystem::free_space()
diff --git a/lib/connector/sabre/file.php b/lib/connector/sabre/file.php
index e4dc8c93445..80f0a0ab4d8 100644
--- a/lib/connector/sabre/file.php
+++ b/lib/connector/sabre/file.php
@@ -26,13 +26,28 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D
/**
* Updates the data
*
+ * The data argument is a readable stream resource.
+ *
+ * After a succesful put operation, you may choose to return an ETag. The
+ * etag must always be surrounded by double-quotes. These quotes must
+ * appear in the actual string you're returning.
+ *
+ * Clients may use the ETag from a PUT request to later on make sure that
+ * when they update the file, the contents haven't changed in the mean
+ * time.
+ *
+ * If you don't plan to store the file byte-by-byte, and you return a
+ * different object on a subsequent GET you are strongly recommended to not
+ * return an ETag, and just return null.
+ *
* @param resource $data
- * @return void
+ * @return string|null
*/
public function put($data) {
OC_Filesystem::file_put_contents($this->path,$data);
+ return OC_Connector_Sabre_Node::getETagPropertyForFile($this->path);
}
/**
@@ -42,7 +57,7 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D
*/
public function get() {
- return OC_Filesystem::readfile($this->path);
+ return OC_Filesystem::fopen($this->path,'rb');
}
@@ -79,9 +94,11 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D
* @return mixed
*/
public function getETag() {
-
- return null;
-
+ $properties = $this->getProperties(array(self::GETETAG_PROPERTYNAME));
+ if (isset($properties[self::GETETAG_PROPERTYNAME])) {
+ return $properties[self::GETETAG_PROPERTYNAME];
+ }
+ return $this->getETagPropertyForFile($this->path);
}
/**
diff --git a/lib/connector/sabre/locks.php b/lib/connector/sabre/locks.php
index 94382e68a1a..e95dcf02d27 100644
--- a/lib/connector/sabre/locks.php
+++ b/lib/connector/sabre/locks.php
@@ -108,7 +108,7 @@ class OC_Connector_Sabre_Locks extends Sabre_DAV_Locks_Backend_Abstract {
$locks = $this->getLocks($uri,false);
$exists = false;
- foreach($locks as $k=>$lock) {
+ foreach($locks as $lock) {
if ($lock->token == $lockInfo->token) $exists = true;
}
diff --git a/lib/connector/sabre/node.php b/lib/connector/sabre/node.php
index be315a0ffd9..663970487fb 100644
--- a/lib/connector/sabre/node.php
+++ b/lib/connector/sabre/node.php
@@ -22,6 +22,7 @@
*/
abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IProperties {
+ const GETETAG_PROPERTYNAME = '{DAV:}getetag';
/**
* The path to the current node
@@ -141,6 +142,7 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr
public function updateProperties($properties) {
$existing = $this->getProperties(array());
foreach($properties as $propertyName => $propertyValue) {
+ $propertyName = preg_replace("/^{.*}/", "", $propertyName); // remove leading namespace from property name
// If it was null, we need to delete the property
if (is_null($propertyValue)) {
if(array_key_exists( $propertyName, $existing )){
@@ -178,7 +180,7 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr
* @param array $properties
* @return void
*/
- function getProperties($properties) {
+ public function getProperties($properties) {
if (is_null($this->property_cache)) {
$query = OC_DB::prepare( 'SELECT * FROM *PREFIX*properties WHERE userid = ? AND propertypath = ?' );
$result = $query->execute( array( OC_User::getUser(), $this->path ));
@@ -200,4 +202,29 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr
}
return $props;
}
+
+ /**
+ * Returns the ETag surrounded by double-quotes for this path.
+ * @param string $path Path of the file
+ * @return string|null Returns null if the ETag can not effectively be determined
+ */
+ static public function getETagPropertyForFile($path) {
+ $tag = OC_Filesystem::hash('md5', $path);
+ if (empty($tag)) {
+ return null;
+ }
+ $etag = '"'.$tag.'"';
+ $query = OC_DB::prepare( 'INSERT INTO *PREFIX*properties (userid,propertypath,propertyname,propertyvalue) VALUES(?,?,?,?)' );
+ $query->execute( array( OC_User::getUser(), $path, self::GETETAG_PROPERTYNAME, $etag ));
+ return $etag;
+ }
+
+ /**
+ * Remove the ETag from the cache.
+ * @param string $path Path of the file
+ */
+ static public function removeETagPropertyForFile($path) {
+ $query = OC_DB::prepare( 'DELETE FROM *PREFIX*properties WHERE userid = ? AND propertypath = ? AND propertyname = ?' );
+ $query->execute( array( OC_User::getUser(), $path, self::GETETAG_PROPERTYNAME ));
+ }
}
diff --git a/lib/db.php b/lib/db.php
index 2a06d72ea32..6971fe4a583 100644
--- a/lib/db.php
+++ b/lib/db.php
@@ -33,8 +33,6 @@ class OC_DB {
static private $MDB2=false;
static private $PDO=false;
static private $schema=false;
- static private $affected=0;
- static private $result=false;
static private $inTransaction=false;
static private $prefix=null;
static private $type=null;
@@ -222,7 +220,7 @@ class OC_DB {
echo( '<b>can not connect to database, using '.$type.'. ('.self::$MDB2->getUserInfo().')</center>');
OC_Log::write('core',self::$MDB2->getUserInfo(),OC_Log::FATAL);
OC_Log::write('core',self::$MDB2->getMessage(),OC_Log::FATAL);
- die( $error );
+ die();
}
// We always, really always want associative arrays
@@ -370,9 +368,6 @@ class OC_DB {
if( $definition instanceof MDB2_Schema_Error ){
die( $definition->getMessage().': '.$definition->getUserInfo());
}
-// if(OC_Config::getValue('dbtype','sqlite')=='sqlite'){
-// $definition['overwrite']=true;//always overwrite for sqlite
-// }
$ret=self::$schema->createDatabase( $definition );
// Die in case something went wrong
@@ -519,8 +514,9 @@ class OC_DB {
// Delete our temporary file
unlink( $file2 );
- foreach($definition['tables'] as $name=>$table){
- self::dropTable($name);
+ $tables=array_keys($definition['tables']);
+ foreach($tables as $table){
+ self::dropTable($table);
}
}
@@ -528,8 +524,7 @@ class OC_DB {
* @brief replaces the owncloud tables with a new set
* @param $file string path to the MDB2 xml db export file
*/
- public static function replaceDB( $file ){
-
+ public static function replaceDB( $file ){
$apps = OC_App::getAllApps();
self::beginTransaction();
// Delete the old tables
diff --git a/lib/eventsource.php b/lib/eventsource.php
index cf10660b94c..95af2e471bc 100644
--- a/lib/eventsource.php
+++ b/lib/eventsource.php
@@ -36,12 +36,15 @@ class OC_EventSource{
header('Cache-Control: no-cache');
$this->fallback=isset($_GET['fallback']) and $_GET['fallback']=='true';
if($this->fallback){
- $fallBackId=$_GET['fallback_id'];
+ $this->fallBackId=$_GET['fallback_id'];
header("Content-Type: text/html");
echo str_repeat('<span></span>'.PHP_EOL,10); //dummy data to keep IE happy
}else{
header("Content-Type: text/event-stream");
}
+ if( !OC_Util::isCallRegistered()){
+ exit();
+ }
flush();
}
diff --git a/lib/exception.php b/lib/exception.php
new file mode 100644
index 00000000000..db516fc12d2
--- /dev/null
+++ b/lib/exception.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * ownCloud
+ *
+ * @author Georg Ehrke
+ * @copyright 2012 georg@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/>.
+ *
+ */
+class OC_Exception extends Exception{
+
+ function __construct($message = null, $code = 0, $file = null, $line = null){
+ parent::__construct($message, $code);
+ if(!is_null($file)){
+ $this->file = $file;
+ }
+ if(!is_null($line)){
+ $this->line = $line;
+ }
+ $this->writelog();
+ }
+
+ private function writelog(){
+ @OC_Log::write(OC_App::getCurrentApp(), $this->getMessage() . '-' . $this->getFile() . '-' . $this->getLine(), OC_Log::FATAL);
+ }
+
+ private function generatesysinfo(){
+ return array('phpversion' => PHP_VERSION,
+ 'os' => php_uname('s'),
+ 'osrelease' => php_uname('r'),
+ 'osarchitecture' => php_uname('m'),
+ 'phpserverinterface' => php_sapi_name(),
+ 'serverprotocol' => $_SERVER['SERVER_PROTOCOL'],
+ 'requestmethod' => $_SERVER['REQUEST_METHOD'],
+ 'https' => ($_SERVER['HTTPS']==''?'false':'true'),
+ 'database'=>(@OC_Config::getValue('dbtype')!=''?@OC_Config::getValue('dbtype'):'')
+ );
+ }
+
+ function __toString(){
+ $tmpl = new OC_Template('core', 'exception', 'guest');
+ $tmpl->assign('showsysinfo', true);
+ $tmpl->assign('message', $this->getMessage());
+ $tmpl->assign('code', $this->getCode());
+ $tmpl->assign('file', $this->getFile());
+ $tmpl->assign('line', $this->getLine());
+ $tmpl->assign('sysinfo', $this->generatesysinfo());
+ $tmpl->printPage();
+ }
+}
+
+function oc_exceptionhandler($exception){
+ switch($exception->getCode()){
+ case E_NOTICE:
+ case E_DEPRECATED:
+ case E_USER_NOTICE:
+ case E_USER_DEPRECATED:
+ break;
+ default:
+ throw new OC_Exception($exception->getMessage(), $exception->getCode(), $exception->getFile(), $exception->getLine());
+ break;
+ }
+ return true;
+}
+
+function oc_errorhandler($errno , $errstr , $errfile , $errline){
+ switch($errno){
+ case E_NOTICE:
+ case E_DEPRECATED:
+ case E_USER_NOTICE:
+ case E_USER_DEPRECATED:
+ break;
+ default:
+ throw new OC_Exception($errstr, $errno, $errfile, $errline);
+ break;
+ }
+ return true;
+}
+set_exception_handler('oc_exceptionhandler');
+set_error_handler('oc_errorhandler');
+error_reporting(E_ERROR | E_WARNING | E_PARSE); \ No newline at end of file
diff --git a/lib/filecache.php b/lib/filecache.php
index b6f4d23986b..8211637d141 100644
--- a/lib/filecache.php
+++ b/lib/filecache.php
@@ -98,6 +98,10 @@ class OC_FileCache{
if(OC_DB::isError($result)){
OC_Log::write('files','error while writing file('.$path.') to cache',OC_Log::ERROR);
}
+
+ if($cache=OC_Cache::getUserCache(true)){
+ $cache->remove('fileid/'.$path);//ensure we don't have -1 cached
+ }
}
/**
@@ -126,7 +130,7 @@ class OC_FileCache{
$query=OC_DB::prepare($sql);
$result=$query->execute($arguments);
if(OC_DB::isError($result)){
- OC_Log::write('files','error while updating file('.$path.') in cache',OC_Log::ERROR);
+ OC_Log::write('files','error while updating file('.$id.') in cache',OC_Log::ERROR);
}
}
@@ -146,6 +150,11 @@ class OC_FileCache{
$query=OC_DB::prepare('UPDATE *PREFIX*fscache SET parent=? ,name=?, path=?, path_hash=? WHERE path_hash=?');
$query->execute(array($newParent,basename($newPath),$newPath,md5($newPath),md5($oldPath)));
+ if(($cache=OC_Cache::getUserCache(true)) && $cache->hasKey('fileid/'.$oldPath)){
+ $cache->set('fileid/'.$newPath,$cache->get('fileid/'.$oldPath));
+ $cache->remove('fileid/'.$oldPath);
+ }
+
$query=OC_DB::prepare('SELECT path FROM *PREFIX*fscache WHERE path LIKE ?');
$oldLength=strlen($oldPath);
$updateQuery=OC_DB::prepare('UPDATE *PREFIX*fscache SET path=?, path_hash=? WHERE path_hash=?');
@@ -153,6 +162,11 @@ class OC_FileCache{
$old=$row['path'];
$new=$newPath.substr($old,$oldLength);
$updateQuery->execute(array($new,md5($new),md5($old)));
+
+ if(($cache=OC_Cache::getUserCache(true)) && $cache->hasKey('fileid/'.$old)){
+ $cache->set('fileid/'.$new,$cache->get('fileid/'.$old));
+ $cache->remove('fileid/'.$old);
+ }
}
}
@@ -171,6 +185,8 @@ class OC_FileCache{
//delete everything inside the folder
$query=OC_DB::prepare('DELETE FROM *PREFIX*fscache WHERE path LIKE ?');
$query->execute(array($root.$path.'/%'));
+
+ OC_Cache::remove('fileid/'.$root.$path);
}
/**
@@ -245,9 +261,14 @@ class OC_FileCache{
if($root===false){
$root=OC_Filesystem::getRoot();
}
+
+ $fullPath=$root.$path;
+ if(($cache=OC_Cache::getUserCache(true)) && $cache->hasKey('fileid/'.$fullPath)){
+ return $cache->get('fileid/'.$fullPath);
+ }
$query=OC_DB::prepare('SELECT id FROM *PREFIX*fscache WHERE path_hash=?');
- $result=$query->execute(array(md5($root.$path)));
+ $result=$query->execute(array(md5($fullPath)));
if(OC_DB::isError($result)){
OC_Log::write('files','error while getting file id of '.$path,OC_Log::ERROR);
return -1;
@@ -255,10 +276,15 @@ class OC_FileCache{
$result=$result->fetchRow();
if(is_array($result)){
- return $result['id'];
+ $id=$result['id'];
}else{
- return -1;
+ $id=-1;
}
+ if($cache=OC_Cache::getUserCache(true)){
+ $cache->set('fileid/'.$fullPath,$id);
+ }
+
+ return $id;
}
/**
@@ -303,7 +329,7 @@ class OC_FileCache{
*/
public static function increaseSize($path,$sizeDiff, $root=false){
if($sizeDiff==0) return;
- $id=self::getId($path,'');
+ $id=self::getId($path,$root);
while($id!=-1){//walk up the filetree increasing the size of all parent folders
$query=OC_DB::prepare('UPDATE *PREFIX*fscache SET size=size+? WHERE id=?');
$query->execute(array($sizeDiff,$id));
diff --git a/lib/filecache/update.php b/lib/filecache/update.php
index dd77f491ca0..93b632acb4e 100644
--- a/lib/filecache/update.php
+++ b/lib/filecache/update.php
@@ -207,7 +207,6 @@ class OC_FileCache_Update{
$cached=OC_FileCache_Cached::get($oldPath,$root);
$oldSize=$cached['size'];
- $size=$view->filesize($newPath);
OC_FileCache::increaseSize(dirname($oldPath),-$oldSize,$root);
OC_FileCache::increaseSize(dirname($newPath),$oldSize,$root);
OC_FileCache::move($oldPath,$newPath);
diff --git a/lib/filestorage.php b/lib/filestorage.php
index c104fca87fe..5d864b5d7bb 100644
--- a/lib/filestorage.php
+++ b/lib/filestorage.php
@@ -24,7 +24,7 @@
* Provde a common interface to all different storage options
*/
abstract class OC_Filestorage{
- public function __construct($parameters){}
+ abstract public function __construct($parameters);
abstract public function mkdir($path);
abstract public function rmdir($path);
abstract public function opendir($path);
@@ -48,7 +48,7 @@ abstract class OC_Filestorage{
abstract public function copy($path1,$path2);
abstract public function fopen($path,$mode);
abstract public function getMimeType($path);
- abstract public function hash($type,$path,$raw);
+ abstract public function hash($type,$path,$raw = false);
abstract public function free_space($path);
abstract public function search($query);
abstract public function touch($path, $mtime=null);
diff --git a/lib/filestorage/common.php b/lib/filestorage/common.php
index 633c2fdfc76..00c76796c57 100644
--- a/lib/filestorage/common.php
+++ b/lib/filestorage/common.php
@@ -204,7 +204,7 @@ abstract class OC_Filestorage_Common extends OC_Filestorage {
unlink($tmpFile);
return $mime;
}
- public function hash($type,$path,$raw){
+ public function hash($type,$path,$raw = false){
$tmpFile=$this->getLocalFile();
$hash=hash($type,$tmpFile,$raw);
unlink($tmpFile);
@@ -229,7 +229,7 @@ abstract class OC_Filestorage_Common extends OC_Filestorage {
}
$tmpFile=OC_Helper::tmpFile($extension);
$target=fopen($tmpFile,'w');
- $count=OC_Helper::streamCopy($source,$target);
+ OC_Helper::streamCopy($source,$target);
return $tmpFile;
}
// abstract public function touch($path, $mtime=null);
diff --git a/lib/filestorage/local.php b/lib/filestorage/local.php
index 658e14eaf9d..6be04db96ae 100644
--- a/lib/filestorage/local.php
+++ b/lib/filestorage/local.php
@@ -4,7 +4,6 @@
*/
class OC_Filestorage_Local extends OC_Filestorage_Common{
protected $datadir;
- private static $mimetypes=null;
public function __construct($arguments){
$this->datadir=$arguments['datadir'];
if(substr($this->datadir,-1)!=='/'){
@@ -41,7 +40,7 @@ class OC_Filestorage_Local extends OC_Filestorage_Common{
}
public function filesize($path){
if($this->is_dir($path)){
- return $this->getFolderSize($path);
+ return 0;
}else{
return filesize($this->datadir.$path);
}
@@ -157,7 +156,7 @@ class OC_Filestorage_Local extends OC_Filestorage_Common{
return $return;
}
- public function hash($type,$path,$raw){
+ public function hash($path,$type,$raw=false){
return hash_file($type,$this->datadir.$path,$raw);
}
@@ -187,15 +186,6 @@ class OC_Filestorage_Local extends OC_Filestorage_Common{
}
/**
- * @brief get the size of folder and it's content
- * @param string $path file path
- * @return int size of folder and it's content
- */
- public function getFolderSize($path){
- return 0;//depricated, use OC_FileCach instead
- }
-
- /**
* check if a file or folder has been updated since $time
* @param int $time
* @return bool
diff --git a/lib/filesystem.php b/lib/filesystem.php
index 5dfcaa107b8..e5817a1bc82 100644
--- a/lib/filesystem.php
+++ b/lib/filesystem.php
@@ -46,105 +46,106 @@
class OC_Filesystem{
static private $storages=array();
static private $mounts=array();
- static private $storageTypes=array();
public static $loaded=false;
- private $fakeRoot='';
+ /**
+ * @var OC_Filestorage $defaultInstance
+ */
static private $defaultInstance;
- /**
- * classname which used for hooks handling
- * used as signalclass in OC_Hooks::emit()
- */
- const CLASSNAME = 'OC_Filesystem';
-
- /**
- * signalname emited before file renaming
- * @param oldpath
- * @param newpath
- */
- const signal_rename = 'rename';
-
- /**
- * signal emited after file renaming
- * @param oldpath
- * @param newpath
- */
- const signal_post_rename = 'post_rename';
-
- /**
- * signal emited before file/dir creation
- * @param path
- * @param run changing this flag to false in hook handler will cancel event
- */
- const signal_create = 'create';
-
- /**
- * signal emited after file/dir creation
- * @param path
- * @param run changing this flag to false in hook handler will cancel event
- */
- const signal_post_create = 'post_create';
-
- /**
- * signal emits before file/dir copy
- * @param oldpath
- * @param newpath
- * @param run changing this flag to false in hook handler will cancel event
- */
- const signal_copy = 'copy';
-
- /**
- * signal emits after file/dir copy
- * @param oldpath
- * @param newpath
- */
- const signal_post_copy = 'post_copy';
-
- /**
- * signal emits before file/dir save
- * @param path
- * @param run changing this flag to false in hook handler will cancel event
- */
- const signal_write = 'write';
-
- /**
- * signal emits after file/dir save
- * @param path
- */
- const signal_post_write = 'post_write';
-
- /**
- * signal emits when reading file/dir
- * @param path
- */
- const signal_read = 'read';
-
- /**
- * signal emits when removing file/dir
- * @param path
- */
- const signal_delete = 'delete';
-
- /**
- * parameters definitions for signals
- */
- const signal_param_path = 'path';
- const signal_param_oldpath = 'oldpath';
- const signal_param_newpath = 'newpath';
-
- /**
- * run - changing this flag to false in hook handler will cancel event
- */
- const signal_param_run = 'run';
-
- /**
- * get the mountpoint of the storage object for a path
- ( note: because a storage is not always mounted inside the fakeroot, the returned mountpoint is relative to the absolute root of the filesystem and doesn't take the chroot into account
- *
- * @param string path
- * @return string
- */
+ /**
+ * classname which used for hooks handling
+ * used as signalclass in OC_Hooks::emit()
+ */
+ const CLASSNAME = 'OC_Filesystem';
+
+ /**
+ * signalname emited before file renaming
+ * @param oldpath
+ * @param newpath
+ */
+ const signal_rename = 'rename';
+
+ /**
+ * signal emited after file renaming
+ * @param oldpath
+ * @param newpath
+ */
+ const signal_post_rename = 'post_rename';
+
+ /**
+ * signal emited before file/dir creation
+ * @param path
+ * @param run changing this flag to false in hook handler will cancel event
+ */
+ const signal_create = 'create';
+
+ /**
+ * signal emited after file/dir creation
+ * @param path
+ * @param run changing this flag to false in hook handler will cancel event
+ */
+ const signal_post_create = 'post_create';
+
+ /**
+ * signal emits before file/dir copy
+ * @param oldpath
+ * @param newpath
+ * @param run changing this flag to false in hook handler will cancel event
+ */
+ const signal_copy = 'copy';
+
+ /**
+ * signal emits after file/dir copy
+ * @param oldpath
+ * @param newpath
+ */
+ const signal_post_copy = 'post_copy';
+
+ /**
+ * signal emits before file/dir save
+ * @param path
+ * @param run changing this flag to false in hook handler will cancel event
+ */
+ const signal_write = 'write';
+
+ /**
+ * signal emits after file/dir save
+ * @param path
+ */
+ const signal_post_write = 'post_write';
+
+ /**
+ * signal emits when reading file/dir
+ * @param path
+ */
+ const signal_read = 'read';
+
+ /**
+ * signal emits when removing file/dir
+ * @param path
+ */
+ const signal_delete = 'delete';
+
+ /**
+ * parameters definitions for signals
+ */
+ const signal_param_path = 'path';
+ const signal_param_oldpath = 'oldpath';
+ const signal_param_newpath = 'newpath';
+
+ /**
+ * run - changing this flag to false in hook handler will cancel event
+ */
+ const signal_param_run = 'run';
+
+ /**
+ * get the mountpoint of the storage object for a path
+ ( note: because a storage is not always mounted inside the fakeroot, the returned mountpoint is relative to the absolute root of the filesystem and doesn't take the chroot into account
+ *
+ * @param string path
+ * @return string
+ */
static public function getMountPoint($path){
OC_Hook::emit(self::CLASSNAME,'get_mountpoint',array('path'=>$path));
if(!$path){
@@ -155,7 +156,8 @@ class OC_Filesystem{
}
$path=str_replace('//', '/',$path);
$foundMountPoint='';
- foreach(OC_Filesystem::$mounts as $mountpoint=>$storage){
+ $mountPoints=array_keys(OC_Filesystem::$mounts);
+ foreach($mountPoints as $mountpoint){
if($mountpoint==$path){
return $mountpoint;
}
@@ -260,10 +262,7 @@ class OC_Filesystem{
* tear down the filesystem, removing all storage providers
*/
static public function tearDown(){
- foreach(self::$storages as $mountpoint=>$storage){
- unset(self::$storages[$mountpoint]);
- }
- $fakeRoot='';
+ self::$storages=array();
}
/**
@@ -287,7 +286,7 @@ class OC_Filesystem{
* @return bool
*/
static public function chroot($fakeRoot){
- return self::$defaultInstance->chroot($path);
+ return self::$defaultInstance->chroot($fakeRoot);
}
/**
@@ -320,22 +319,8 @@ class OC_Filesystem{
if(substr($mountpoint,-1)!=='/'){
$mountpoint=$mountpoint.'/';
}
- if (self::getView() != null && $mountpoint != '/' && !self::is_dir(basename($mountpoint))) {
- self::mkdir(basename($mountpoint));
- }
self::$mounts[$mountpoint]=array('class'=>$class,'arguments'=>$arguments);
}
-
- /**
- * create all storage backends mounted in the filesystem
- */
- static private function mountAll(){
- foreach(self::$mounts as $mountPoint=>$mount){
- if(!isset(self::$storages[$mountPoint])){
- self::$storages[$mountPoint]=self::createStorage($mount['type'],$mount['arguments']);
- }
- }
- }
/**
* return the path to a local version of the file
@@ -488,8 +473,8 @@ class OC_Filesystem{
static public function getMimeType($path){
return self::$defaultInstance->getMimeType($path);
}
- static public function hash($type,$path){
- return self::$defaultInstance->hash($type,$path);
+ static public function hash($type,$path, $raw = false){
+ return self::$defaultInstance->hash($type,$path, $raw);
}
static public function free_space($path='/'){
@@ -506,9 +491,17 @@ class OC_Filesystem{
* @return bool
*/
static public function hasUpdated($path,$time){
- return self::$defaultInstance->hasUpdated($path);
+ return self::$defaultInstance->hasUpdated($path,$time);
+ }
+
+ static public function removeETagHook($params) {
+ $path=$params['path'];
+ OC_Connector_Sabre_Node::removeETagPropertyForFile($path);
}
}
+OC_Hook::connect('OC_Filesystem','post_write', 'OC_Filesystem','removeETagHook');
+OC_Hook::connect('OC_Filesystem','post_delete','OC_Filesystem','removeETagHook');
+OC_Hook::connect('OC_Filesystem','post_rename','OC_Filesystem','removeETagHook');
OC_Util::setupFS();
require_once('filecache.php');
diff --git a/lib/filesystemview.php b/lib/filesystemview.php
index 05731299467..af1a578ec6a 100644
--- a/lib/filesystemview.php
+++ b/lib/filesystemview.php
@@ -22,19 +22,19 @@
/**
- * Class to provide access to ownCloud filesystem via a "view", and methods for
- * working with files within that view (e.g. read, write, delete, etc.). Each
- * view is restricted to a set of directories via a virtual root. The default view
- * uses the currently logged in user's data directory as root (parts of
+ * Class to provide access to ownCloud filesystem via a "view", and methods for
+ * working with files within that view (e.g. read, write, delete, etc.). Each
+ * view is restricted to a set of directories via a virtual root. The default view
+ * uses the currently logged in user's data directory as root (parts of
* OC_Filesystem are merely a wrapper for OC_FilesystemView).
- *
+ *
* Apps that need to access files outside of the user data folders (to modify files
* belonging to a user other than the one currently logged in, for example) should
* use this class directly rather than using OC_Filesystem, or making use of PHP's
- * built-in file manipulation functions. This will ensure all hooks and proxies
+ * built-in file manipulation functions. This will ensure all hooks and proxies
* are triggered correctly.
*
- * Filesystem functions are not called directly; they are passed to the correct
+ * Filesystem functions are not called directly; they are passed to the correct
* OC_Filestorage object
*/
@@ -43,11 +43,11 @@ class OC_FilesystemView {
private $internal_path_cache=array();
private $storage_cache=array();
- public function __construct($root){
+ public function __construct($root) {
$this->fakeRoot=$root;
}
- public function getAbsolutePath($path){
+ public function getAbsolutePath($path) {
if(!$path){
$path='/';
}
@@ -63,9 +63,9 @@ class OC_FilesystemView {
* @param string fakeRoot
* @return bool
*/
- public function chroot($fakeRoot){
+ public function chroot($fakeRoot) {
if(!$fakeRoot==''){
- if($fakeRoot[0]!=='/'){
+ if($fakeRoot[0]!=='/') {
$fakeRoot='/'.$fakeRoot;
}
}
@@ -76,7 +76,7 @@ class OC_FilesystemView {
* get the fake root
* @return string
*/
- public function getRoot(){
+ public function getRoot() {
return $this->fakeRoot;
}
@@ -85,7 +85,7 @@ class OC_FilesystemView {
* @param string path
* @return bool
*/
- public function getInternalPath($path){
+ public function getInternalPath($path) {
if (!isset($this->internal_path_cache[$path])) {
$this->internal_path_cache[$path] = OC_Filesystem::getInternalPath($this->getAbsolutePath($path));
}
@@ -97,23 +97,23 @@ class OC_FilesystemView {
* @param string path
* @return string
*/
- public function getRelativePath($path){
- if($this->fakeRoot==''){
+ public function getRelativePath($path) {
+ if($this->fakeRoot=='') {
return $path;
}
- if(strpos($path,$this->fakeRoot)!==0){
+ if(strpos($path, $this->fakeRoot)!==0) {
return null;
}else{
- return substr($path,strlen($this->fakeRoot));
+ return substr($path, strlen($this->fakeRoot));
}
}
-
+
/**
* get the storage object for a path
* @param string path
* @return OC_Filestorage
*/
- public function getStorage($path){
+ public function getStorage($path) {
if (!isset($this->storage_cache[$path])) {
$this->storage_cache[$path] = OC_Filesystem::getStorage($this->getAbsolutePath($path));
}
@@ -127,7 +127,7 @@ class OC_FilesystemView {
* @param string path
* @return string
*/
- public function getMountPoint($path){
+ public function getMountPoint($path) {
return OC_Filesystem::getMountPoint($this->getAbsolutePath($path));
}
@@ -137,55 +137,55 @@ class OC_FilesystemView {
* @param string path
* @return string
*/
- public function getLocalFile($path){
- $parent=substr($path,0,strrpos($path,'/'));
- if(OC_Filesystem::isValidPath($parent) and $storage=$this->getStorage($path)){
+ public function getLocalFile($path) {
+ $parent=substr($path, 0, strrpos($path,'/'));
+ if(OC_Filesystem::isValidPath($parent) and $storage=$this->getStorage($path)) {
return $storage->getLocalFile($this->getInternalPath($path));
}
}
/**
- * the following functions operate with arguments and return values identical
- * to those of their PHP built-in equivalents. Mostly they are merely wrappers
+ * the following functions operate with arguments and return values identical
+ * to those of their PHP built-in equivalents. Mostly they are merely wrappers
* for OC_Filestorage via basicOperation().
*/
- public function mkdir($path){
- return $this->basicOperation('mkdir',$path,array('create','write'));
+ public function mkdir($path) {
+ return $this->basicOperation('mkdir', $path, array('create', 'write'));
}
- public function rmdir($path){
- return $this->basicOperation('rmdir',$path,array('delete'));
+ public function rmdir($path) {
+ return $this->basicOperation('rmdir', $path, array('delete'));
}
- public function opendir($path){
- return $this->basicOperation('opendir',$path,array('read'));
+ public function opendir($path) {
+ return $this->basicOperation('opendir', $path, array('read'));
}
- public function readdir($handle){
+ public function readdir($handle) {
$fsLocal= new OC_Filestorage_Local( array( 'datadir' => '/' ) );
return $fsLocal->readdir( $handle );
}
- public function is_dir($path){
+ public function is_dir($path) {
if($path=='/'){
return true;
}
- return $this->basicOperation('is_dir',$path);
+ return $this->basicOperation('is_dir', $path);
}
- public function is_file($path){
+ public function is_file($path) {
if($path=='/'){
return false;
}
- return $this->basicOperation('is_file',$path);
+ return $this->basicOperation('is_file', $path);
}
- public function stat($path){
- return $this->basicOperation('stat',$path);
+ public function stat($path) {
+ return $this->basicOperation('stat', $path);
}
- public function filetype($path){
- return $this->basicOperation('filetype',$path);
+ public function filetype($path) {
+ return $this->basicOperation('filetype', $path);
}
- public function filesize($path){
- return $this->basicOperation('filesize',$path);
+ public function filesize($path) {
+ return $this->basicOperation('filesize', $path);
}
- public function readfile($path){
+ public function readfile($path) {
@ob_end_clean();
- $handle=$this->fopen($path,'rb');
+ $handle=$this->fopen($path, 'rb');
if ($handle) {
$chunkSize = 8192;// 8 MB chunks
while (!feof($handle)) {
@@ -224,131 +224,204 @@ class OC_FilesystemView {
public function isSharable($path) {
return $this->basicOperation('isSharable', $path);
}
- public function file_exists($path){
+ public function file_exists($path) {
if($path=='/'){
return true;
}
- return $this->basicOperation('file_exists',$path);
+ return $this->basicOperation('file_exists', $path);
}
- public function filectime($path){
- return $this->basicOperation('filectime',$path);
+ public function filectime($path) {
+ return $this->basicOperation('filectime', $path);
}
- public function filemtime($path){
- return $this->basicOperation('filemtime',$path);
+ public function filemtime($path) {
+ return $this->basicOperation('filemtime', $path);
}
- public function touch($path, $mtime=null){
+ public function touch($path, $mtime=null) {
return $this->basicOperation('touch', $path, array('write'), $mtime);
}
- public function file_get_contents($path){
- return $this->basicOperation('file_get_contents',$path,array('read'));
- }
- public function file_put_contents($path,$data){
- if(is_resource($data)){//not having to deal with streams in file_put_contents makes life easier
- $exists=$this->file_exists($path);
- $run=true;
- if(!$exists){
- OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_create, array( OC_Filesystem::signal_param_path => $path, OC_Filesystem::signal_param_run => &$run));
+ public function file_get_contents($path) {
+ return $this->basicOperation('file_get_contents', $path, array('read'));
+ }
+ public function file_put_contents($path, $data) {
+ if(is_resource($data)) {//not having to deal with streams in file_put_contents makes life easier
+ $exists = $this->file_exists($path);
+ $run = true;
+ if(!$exists) {
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ OC_Filesystem::signal_create,
+ array(
+ OC_Filesystem::signal_param_path => $path,
+ OC_Filesystem::signal_param_run => &$run
+ )
+ );
}
- OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_write, array( OC_Filesystem::signal_param_path => $path, OC_Filesystem::signal_param_run => &$run));
- if(!$run){
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ OC_Filesystem::signal_write,
+ array(
+ OC_Filesystem::signal_param_path => $path,
+ OC_Filesystem::signal_param_run => &$run
+ )
+ );
+ if(!$run) {
return false;
}
- $target=$this->fopen($path,'w');
- if($target){
- $count=OC_Helper::streamCopy($data,$target);
+ $target=$this->fopen($path, 'w');
+ if($target) {
+ $count=OC_Helper::streamCopy($data, $target);
fclose($target);
fclose($data);
- if(!$exists){
- OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_create, array( OC_Filesystem::signal_param_path => $path));
+ if(!$exists) {
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ OC_Filesystem::signal_post_create,
+ array( OC_Filesystem::signal_param_path => $path)
+ );
}
- OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_write, array( OC_Filesystem::signal_param_path => $path));
- return $count>0;
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ OC_Filesystem::signal_post_write,
+ array( OC_Filesystem::signal_param_path => $path)
+ );
+ return $count > 0;
}else{
return false;
}
}else{
- return $this->basicOperation('file_put_contents',$path,array('create','write'),$data);
+ return $this->basicOperation('file_put_contents', $path, array('create', 'write'), $data);
}
}
- public function unlink($path){
- return $this->basicOperation('unlink',$path,array('delete'));
+ public function unlink($path) {
+ return $this->basicOperation('unlink', $path, array('delete'));
}
public function deleteAll( $directory, $empty = false ) {
return $this->basicOperation( 'deleteAll', $directory, array('delete'), $empty );
}
- public function rename($path1,$path2){
- $absolutePath1=$this->getAbsolutePath($path1);
- $absolutePath2=$this->getAbsolutePath($path2);
- if(OC_FileProxy::runPreProxies('rename',$absolutePath1,$absolutePath2) and OC_Filesystem::isValidPath($path2)){
- $path1=$this->getRelativePath($absolutePath1);
- $path2=$this->getRelativePath($absolutePath2);
- if($path1==null or $path2==null){
+ public function rename($path1, $path2) {
+ $absolutePath1 = $this->getAbsolutePath($path1);
+ $absolutePath2 = $this->getAbsolutePath($path2);
+ if(OC_FileProxy::runPreProxies('rename', $absolutePath1, $absolutePath2) and OC_Filesystem::isValidPath($path2)) {
+ $path1 = $this->getRelativePath($absolutePath1);
+ $path2 = $this->getRelativePath($absolutePath2);
+ if($path1 == null or $path2 == null) {
return false;
}
$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){
- $mp1=$this->getMountPoint($path1);
- $mp2=$this->getMountPoint($path2);
- if($mp1==$mp2){
- if($storage=$this->getStorage($path1)){
- $result=$storage->rename($this->getInternalPath($path1),$this->getInternalPath($path2));
+ 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) {
+ $mp1 = $this->getMountPoint($path1);
+ $mp2 = $this->getMountPoint($path2);
+ if($mp1 == $mp2) {
+ if($storage = $this->getStorage($path1)) {
+ $result = $storage->rename($this->getInternalPath($path1), $this->getInternalPath($path2));
}
- }else{
- $source=$this->fopen($path1,'r');
- $target=$this->fopen($path2,'w');
- $count=OC_Helper::streamCopy($source,$target);
- $storage1=$this->getStorage($path1);
+ } else {
+ $source = $this->fopen($path1, 'r');
+ $target = $this->fopen($path2, 'w');
+ $count = OC_Helper::streamCopy($source, $target);
+ $storage1 = $this->getStorage($path1);
$storage1->unlink($this->getInternalPath($path1));
- $result=$count>0;
+ $result = $count>0;
}
- OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_rename, array( OC_Filesystem::signal_param_oldpath => $path1, OC_Filesystem::signal_param_newpath=>$path2));
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ OC_Filesystem::signal_post_rename,
+ array(
+ OC_Filesystem::signal_param_oldpath => $path1,
+ OC_Filesystem::signal_param_newpath => $path2
+ )
+ );
return $result;
}
}
}
- public function copy($path1,$path2){
- $absolutePath1=$this->getAbsolutePath($path1);
- $absolutePath2=$this->getAbsolutePath($path2);
- if(OC_FileProxy::runPreProxies('copy',$absolutePath1,$absolutePath2) and OC_Filesystem::isValidPath($path2)){
- $path1=$this->getRelativePath($absolutePath1);
- $path2=$this->getRelativePath($absolutePath2);
- if($path1==null or $path2==null){
+ public function copy($path1, $path2) {
+ $absolutePath1 = $this->getAbsolutePath($path1);
+ $absolutePath2 = $this->getAbsolutePath($path2);
+ if(OC_FileProxy::runPreProxies('copy', $absolutePath1, $absolutePath2) and OC_Filesystem::isValidPath($path2)) {
+ $path1 = $this->getRelativePath($absolutePath1);
+ $path2 = $this->getRelativePath($absolutePath2);
+ if($path1 == null or $path2 == null) {
return false;
}
$run=true;
- OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_copy, array( OC_Filesystem::signal_param_oldpath => $path1 , OC_Filesystem::signal_param_newpath=>$path2, OC_Filesystem::signal_param_run => &$run));
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ OC_Filesystem::signal_copy,
+ array(
+ OC_Filesystem::signal_param_oldpath => $path1,
+ OC_Filesystem::signal_param_newpath=>$path2,
+ OC_Filesystem::signal_param_run => &$run
+ )
+ );
$exists=$this->file_exists($path2);
- if($run and !$exists){
- OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_create, array( OC_Filesystem::signal_param_path => $path2, OC_Filesystem::signal_param_run => &$run));
+ if($run and !$exists) {
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ OC_Filesystem::signal_create,
+ array(
+ OC_Filesystem::signal_param_path => $path2,
+ OC_Filesystem::signal_param_run => &$run
+ )
+ );
}
- if($run){
- OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_write, array( OC_Filesystem::signal_param_path => $path2, OC_Filesystem::signal_param_run => &$run));
+ if($run) {
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ OC_Filesystem::signal_write,
+ array(
+ OC_Filesystem::signal_param_path => $path2,
+ OC_Filesystem::signal_param_run => &$run
+ )
+ );
}
- if($run){
+ if($run) {
$mp1=$this->getMountPoint($path1);
$mp2=$this->getMountPoint($path2);
- if($mp1==$mp2){
- if($storage=$this->getStorage($path1)){
- $result=$storage->copy($this->getInternalPath($path1),$this->getInternalPath($path2));
+ if($mp1 == $mp2){
+ if($storage = $this->getStorage($path1)) {
+ $result=$storage->copy($this->getInternalPath($path1), $this->getInternalPath($path2));
}
- }else{
- $source=$this->fopen($path1,'r');
- $target=$this->fopen($path2,'w');
- $result=OC_Helper::streamCopy($source,$target);
+ } else {
+ $source = $this->fopen($path1, 'r');
+ $target = $this->fopen($path2, 'w');
+ $result = OC_Helper::streamCopy($source, $target);
}
- OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_copy, array( OC_Filesystem::signal_param_oldpath => $path1 , OC_Filesystem::signal_param_newpath=>$path2));
- if(!$exists){
- OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_create, array( OC_Filesystem::signal_param_path => $path2));
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ OC_Filesystem::signal_post_copy,
+ array(
+ OC_Filesystem::signal_param_oldpath => $path1,
+ OC_Filesystem::signal_param_newpath=>$path2
+ )
+ );
+ if(!$exists) {
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ OC_Filesystem::signal_post_create,
+ array(OC_Filesystem::signal_param_path => $path2)
+ );
}
- OC_Hook::emit( OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_write, array( OC_Filesystem::signal_param_path => $path2));
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ OC_Filesystem::signal_post_write,
+ array( OC_Filesystem::signal_param_path => $path2)
+ );
return $result;
}
}
}
- public function fopen($path,$mode){
+ public function fopen($path, $mode) {
$hooks=array();
- switch($mode){
+ switch($mode) {
case 'r':
case 'rb':
$hooks[]='read';
@@ -376,49 +449,68 @@ class OC_FilesystemView {
OC_Log::write('core','invalid mode ('.$mode.') for '.$path,OC_Log::ERROR);
}
- return $this->basicOperation('fopen',$path,$hooks,$mode);
+ return $this->basicOperation('fopen', $path, $hooks, $mode);
}
- public function toTmpFile($path){
- if(OC_Filesystem::isValidPath($path)){
- $source=$this->fopen($path,'r');
- if($source){
+ public function toTmpFile($path) {
+ if(OC_Filesystem::isValidPath($path)) {
+ $source = $this->fopen($path, 'r');
+ if($source) {
$extension='';
- $extOffset=strpos($path,'.');
+ $extOffset=strpos($path, '.');
if($extOffset !== false) {
- $extension=substr($path,strrpos($path,'.'));
+ $extension=substr($path, strrpos($path,'.'));
}
- $tmpFile=OC_Helper::tmpFile($extension);
- file_put_contents($tmpFile,$source);
+ $tmpFile = OC_Helper::tmpFile($extension);
+ file_put_contents($tmpFile, $source);
return $tmpFile;
}
}
}
- public function fromTmpFile($tmpFile,$path){
- if(OC_Filesystem::isValidPath($path)){
- if(!$tmpFile){
+ public function fromTmpFile($tmpFile, $path) {
+ if(OC_Filesystem::isValidPath($path)) {
+ if(!$tmpFile) {
debug_print_backtrace();
}
- $source=fopen($tmpFile,'r');
- if($source){
- $this->file_put_contents($path,$source);
+ $source=fopen($tmpFile, 'r');
+ if($source) {
+ $this->file_put_contents($path, $source);
unlink($tmpFile);
return true;
- }else{
+ } else {
}
- }else{
+ } else {
return false;
}
}
- public function getMimeType($path){
- return $this->basicOperation('getMimeType',$path);
- }
- public function hash($type,$path){
- return $this->basicOperation('hash',$path,array('read'));
+ public function getMimeType($path) {
+ return $this->basicOperation('getMimeType', $path);
+ }
+ public function hash($type, $path, $raw = false) {
+ $absolutePath = $this->getAbsolutePath($path);
+ if (OC_FileProxy::runPreProxies('hash', $absolutePath) && OC_Filesystem::isValidPath($path)) {
+ $path = $this->getRelativePath($absolutePath);
+ if ($path == null) {
+ return false;
+ }
+ if (OC_Filesystem::$loaded && $this->fakeRoot == OC_Filesystem::getRoot()) {
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ OC_Filesystem::signal_read,
+ array( OC_Filesystem::signal_param_path => $path)
+ );
+ }
+ if ($storage = $this->getStorage($path)) {
+ $result = $storage->hash($type, $this->getInternalPath($path), $raw);
+ $result = OC_FileProxy::runPostProxies('hash', $absolutePath, $result);
+ return $result;
+ }
+ }
+ return null;
}
- public function free_space($path='/'){
- return $this->basicOperation('free_space',$path);
+ public function free_space($path='/') {
+ return $this->basicOperation('free_space', $path);
}
/**
@@ -428,41 +520,56 @@ class OC_FilesystemView {
* @param array (optional) hooks
* @param mixed (optional) $extraParam
* @return mixed
- *
- * This method takes requests for basic filesystem functions (e.g. reading & writing
- * files), processes hooks and proxies, sanitises paths, and finally passes them on to
+ *
+ * This method takes requests for basic filesystem functions (e.g. reading & writing
+ * files), processes hooks and proxies, sanitises paths, and finally passes them on to
* OC_Filestorage for delegation to a storage backend for execution
*/
- private function basicOperation($operation,$path,$hooks=array(),$extraParam=null){
- $absolutePath=$this->getAbsolutePath($path);
- if(OC_FileProxy::runPreProxies($operation,$absolutePath, $extraParam) and OC_Filesystem::isValidPath($path)){
- $path=$this->getRelativePath($absolutePath);
- if($path==null){
+ private function basicOperation($operation, $path, $hooks=array(), $extraParam=null) {
+ $absolutePath = $this->getAbsolutePath($path);
+ if(OC_FileProxy::runPreProxies($operation, $absolutePath, $extraParam) and OC_Filesystem::isValidPath($path)) {
+ $path = $this->getRelativePath($absolutePath);
+ if($path == null) {
return false;
}
- $internalPath=$this->getInternalPath($path);
- $run=true;
- if(OC_Filesystem::$loaded and $this->fakeRoot==OC_Filesystem::getRoot()){
- foreach($hooks as $hook){
- if($hook!='read'){
- OC_Hook::emit( OC_Filesystem::CLASSNAME, $hook, array( OC_Filesystem::signal_param_path => $path, OC_Filesystem::signal_param_run => &$run));
- }else{
- OC_Hook::emit( OC_Filesystem::CLASSNAME, $hook, array( OC_Filesystem::signal_param_path => $path));
+ $internalPath = $this->getInternalPath($path);
+ $run = true;
+ if(OC_Filesystem::$loaded and $this->fakeRoot==OC_Filesystem::getRoot()) {
+ foreach($hooks as $hook) {
+ if($hook!='read') {
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ $hook,
+ array(
+ OC_Filesystem::signal_param_path => $path,
+ OC_Filesystem::signal_param_run => &$run
+ )
+ );
+ } else {
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ $hook,
+ array( OC_Filesystem::signal_param_path => $path)
+ );
}
}
}
- if($run and $storage=$this->getStorage($path)){
- if(!is_null($extraParam)){
- $result=$storage->$operation($internalPath,$extraParam);
- }else{
- $result=$storage->$operation($internalPath);
+ if($run and $storage = $this->getStorage($path)) {
+ if(!is_null($extraParam)) {
+ $result = $storage->$operation($internalPath, $extraParam);
+ } else {
+ $result = $storage->$operation($internalPath);
}
- $result=OC_FileProxy::runPostProxies($operation,$this->getAbsolutePath($path),$result);
- if(OC_Filesystem::$loaded and $this->fakeRoot==OC_Filesystem::getRoot()){
- if($operation!='fopen'){//no post hooks for fopen, the file stream is still open
- foreach($hooks as $hook){
+ $result = OC_FileProxy::runPostProxies($operation, $this->getAbsolutePath($path), $result);
+ if(OC_Filesystem::$loaded and $this->fakeRoot==OC_Filesystem::getRoot()) {
+ if($operation!='fopen') {//no post hooks for fopen, the file stream is still open
+ foreach($hooks as $hook) {
if($hook!='read'){
- OC_Hook::emit( OC_Filesystem::CLASSNAME, 'post_'.$hook, array( OC_Filesystem::signal_param_path => $path));
+ OC_Hook::emit(
+ OC_Filesystem::CLASSNAME,
+ 'post_'.$hook,
+ array( OC_Filesystem::signal_param_path => $path)
+ );
}
}
}
@@ -478,7 +585,7 @@ class OC_FilesystemView {
* @param int $time
* @return bool
*/
- public function hasUpdated($path,$time){
- return $this->basicOperation('hasUpdated',$path,array(),$time);
+ public function hasUpdated($path, $time) {
+ return $this->basicOperation('hasUpdated', $path, array(), $time);
}
}
diff --git a/lib/group.php b/lib/group.php
index ceee5fa4edb..12e5f5ebb30 100644
--- a/lib/group.php
+++ b/lib/group.php
@@ -43,7 +43,7 @@ class OC_Group {
* @returns true/false
*/
public static function useBackend( $backend ){
- if($backend instanceof OC_Group_Backend){
+ if($backend instanceof OC_Group_Interface){
self::$_usedBackends[]=$backend;
}
}
@@ -168,7 +168,7 @@ class OC_Group {
if($run){
$succes=false;
-
+
//add the user to the all backends that have the group
foreach(self::$_usedBackends as $backend){
if(!$backend->implementsActions(OC_GROUP_BACKEND_ADD_TO_GROUP))
@@ -245,7 +245,7 @@ class OC_Group {
asort($groups);
return $groups;
}
-
+
/**
* check if a group exists
* @param string $gid
@@ -259,7 +259,7 @@ class OC_Group {
}
return false;
}
-
+
/**
* @brief get a list of all users in a group
* @returns array with user ids
diff --git a/lib/group/backend.php b/lib/group/backend.php
index 24778afd1e5..ebc078f152a 100644
--- a/lib/group/backend.php
+++ b/lib/group/backend.php
@@ -37,7 +37,7 @@ define('OC_GROUP_BACKEND_REMOVE_FROM_GOUP', 0x00001000);
/**
* Abstract base class for user management
*/
-abstract class OC_Group_Backend {
+abstract class OC_Group_Backend implements OC_Group_Interface {
protected $possibleActions = array(
OC_GROUP_BACKEND_CREATE_GROUP => 'createGroup',
OC_GROUP_BACKEND_DELETE_GROUP => 'deleteGroup',
diff --git a/lib/group/database.php b/lib/group/database.php
index fb173665eb8..2770ec185c4 100644
--- a/lib/group/database.php
+++ b/lib/group/database.php
@@ -41,7 +41,6 @@
* Class for group management in a SQL Database (e.g. MySQL, SQLite)
*/
class OC_Group_Database extends OC_Group_Backend {
- private $userGroupCache=array();
/**
* @brief Try to create a new group
@@ -116,7 +115,7 @@ class OC_Group_Database extends OC_Group_Backend {
// No duplicate entries!
if( !$this->inGroup( $uid, $gid )){
$query = OC_DB::prepare( "INSERT INTO `*PREFIX*group_user` ( `uid`, `gid` ) VALUES( ?, ? )" );
- $result = $query->execute( array( $uid, $gid ));
+ $query->execute( array( $uid, $gid ));
return true;
}else{
return false;
@@ -133,7 +132,7 @@ class OC_Group_Database extends OC_Group_Backend {
*/
public function removeFromGroup( $uid, $gid ){
$query = OC_DB::prepare( "DELETE FROM *PREFIX*group_user WHERE uid = ? AND gid = ?" );
- $result = $query->execute( array( $uid, $gid ));
+ $query->execute( array( $uid, $gid ));
return true;
}
diff --git a/lib/group/dummy.php b/lib/group/dummy.php
index 0825b10708a..1243891023f 100644
--- a/lib/group/dummy.php
+++ b/lib/group/dummy.php
@@ -126,7 +126,8 @@ class OC_Group_Dummy extends OC_Group_Backend {
*/
public function getUserGroups($uid){
$groups=array();
- foreach($this->groups as $group=>$user){
+ $allGroups=array_keys($this->groups);
+ foreach($allGroups as $group){
if($this->inGroup($uid,$group)){
$groups[]=$group;
}
diff --git a/lib/group/example.php b/lib/group/example.php
index c18562db7a4..9c9ece5ac77 100644
--- a/lib/group/example.php
+++ b/lib/group/example.php
@@ -34,7 +34,7 @@ abstract class OC_Group_Example {
* Trys to create a new group. If the group name already exists, false will
* be returned.
*/
- public static function createGroup($gid){}
+ abstract public static function createGroup($gid);
/**
* @brief delete a group
@@ -43,7 +43,7 @@ abstract class OC_Group_Example {
*
* Deletes a group and removes it from the group_user-table
*/
- public static function deleteGroup($gid){}
+ abstract public static function deleteGroup($gid);
/**
* @brief is user in group?
@@ -53,7 +53,7 @@ abstract class OC_Group_Example {
*
* Checks whether the user is member of a group or not.
*/
- public static function inGroup($uid, $gid){}
+ abstract public static function inGroup($uid, $gid);
/**
* @brief Add a user to a group
@@ -63,7 +63,7 @@ abstract class OC_Group_Example {
*
* Adds a user to a group.
*/
- public static function addToGroup($uid, $gid){}
+ abstract public static function addToGroup($uid, $gid);
/**
* @brief Removes a user from a group
@@ -73,7 +73,7 @@ abstract class OC_Group_Example {
*
* removes the user from a group.
*/
- public static function removeFromGroup($uid,$gid){}
+ abstract public static function removeFromGroup($uid,$gid);
/**
* @brief Get all groups a user belongs to
@@ -83,7 +83,7 @@ abstract class OC_Group_Example {
* This function fetches all groups a user belongs to. It does not check
* if the user exists at all.
*/
- public static function getUserGroups($uid){}
+ abstract public static function getUserGroups($uid);
/**
* @brief get a list of all groups
@@ -91,19 +91,19 @@ abstract class OC_Group_Example {
*
* Returns a list with all groups
*/
- public static function getGroups(){}
+ abstract public static function getGroups();
/**
* check if a group exists
* @param string $gid
* @return bool
*/
- public function groupExists($gid){}
+ abstract public function groupExists($gid);
/**
* @brief get a list of all users in a group
* @returns array with user ids
*/
- public static function usersInGroup($gid){}
+ abstract public static function usersInGroup($gid);
}
diff --git a/lib/group/interface.php b/lib/group/interface.php
new file mode 100644
index 00000000000..7cca6061e10
--- /dev/null
+++ b/lib/group/interface.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * ownCloud - group interface
+ *
+ * @author Arthur Schiwon
+ * @copyright 2012 Arthur Schiwon blizzz@owncloud.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/>.
+ *
+ */
+
+interface OC_Group_Interface {
+ /**
+ * @brief Check if backend implements actions
+ * @param $actions bitwise-or'ed actions
+ * @returns boolean
+ *
+ * Returns the supported actions as int to be
+ * compared with OC_GROUP_BACKEND_CREATE_GROUP etc.
+ */
+ public function implementsActions($actions);
+
+ /**
+ * @brief is user in group?
+ * @param $uid uid of the user
+ * @param $gid gid of the group
+ * @returns true/false
+ *
+ * Checks whether the user is member of a group or not.
+ */
+ public function inGroup($uid, $gid);
+
+ /**
+ * @brief Get all groups a user belongs to
+ * @param $uid Name of the user
+ * @returns array with group names
+ *
+ * This function fetches all groups a user belongs to. It does not check
+ * if the user exists at all.
+ */
+ public function getUserGroups($uid);
+
+ /**
+ * @brief get a list of all groups
+ * @returns array with group names
+ *
+ * Returns a list with all groups
+ */
+ public function getGroups();
+
+ /**
+ * check if a group exists
+ * @param string $gid
+ * @return bool
+ */
+ public function groupExists($gid);
+
+ /**
+ * @brief get a list of all users in a group
+ * @returns array with user ids
+ */
+ public function usersInGroup($gid);
+
+} \ No newline at end of file
diff --git a/lib/helper.php b/lib/helper.php
index 0d18098a4e7..666bc6badfc 100644
--- a/lib/helper.php
+++ b/lib/helper.php
@@ -175,10 +175,8 @@ class OC_Helper {
*/
public static function mimetypeIcon( $mimetype ){
$alias=array('application/xml'=>'code/xml');
-// echo $mimetype;
if(isset($alias[$mimetype])){
$mimetype=$alias[$mimetype];
-// echo $mimetype;
}
// Replace slash with a minus
$mimetype = str_replace( "/", "-", $mimetype );
@@ -345,18 +343,24 @@ class OC_Helper {
*/
static function getMimeType($path){
$isWrapped=(strpos($path,'://')!==false) and (substr($path,0,7)=='file://');
- $mimeType='application/octet-stream';
- if ($mimeType=='application/octet-stream') {
- self::$mimetypes = include('mimetypes.fixlist.php');
- $extension=strtolower(strrchr(basename($path), "."));
- $extension=substr($extension,1);//remove leading .
- $mimeType=(isset(self::$mimetypes[$extension]))?self::$mimetypes[$extension]:'application/octet-stream';
- }
if (@is_dir($path)) {
// directories are easy
return "httpd/unix-directory";
}
+
+ if(strpos($path,'.')){
+ //try to guess the type by the file extension
+ if(!self::$mimetypes || self::$mimetypes != include('mimetypes.list.php')){
+ self::$mimetypes=include('mimetypes.list.php');
+ }
+ $extension=strtolower(strrchr(basename($path), "."));
+ $extension=substr($extension,1);//remove leading .
+ $mimeType=(isset(self::$mimetypes[$extension]))?self::$mimetypes[$extension]:'application/octet-stream';
+ }else{
+ $mimeType='application/octet-stream';
+ }
+
if($mimeType=='application/octet-stream' and function_exists('finfo_open') and function_exists('finfo_file') and $finfo=finfo_open(FILEINFO_MIME)){
$info = @strtolower(finfo_file($finfo,$path));
if($info){
@@ -385,15 +389,6 @@ class OC_Helper {
}
}
- if ($mimeType=='application/octet-stream') {
- // Fallback solution: (try to guess the type by the file extension
- if(!self::$mimetypes || self::$mimetypes != include('mimetypes.list.php')){
- self::$mimetypes=include('mimetypes.list.php');
- }
- $extension=strtolower(strrchr(basename($path), "."));
- $extension=substr($extension,1);//remove leading .
- $mimeType=(isset(self::$mimetypes[$extension]))?self::$mimetypes[$extension]:'application/octet-stream';
- }
return $mimeType;
}
@@ -676,12 +671,38 @@ class OC_Helper {
*/
public static function mb_str_replace($search, $replace, $subject, $encoding = 'UTF-8', &$count = null) {
$offset = -1;
- $length = mb_strlen($search, 'UTF-8');
- while(($i = mb_strrpos($subject, $search, $offset, 'UTF-8'))) {
+ $length = mb_strlen($search, $encoding);
+ while(($i = mb_strrpos($subject, $search, $offset, $encoding))) {
$subject = OC_Helper::mb_substr_replace($subject, $replace, $i, $length);
- $offset = $i - mb_strlen($subject, 'UTF-8') - 1;
+ $offset = $i - mb_strlen($subject, $encoding) - 1;
$count++;
}
return $subject;
}
+
+ /**
+ * @brief performs a search in a nested array
+ * @param haystack the array to be searched
+ * @param needle the search string
+ * @param $index optional, only search this key name
+ * @return the key of the matching field, otherwise false
+ *
+ * performs a search in a nested array
+ *
+ * taken from http://www.php.net/manual/en/function.array-search.php#97645
+ */
+ public static function recursiveArraySearch($haystack, $needle, $index = null) {
+ $aIt = new RecursiveArrayIterator($haystack);
+ $it = new RecursiveIteratorIterator($aIt);
+
+ while($it->valid()) {
+ if (((isset($index) AND ($it->key() == $index)) OR (!isset($index))) AND ($it->current() == $needle)) {
+ return $aIt->key();
+ }
+
+ $it->next();
+ }
+
+ return false;
+ }
}
diff --git a/lib/image.php b/lib/image.php
index 01e843d8316..90c64320a7c 100644
--- a/lib/image.php
+++ b/lib/image.php
@@ -23,12 +23,12 @@
//From user comments at http://dk2.php.net/manual/en/function.exif-imagetype.php
if ( ! function_exists( 'exif_imagetype' ) ) {
- function exif_imagetype ( $filename ) {
- if ( ( list($width, $height, $type, $attr) = getimagesize( $filename ) ) !== false ) {
- return $type;
- }
- return false;
- }
+ function exif_imagetype ( $filename ) {
+ if ( ( $info = getimagesize( $filename ) ) !== false ) {
+ return $info[2];
+ }
+ return false;
+ }
}
function ellipsis($str, $maxlen) {
@@ -66,7 +66,6 @@ class OC_Image {
public function __construct($imageref = null) {
//OC_Log::write('core',__METHOD__.'(): start', OC_Log::DEBUG);
if(!extension_loaded('gd') || !function_exists('gd_info')) {
- //if(!function_exists('imagecreatefromjpeg')) {
OC_Log::write('core',__METHOD__.'(): GD module not installed', OC_Log::ERROR);
return false;
}
@@ -364,7 +363,7 @@ class OC_Image {
public function load($imageref) {
if(is_resource($imageref)) {
if(get_resource_type($imageref) == 'gd') {
- $this->resource = $res;
+ $this->resource = $imageref;
return $this->resource;
} elseif(in_array(get_resource_type($imageref), array('file','stream'))) {
return $this->loadFromFileHandle($imageref);
@@ -650,9 +649,6 @@ class OC_Image {
OC_Log::write('core',__METHOD__.'(): No image loaded', OC_Log::ERROR);
return false;
}
- $width_orig=imageSX($this->resource);
- $height_orig=imageSY($this->resource);
- //OC_Log::write('core',__METHOD__.'(): Original size: '.$width_orig.'x'.$height_orig, OC_Log::DEBUG);
$process = imagecreatetruecolor($w, $h);
if ($process == false) {
OC_Log::write('core',__METHOD__.'(): Error creating true color image',OC_Log::ERROR);
diff --git a/lib/installer.php b/lib/installer.php
index 00feb6d4709..a8b56cb34f2 100644
--- a/lib/installer.php
+++ b/lib/installer.php
@@ -126,19 +126,19 @@ class OC_Installer{
return false;
}
$info=OC_App::getAppInfo($extractDir.'/appinfo/info.xml',true);
- // check the code for not allowed calls
- if(!OC_Installer::checkCode($info['id'],$extractDir)){
+ // check the code for not allowed calls
+ if(!OC_Installer::checkCode($info['id'],$extractDir)){
OC_Log::write('core','App can\'t be installed because of not allowed code in the App',OC_Log::ERROR);
OC_Helper::rmdirr($extractDir);
- return false;
+ return false;
}
- // check if the app is compatible with this version of ownCloud
+ // check if the app is compatible with this version of ownCloud
$version=OC_Util::getVersion();
- if(!isset($info['require']) or ($version[0]>$info['require'])){
+ if(!isset($info['require']) or ($version[0]>$info['require'])){
OC_Log::write('core','App can\'t be installed because it is not compatible with this version of ownCloud',OC_Log::ERROR);
OC_Helper::rmdirr($extractDir);
- return false;
+ return false;
}
//check if an app with the same id is already installed
@@ -339,12 +339,12 @@ class OC_Installer{
}
- /**
- * check the code of an app with some static code checks
- * @param string $folder the folder of the app to check
- * @returns true for app is o.k. and false for app is not o.k.
- */
- public static function checkCode($appname,$folder){
+ /**
+ * check the code of an app with some static code checks
+ * @param string $folder the folder of the app to check
+ * @returns true for app is o.k. and false for app is not o.k.
+ */
+ public static function checkCode($appname,$folder){
$blacklist=array(
'exec(',
@@ -377,9 +377,7 @@ class OC_Installer{
return true;
}else{
- return true;
+ return true;
}
- }
-
-
+ }
}
diff --git a/lib/json.php b/lib/json.php
index c49b831c12b..b46878375d5 100644
--- a/lib/json.php
+++ b/lib/json.php
@@ -94,12 +94,12 @@ class OC_JSON{
* Encode and print $data in json format
*/
public static function encodedPrint($data,$setContentType=true){
- // Disable mimesniffing, don't move this to setContentTypeHeader!
- header( 'X-Content-Type-Options: nosniff' );
- if($setContentType){
- self::setContentTypeHeader();
- }
- array_walk_recursive($data, array('OC_JSON', 'to_string'));
- echo json_encode($data);
+ // Disable mimesniffing, don't move this to setContentTypeHeader!
+ header( 'X-Content-Type-Options: nosniff' );
+ if($setContentType){
+ self::setContentTypeHeader();
+ }
+ array_walk_recursive($data, array('OC_JSON', 'to_string'));
+ echo json_encode($data);
}
}
diff --git a/lib/l10n.php b/lib/l10n.php
index de8514573d3..e7f5ffea0e4 100644
--- a/lib/l10n.php
+++ b/lib/l10n.php
@@ -154,8 +154,15 @@ class OC_L10N{
*
* Returns the translation. If no translation is found, $textArray will be
* returned.
+ *
+ *
+ * @deprecated deprecated since ownCloud version 5.0
+ * This method will probably be removed with ownCloud 6.0
+ *
+ *
*/
public function tA($textArray){
+ OC_Log::write('core', 'DEPRECATED: the method tA is deprecated and will be removed soon.',OC_Log::WARN);
$result = array();
foreach($textArray as $key => $text){
$result[$key] = (string)$this->t($text);
diff --git a/lib/migrate.php b/lib/migrate.php
index f788a637d3c..1b6367ed6ec 100644
--- a/lib/migrate.php
+++ b/lib/migrate.php
@@ -91,7 +91,7 @@ class OC_Migrate{
if( self::$exporttype == 'user' ){
// Check user exists
if( !is_null($uid) ){
- $db = new OC_User_Database;
+ $db = new OC_User_Database;
if( !$db->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 ) );
diff --git a/lib/mimetypes.fixlist.php b/lib/mimetypes.fixlist.php
deleted file mode 100644
index 13e3f16b369..00000000000
--- a/lib/mimetypes.fixlist.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-return array(
- 'ics'=>'text/calendar',
- 'ical'=>'text/calendar',
- 'js'=>'application/javascript',
- 'odt'=>'application/vnd.oasis.opendocument.text',
- 'ods'=>'application/vnd.oasis.opendocument.spreadsheet',
- 'odg'=>'application/vnd.oasis.opendocument.graphics',
- 'odp'=>'application/vnd.oasis.opendocument.presentation',
- 'pl'=>'text/x-script.perl',
- 'py'=>'text/x-script.phyton',
- 'vcf' => 'text/vcard',
- 'vcard' => 'text/vcard',
- 'doc'=>'application/msword',
- 'docx'=>'application/msword',
- 'xls'=>'application/msexcel',
- 'xlsx'=>'application/msexcel',
- 'ppt'=>'application/mspowerpoint',
- 'pptx'=>'application/mspowerpoint',
- 'sgf' => 'application/sgf',
- 'cdr' => 'application/coreldraw'
-);
diff --git a/lib/mimetypes.list.php b/lib/mimetypes.list.php
index ccf47999b1c..f7207493f7f 100644
--- a/lib/mimetypes.list.php
+++ b/lib/mimetypes.list.php
@@ -78,5 +78,16 @@ return array(
'mpeg'=>'video/mpeg',
'mov'=>'video/quicktime',
'webm'=>'video/webm',
- 'wmv'=>'video/x-ms-asf'
+ 'wmv'=>'video/x-ms-asf',
+ 'py'=>'text/x-script.phyton',
+ 'vcf' => 'text/vcard',
+ 'vcard' => 'text/vcard',
+ 'doc'=>'application/msword',
+ 'docx'=>'application/msword',
+ 'xls'=>'application/msexcel',
+ 'xlsx'=>'application/msexcel',
+ 'ppt'=>'application/mspowerpoint',
+ 'pptx'=>'application/mspowerpoint',
+ 'sgf' => 'application/sgf',
+ 'cdr' => 'application/coreldraw',
);
diff --git a/lib/minimizer.php b/lib/minimizer.php
index 3bf5ff9980b..3dc89e331a6 100644
--- a/lib/minimizer.php
+++ b/lib/minimizer.php
@@ -46,3 +46,13 @@ abstract class OC_Minimizer {
echo $out;
}
}
+
+if (!function_exists('gzdecode')) {
+ function gzdecode($data,$maxlength=null,&$filename='',&$error='')
+ {
+ if (strcmp(substr($data,0,9),"\x1f\x8b\x8\0\0\0\0\0\0")) {
+ return null; // Not the GZIP format we expect (See RFC 1952)
+ }
+ return gzinflate(substr($data,10,-8));
+ }
+}
diff --git a/lib/ocs.php b/lib/ocs.php
index 1be41202d78..3157aae99e6 100644
--- a/lib/ocs.php
+++ b/lib/ocs.php
@@ -4,7 +4,9 @@
* ownCloud
*
* @author Frank Karlitschek
+* @author Michael Gapczynski
* @copyright 2012 Frank Karlitschek frank@owncloud.org
+* @copyright 2012 Michael Gapczynski mtgap@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
@@ -29,429 +31,453 @@
*/
class OC_OCS {
- /**
- * reads input date from get/post/cookies and converts the date to a special data-type
- *
- * @param variable $key
- * @param variable-type $type
- * @param priority $getpriority
- * @param default $default
- * @return data
- */
- public static function readData($key,$type='raw',$getpriority=false,$default='') {
- if($getpriority) {
- if(isset($_GET[$key])) {
- $data=$_GET[$key];
- } elseif(isset($_POST[$key])) {
- $data=$_POST[$key];
- } else {
- if($default=='') {
- if(($type=='int') or ($type=='float')) $data=0; else $data='';
- } else {
- $data=$default;
- }
- }
- } else {
- if(isset($_POST[$key])) {
- $data=$_POST[$key];
- } elseif(isset($_GET[$key])) {
- $data=$_GET[$key];
- } elseif(isset($_COOKIE[$key])) {
- $data=$_COOKIE[$key];
- } else {
- if($default=='') {
- if(($type=='int') or ($type=='float')) $data=0; else $data='';
- } else {
- $data=$default;
- }
- }
- }
-
- if($type=='raw') return($data);
- elseif($type=='text') return(addslashes(strip_tags($data)));
- elseif($type=='int') { $data = (int) $data; return($data); }
- elseif($type=='float') { $data = (float) $data; return($data); }
- elseif($type=='array') { $data = $data; return($data); }
- }
-
-
- /**
- main function to handle the REST request
- **/
- public static function handle() {
-
- // overwrite the 404 error page returncode
- header("HTTP/1.0 200 OK");
-
-
- if($_SERVER['REQUEST_METHOD'] == 'GET') {
- $method='get';
- }elseif($_SERVER['REQUEST_METHOD'] == 'PUT') {
- $method='put';
- parse_str(file_get_contents("php://input"),$put_vars);
- }elseif($_SERVER['REQUEST_METHOD'] == 'POST') {
- $method='post';
- }else{
- echo('internal server error: method not supported');
- exit();
- }
-
- // preprocess url
- $url=$_SERVER['REQUEST_URI'];
- if(substr($url,(strlen($url)-1))<>'/') $url.='/';
- $ex=explode('/',$url);
- $paracount=count($ex);
-
- // eventhandler
- // CONFIG
- // apiconfig - GET - CONFIG
- if(($method=='get') and (strtolower($ex[$paracount-3])=='v1.php') and (strtolower($ex[$paracount-2])=='config')){
- $format=OC_OCS::readdata('format','text');
- OC_OCS::apiconfig($format);
-
- // PERSON
- // personcheck - POST - PERSON/CHECK
- }elseif(($method=='post') and (strtolower($ex[$paracount-4])=='v1.php') and (strtolower($ex[$paracount-3])=='person') and (strtolower($ex[$paracount-2])=='check')){
- $format=OC_OCS::readdata('format','text');
- $login=OC_OCS::readdata('login','text');
- $passwd=OC_OCS::readdata('password','text');
- OC_OCS::personcheck($format,$login,$passwd);
-
- // ACTIVITY
- // activityget - GET ACTIVITY page,pagesize als urlparameter
- }elseif(($method=='get') and (strtolower($ex[$paracount-3])=='v1.php')and (strtolower($ex[$paracount-2])=='activity')){
- $format=OC_OCS::readdata('format','text');
- $page=OC_OCS::readdata('page','int');
- $pagesize=OC_OCS::readdata('pagesize','int');
- if($pagesize<1 or $pagesize>100) $pagesize=10;
- OC_OCS::activityget($format,$page,$pagesize);
-
- // activityput - POST ACTIVITY
- }elseif(($method=='post') and (strtolower($ex[$paracount-3])=='v1.php')and (strtolower($ex[$paracount-2])=='activity')){
- $format=OC_OCS::readdata('format','text');
- $message=OC_OCS::readdata('message','text');
- OC_OCS::activityput($format,$message);
-
- // PRIVATEDATA
- // get - GET DATA
- }elseif(($method=='get') and (strtolower($ex[$paracount-4])=='v1.php')and (strtolower($ex[$paracount-2])=='getattribute')){
- $format=OC_OCS::readdata('format','text');
- OC_OCS::privateDataGet($format);
-
- }elseif(($method=='get') and (strtolower($ex[$paracount-5])=='v1.php')and (strtolower($ex[$paracount-3])=='getattribute')){
- $format=OC_OCS::readdata('format','text');
- $app=$ex[$paracount-2];
- OC_OCS::privateDataGet($format, $app);
- }elseif(($method=='get') and (strtolower($ex[$paracount-6])=='v1.php')and (strtolower($ex[$paracount-4])=='getattribute')){
- $format=OC_OCS::readdata('format','text');
- $key=$ex[$paracount-2];
- $app=$ex[$paracount-3];
- OC_OCS::privateDataGet($format, $app,$key);
-
- // set - POST DATA
- }elseif(($method=='post') and (strtolower($ex[$paracount-6])=='v1.php')and (strtolower($ex[$paracount-4])=='setattribute')){
- $format=OC_OCS::readdata('format','text');
- $key=$ex[$paracount-2];
- $app=$ex[$paracount-3];
- $value=OC_OCS::readdata('value','text');
- OC_OCS::privatedataset($format, $app, $key, $value);
- // delete - POST DATA
- }elseif(($method=='post') and (strtolower($ex[$paracount-6])=='v1.php')and (strtolower($ex[$paracount-4])=='deleteattribute')){
- $format=OC_OCS::readdata('format','text');
- $key=$ex[$paracount-2];
- $app=$ex[$paracount-3];
- OC_OCS::privatedatadelete($format, $app, $key);
-
- }else{
- $format=OC_OCS::readdata('format','text');
- $txt='Invalid query, please check the syntax. API specifications are here: http://www.freedesktop.org/wiki/Specifications/open-collaboration-services. DEBUG OUTPUT:'."\n";
- $txt.=OC_OCS::getdebugoutput();
- echo(OC_OCS::generatexml($format,'failed',999,$txt));
- }
- exit();
- }
-
- /**
- * generated some debug information to make it easier to find faild API calls
- * @return debug data string
- */
- private static function getDebugOutput() {
- $txt='';
- $txt.="debug output:\n";
- if(isset($_SERVER['REQUEST_METHOD'])) $txt.='http request method: '.$_SERVER['REQUEST_METHOD']."\n";
- if(isset($_SERVER['REQUEST_URI'])) $txt.='http request uri: '.$_SERVER['REQUEST_URI']."\n";
- if(isset($_GET)) foreach($_GET as $key=>$value) $txt.='get parameter: '.$key.'->'.$value."\n";
- if(isset($_POST)) foreach($_POST as $key=>$value) $txt.='post parameter: '.$key.'->'.$value."\n";
- return($txt);
- }
-
- /**
- * checks if the user is authenticated
- * checks the IP whitlist, apikeys and login/password combination
- * if $forceuser is true and the authentication failed it returns an 401 http response.
- * if $forceuser is false and authentification fails it returns an empty username string
- * @param bool $forceuser
- * @return username string
- */
- private static function checkPassword($forceuser=true) {
- //valid user account ?
- if(isset($_SERVER['PHP_AUTH_USER'])) $authuser=$_SERVER['PHP_AUTH_USER']; else $authuser='';
- if(isset($_SERVER['PHP_AUTH_PW'])) $authpw=$_SERVER['PHP_AUTH_PW']; else $authpw='';
-
- if(empty($authuser)) {
- if($forceuser){
- header('WWW-Authenticate: Basic realm="your valid user account or api key"');
- header('HTTP/1.0 401 Unauthorized');
- exit;
- }else{
- $identifieduser='';
- }
- }else{
- if(!OC_User::login($authuser,$authpw)){
- if($forceuser){
- header('WWW-Authenticate: Basic realm="your valid user account or api key"');
- header('HTTP/1.0 401 Unauthorized');
- exit;
- }else{
- $identifieduser='';
- }
- }else{
- $identifieduser=$authuser;
- }
- }
-
- return($identifieduser);
- }
-
-
- /**
- * generates the xml or json response for the API call from an multidimenional data array.
- * @param string $format
- * @param string $status
- * @param string $statuscode
- * @param string $message
- * @param array $data
- * @param string $tag
- * @param string $tagattribute
- * @param int $dimension
- * @param int $itemscount
- * @param int $itemsperpage
- * @return string xml/json
- */
- private static function generateXml($format,$status,$statuscode,$message,$data=array(),$tag='',$tagattribute='',$dimension=-1,$itemscount='',$itemsperpage='') {
- if($format=='json') {
-
- $json=array();
- $json['status']=$status;
- $json['statuscode']=$statuscode;
- $json['message']=$message;
- $json['totalitems']=$itemscount;
- $json['itemsperpage']=$itemsperpage;
- $json['data']=$data;
- return(json_encode($json));
-
-
- }else{
- $txt='';
- $writer = xmlwriter_open_memory();
- xmlwriter_set_indent( $writer, 2 );
- xmlwriter_start_document($writer );
- xmlwriter_start_element($writer,'ocs');
- xmlwriter_start_element($writer,'meta');
- xmlwriter_write_element($writer,'status',$status);
- xmlwriter_write_element($writer,'statuscode',$statuscode);
- xmlwriter_write_element($writer,'message',$message);
- if($itemscount<>'') xmlwriter_write_element($writer,'totalitems',$itemscount);
- if(!empty($itemsperpage)) xmlwriter_write_element($writer,'itemsperpage',$itemsperpage);
- xmlwriter_end_element($writer);
- if($dimension=='0') {
- // 0 dimensions
- xmlwriter_write_element($writer,'data',$data);
-
- }elseif($dimension=='1') {
- xmlwriter_start_element($writer,'data');
- foreach($data as $key=>$entry) {
- xmlwriter_write_element($writer,$key,$entry);
- }
- xmlwriter_end_element($writer);
-
- }elseif($dimension=='2') {
- xmlwriter_start_element($writer,'data');
- foreach($data as $entry) {
- xmlwriter_start_element($writer,$tag);
- if(!empty($tagattribute)) {
- xmlwriter_write_attribute($writer,'details',$tagattribute);
- }
- foreach($entry as $key=>$value) {
- if(is_array($value)){
- foreach($value as $k=>$v) {
- xmlwriter_write_element($writer,$k,$v);
- }
- } else {
- xmlwriter_write_element($writer,$key,$value);
- }
- }
- xmlwriter_end_element($writer);
- }
- xmlwriter_end_element($writer);
-
- }elseif($dimension=='3') {
- xmlwriter_start_element($writer,'data');
- foreach($data as $entrykey=>$entry) {
- xmlwriter_start_element($writer,$tag);
- if(!empty($tagattribute)) {
- xmlwriter_write_attribute($writer,'details',$tagattribute);
- }
- foreach($entry as $key=>$value) {
- if(is_array($value)){
- xmlwriter_start_element($writer,$entrykey);
- foreach($value as $k=>$v) {
- xmlwriter_write_element($writer,$k,$v);
- }
- xmlwriter_end_element($writer);
- } else {
- xmlwriter_write_element($writer,$key,$value);
- }
- }
- xmlwriter_end_element($writer);
- }
- xmlwriter_end_element($writer);
- }elseif($dimension=='dynamic') {
- xmlwriter_start_element($writer,'data');
- OC_OCS::toxml($writer,$data,'comment');
- xmlwriter_end_element($writer);
- }
-
- xmlwriter_end_element($writer);
-
- xmlwriter_end_document( $writer );
- $txt.=xmlwriter_output_memory( $writer );
- unset($writer);
- return($txt);
- }
- }
-
- public static function toXml($writer,$data,$node) {
- foreach($data as $key => $value) {
- if (is_numeric($key)) {
- $key = $node;
- }
- if (is_array($value)){
- xmlwriter_start_element($writer,$key);
- OC_OCS::toxml($writer,$value,$node);
- xmlwriter_end_element($writer);
- }else{
- xmlwriter_write_element($writer,$key,$value);
- }
-
- }
- }
-
-
-
-
- /**
- * return the config data of this server
- * @param string $format
- * @return string xml/json
- */
- private static function apiConfig($format) {
- $user=OC_OCS::checkpassword(false);
- $url=substr(OCP\Util::getServerHost().$_SERVER['SCRIPT_NAME'],0,-11).'';
-
- $xml['version']='1.5';
- $xml['website']='ownCloud';
- $xml['host']=OCP\Util::getServerHost();
- $xml['contact']='';
- $xml['ssl']='false';
- echo(OC_OCS::generatexml($format,'ok',100,'',$xml,'config','',1));
- }
-
-
- /**
- * check if the provided login/apikey/password is valid
- * @param string $format
- * @param string $login
- * @param string $passwd
- * @return string xml/json
- */
- private static function personCheck($format,$login,$passwd) {
- if($login<>''){
- if(OC_User::login($login,$passwd)){
- $xml['person']['personid']=$login;
- echo(OC_OCS::generatexml($format,'ok',100,'',$xml,'person','check',2));
- }else{
- echo(OC_OCS::generatexml($format,'failed',102,'login not valid'));
- }
- }else{
- echo(OC_OCS::generatexml($format,'failed',101,'please specify all mandatory fields'));
- }
- }
-
-
-
- // ACTIVITY API #############################################
-
- /**
- * get my activities
- * @param string $format
- * @param string $page
- * @param string $pagesize
- * @return string xml/json
- */
- private static function activityGet($format,$page,$pagesize) {
- $user=OC_OCS::checkpassword();
-
- //TODO
-
- $txt=OC_OCS::generatexml($format,'ok',100,'',$xml,'activity','full',2,$totalcount,$pagesize);
- echo($txt);
- }
-
- /**
- * submit a activity
- * @param string $format
- * @param string $message
- * @return string xml/json
- */
- private static function activityPut($format,$message) {
- // not implemented in ownCloud
- $user=OC_OCS::checkpassword();
- echo(OC_OCS::generatexml($format,'ok',100,''));
- }
-
- // PRIVATEDATA API #############################################
-
- /**
- * get private data and create the xml for ocs
- * @param string $format
- * @param string $app
- * @param string $key
- * @return string xml/json
- */
- private static function privateDataGet($format,$app="",$key="") {
- $user=OC_OCS::checkpassword();
- $result=OC_OCS::getData($user,$app,$key);
- $xml=array();
- foreach($result as $i=>$log) {
- $xml[$i]['key']=$log['key'];
- $xml[$i]['app']=$log['app'];
- $xml[$i]['value']=$log['value'];
- }
-
-
- $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'privatedata', 'full', 2, count($xml), 0);//TODO: replace 'privatedata' with 'attribute' once a new libattice has been released that works with it
- echo($txt);
- }
-
- /**
- * set private data referenced by $key to $value and generate the xml for ocs
- * @param string $format
- * @param string $app
- * @param string $key
- * @param string $value
- * @return string xml/json
- */
+ /**
+ * reads input date from get/post/cookies and converts the date to a special data-type
+ *
+ * @param string HTTP method to read the key from
+ * @param string Parameter to read
+ * @param string Variable type to format data
+ * @param mixed Default value to return if the key is not found
+ * @return mixed Data or if the key is not found and no default is set it will exit with a 400 Bad request
+ */
+ public static function readData($method, $key, $type = 'raw', $default = null) {
+ if ($method == 'get') {
+ if (isset($_GET[$key])) {
+ $data = $_GET[$key];
+ } else if (isset($default)) {
+ return $default;
+ } else {
+ $data = false;
+ }
+ } else if ($method == 'post') {
+ if (isset($_POST[$key])) {
+ $data = $_POST[$key];
+ } else if (isset($default)) {
+ return $default;
+ } else {
+ $data = false;
+ }
+ }
+ if ($data === false) {
+ echo self::generateXml('', 'fail', 400, 'Bad request. Please provide a valid '.$key);
+ exit();
+ } else {
+ // NOTE: Is the raw type necessary? It might be a little risky without sanitization
+ if ($type == 'raw') return $data;
+ elseif ($type == 'text') return OC_Util::sanitizeHTML($data);
+ elseif ($type == 'int') return (int) $data;
+ elseif ($type == 'float') return (float) $data;
+ elseif ($type == 'array') return OC_Util::sanitizeHTML($data);
+ else return OC_Util::sanitizeHTML($data);
+ }
+ }
+
+ /**
+ main function to handle the REST request
+ **/
+ public static function handle() {
+ // overwrite the 404 error page returncode
+ header("HTTP/1.0 200 OK");
+
+
+ if($_SERVER['REQUEST_METHOD'] == 'GET') {
+ $method='get';
+ }elseif($_SERVER['REQUEST_METHOD'] == 'PUT') {
+ $method='put';
+ parse_str(file_get_contents("php://input"),$put_vars);
+ }elseif($_SERVER['REQUEST_METHOD'] == 'POST') {
+ $method='post';
+ }else{
+ echo('internal server error: method not supported');
+ exit();
+ }
+
+ // preprocess url
+ $url = strtolower($_SERVER['REQUEST_URI']);
+ if(substr($url,(strlen($url)-1))<>'/') $url.='/';
+ $ex=explode('/',$url);
+ $paracount=count($ex);
+ $format = self::readData($method, 'format', 'text', '');
+
+ // eventhandler
+ // CONFIG
+ // apiconfig - GET - CONFIG
+ if(($method=='get') and ($ex[$paracount-3] == 'v1.php') and ($ex[$paracount-2] == 'config')){
+ OC_OCS::apiconfig($format);
+
+ // PERSON
+ // personcheck - POST - PERSON/CHECK
+ }elseif(($method=='post') and ($ex[$paracount-4] == 'v1.php') and ($ex[$paracount-3]=='person') and ($ex[$paracount-2] == 'check')){
+ $login = self::readData($method, 'login', 'text');
+ $passwd = self::readData($method, 'password', 'text');
+ OC_OCS::personcheck($format,$login,$passwd);
+
+ // ACTIVITY
+ // activityget - GET ACTIVITY page,pagesize als urlparameter
+ }elseif(($method=='get') and ($ex[$paracount-3] == 'v1.php') and ($ex[$paracount-2] == 'activity')){
+ $page = self::readData($method, 'page', 'int', 0);
+ $pagesize = self::readData($method, 'pagesize','int', 10);
+ if($pagesize<1 or $pagesize>100) $pagesize=10;
+ OC_OCS::activityget($format,$page,$pagesize);
+
+ // activityput - POST ACTIVITY
+ }elseif(($method=='post') and ($ex[$paracount-3] == 'v1.php') and ($ex[$paracount-2] == 'activity')){
+ $message = self::readData($method, 'message', 'text');
+ OC_OCS::activityput($format,$message);
+
+
+ // PRIVATEDATA
+ // get - GET DATA
+ }elseif(($method=='get') and ($ex[$paracount-4] == 'v1.php') and ($ex[$paracount-2] == 'getattribute')){
+ OC_OCS::privateDataGet($format);
+
+ }elseif(($method=='get') and ($ex[$paracount-5] == 'v1.php') and ($ex[$paracount-3] == 'getattribute')){
+ $app=$ex[$paracount-2];
+ OC_OCS::privateDataGet($format, $app);
+ }elseif(($method=='get') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-4] == 'getattribute')){
+
+ $key=$ex[$paracount-2];
+ $app=$ex[$paracount-3];
+ OC_OCS::privateDataGet($format, $app,$key);
+
+ // set - POST DATA
+ }elseif(($method=='post') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-4] == 'setattribute')){
+ $key=$ex[$paracount-2];
+ $app=$ex[$paracount-3];
+ $value = self::readData($method, 'value', 'text');
+ OC_OCS::privatedataset($format, $app, $key, $value);
+ // delete - POST DATA
+ }elseif(($method=='post') and ($ex[$paracount-6] =='v1.php') and ($ex[$paracount-4] == 'deleteattribute')){
+ $key=$ex[$paracount-2];
+ $app=$ex[$paracount-3];
+ OC_OCS::privatedatadelete($format, $app, $key);
+
+ // CLOUD
+ // systemWebApps
+ }elseif(($method=='get') and ($ex[$paracount-5] == 'v1.php') and ($ex[$paracount-4]=='cloud') and ($ex[$paracount-3] == 'system') and ($ex[$paracount-2] == 'webapps')){
+ OC_OCS::systemwebapps($format);
+
+ // quotaget
+ }elseif(($method=='get') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'quota')){
+ $user=$ex[$paracount-3];
+ OC_OCS::quotaget($format,$user);
+
+ // quotaset
+ }elseif(($method=='post') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'quota')){
+ $user=$ex[$paracount-3];
+ $quota = self::readData('post', 'quota', 'int');
+ OC_OCS::quotaset($format,$user,$quota);
+
+ // keygetpublic
+ }elseif(($method=='get') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'publickey')){
+ $user=$ex[$paracount-3];
+ OC_OCS::publicKeyGet($format,$user);
+
+ // keygetprivate
+ }elseif(($method=='get') and ($ex[$paracount-6] == 'v1.php') and ($ex[$paracount-5]=='cloud') and ($ex[$paracount-4] == 'user') and ($ex[$paracount-2] == 'privatekey')){
+ $user=$ex[$paracount-3];
+ OC_OCS::privateKeyGet($format,$user);
+
+
+// add more calls here
+// please document all the call in the draft spec
+// http://www.freedesktop.org/wiki/Specifications/open-collaboration-services-1.7#CLOUD
+
+// TODO:
+// users
+// groups
+// bookmarks
+// sharing
+// versioning
+// news (rss)
+
+
+
+ }else{
+ $txt='Invalid query, please check the syntax. API specifications are here: http://www.freedesktop.org/wiki/Specifications/open-collaboration-services. DEBUG OUTPUT:'."\n";
+ $txt.=OC_OCS::getdebugoutput();
+ echo(OC_OCS::generatexml($format,'failed',999,$txt));
+ }
+ exit();
+ }
+
+ /**
+ * generated some debug information to make it easier to find faild API calls
+ * @return debug data string
+ */
+ private static function getDebugOutput() {
+ $txt='';
+ $txt.="debug output:\n";
+ if(isset($_SERVER['REQUEST_METHOD'])) $txt.='http request method: '.$_SERVER['REQUEST_METHOD']."\n";
+ if(isset($_SERVER['REQUEST_URI'])) $txt.='http request uri: '.$_SERVER['REQUEST_URI']."\n";
+ if(isset($_GET)) foreach($_GET as $key=>$value) $txt.='get parameter: '.$key.'->'.$value."\n";
+ if(isset($_POST)) foreach($_POST as $key=>$value) $txt.='post parameter: '.$key.'->'.$value."\n";
+ return($txt);
+ }
+
+ /**
+ * checks if the user is authenticated
+ * checks the IP whitlist, apikeys and login/password combination
+ * if $forceuser is true and the authentication failed it returns an 401 http response.
+ * if $forceuser is false and authentification fails it returns an empty username string
+ * @param bool $forceuser
+ * @return username string
+ */
+ private static function checkPassword($forceuser=true) {
+ //valid user account ?
+ if(isset($_SERVER['PHP_AUTH_USER'])) $authuser=$_SERVER['PHP_AUTH_USER']; else $authuser='';
+ if(isset($_SERVER['PHP_AUTH_PW'])) $authpw=$_SERVER['PHP_AUTH_PW']; else $authpw='';
+
+ if(empty($authuser)) {
+ if($forceuser){
+ header('WWW-Authenticate: Basic realm="your valid user account or api key"');
+ header('HTTP/1.0 401 Unauthorized');
+ exit;
+ }else{
+ $identifieduser='';
+ }
+ }else{
+ if(!OC_User::login($authuser,$authpw)){
+ if($forceuser){
+ header('WWW-Authenticate: Basic realm="your valid user account or api key"');
+ header('HTTP/1.0 401 Unauthorized');
+ exit;
+ }else{
+ $identifieduser='';
+ }
+ }else{
+ $identifieduser=$authuser;
+ }
+ }
+
+ return($identifieduser);
+ }
+
+
+ /**
+ * generates the xml or json response for the API call from an multidimenional data array.
+ * @param string $format
+ * @param string $status
+ * @param string $statuscode
+ * @param string $message
+ * @param array $data
+ * @param string $tag
+ * @param string $tagattribute
+ * @param int $dimension
+ * @param int $itemscount
+ * @param int $itemsperpage
+ * @return string xml/json
+ */
+ private static function generateXml($format,$status,$statuscode,$message,$data=array(),$tag='',$tagattribute='',$dimension=-1,$itemscount='',$itemsperpage='') {
+ if($format=='json') {
+ $json=array();
+ $json['status']=$status;
+ $json['statuscode']=$statuscode;
+ $json['message']=$message;
+ $json['totalitems']=$itemscount;
+ $json['itemsperpage']=$itemsperpage;
+ $json['data']=$data;
+ return(json_encode($json));
+ }else{
+ $txt='';
+ $writer = xmlwriter_open_memory();
+ xmlwriter_set_indent( $writer, 2 );
+ xmlwriter_start_document($writer );
+ xmlwriter_start_element($writer,'ocs');
+ xmlwriter_start_element($writer,'meta');
+ xmlwriter_write_element($writer,'status',$status);
+ xmlwriter_write_element($writer,'statuscode',$statuscode);
+ xmlwriter_write_element($writer,'message',$message);
+ if($itemscount<>'') xmlwriter_write_element($writer,'totalitems',$itemscount);
+ if(!empty($itemsperpage)) xmlwriter_write_element($writer,'itemsperpage',$itemsperpage);
+ xmlwriter_end_element($writer);
+ if($dimension=='0') {
+ // 0 dimensions
+ xmlwriter_write_element($writer,'data',$data);
+
+ }elseif($dimension=='1') {
+ xmlwriter_start_element($writer,'data');
+ foreach($data as $key=>$entry) {
+ xmlwriter_write_element($writer,$key,$entry);
+ }
+ xmlwriter_end_element($writer);
+
+ }elseif($dimension=='2') {
+ xmlwriter_start_element($writer,'data');
+ foreach($data as $entry) {
+ xmlwriter_start_element($writer,$tag);
+ if(!empty($tagattribute)) {
+ xmlwriter_write_attribute($writer,'details',$tagattribute);
+ }
+ foreach($entry as $key=>$value) {
+ if(is_array($value)){
+ foreach($value as $k=>$v) {
+ xmlwriter_write_element($writer,$k,$v);
+ }
+ } else {
+ xmlwriter_write_element($writer,$key,$value);
+ }
+ }
+ xmlwriter_end_element($writer);
+ }
+ xmlwriter_end_element($writer);
+
+ }elseif($dimension=='3') {
+ xmlwriter_start_element($writer,'data');
+ foreach($data as $entrykey=>$entry) {
+ xmlwriter_start_element($writer,$tag);
+ if(!empty($tagattribute)) {
+ xmlwriter_write_attribute($writer,'details',$tagattribute);
+ }
+ foreach($entry as $key=>$value) {
+ if(is_array($value)){
+ xmlwriter_start_element($writer,$entrykey);
+ foreach($value as $k=>$v) {
+ xmlwriter_write_element($writer,$k,$v);
+ }
+ xmlwriter_end_element($writer);
+ } else {
+ xmlwriter_write_element($writer,$key,$value);
+ }
+ }
+ xmlwriter_end_element($writer);
+ }
+ xmlwriter_end_element($writer);
+ }elseif($dimension=='dynamic') {
+ xmlwriter_start_element($writer,'data');
+ OC_OCS::toxml($writer,$data,'comment');
+ xmlwriter_end_element($writer);
+ }
+
+ xmlwriter_end_element($writer);
+
+ xmlwriter_end_document( $writer );
+ $txt.=xmlwriter_output_memory( $writer );
+ unset($writer);
+ return($txt);
+ }
+ }
+
+ public static function toXml($writer,$data,$node) {
+ foreach($data as $key => $value) {
+ if (is_numeric($key)) {
+ $key = $node;
+ }
+ if (is_array($value)){
+ xmlwriter_start_element($writer,$key);
+ OC_OCS::toxml($writer,$value,$node);
+ xmlwriter_end_element($writer);
+ }else{
+ xmlwriter_write_element($writer,$key,$value);
+ }
+ }
+ }
+
+
+
+
+ /**
+ * return the config data of this server
+ * @param string $format
+ * @return string xml/json
+ */
+ private static function apiConfig($format) {
+ $user=OC_OCS::checkpassword(false);
+ $url=substr(OCP\Util::getServerHost().$_SERVER['SCRIPT_NAME'],0,-11).'';
+
+ $xml['version']='1.7';
+ $xml['website']='ownCloud';
+ $xml['host']=OCP\Util::getServerHost();
+ $xml['contact']='';
+ $xml['ssl']='false';
+ echo(OC_OCS::generatexml($format,'ok',100,'',$xml,'config','',1));
+ }
+
+
+ /**
+ * check if the provided login/apikey/password is valid
+ * @param string $format
+ * @param string $login
+ * @param string $passwd
+ * @return string xml/json
+ */
+ private static function personCheck($format,$login,$passwd) {
+ if($login<>''){
+ if(OC_User::login($login,$passwd)){
+ $xml['person']['personid']=$login;
+ echo(OC_OCS::generatexml($format,'ok',100,'',$xml,'person','check',2));
+ }else{
+ echo(OC_OCS::generatexml($format,'failed',102,'login not valid'));
+ }
+ }else{
+ echo(OC_OCS::generatexml($format,'failed',101,'please specify all mandatory fields'));
+ }
+ }
+
+
+
+ // ACTIVITY API #############################################
+
+ /**
+ * get my activities
+ * @param string $format
+ * @param string $page
+ * @param string $pagesize
+ * @return string xml/json
+ */
+ private static function activityGet($format,$page,$pagesize) {
+ $user=OC_OCS::checkpassword();
+
+ //TODO
+
+ $txt=OC_OCS::generatexml($format,'ok',100,'',$xml,'activity','full',2,$totalcount,$pagesize);
+ echo($txt);
+ }
+
+ /**
+ * submit a activity
+ * @param string $format
+ * @param string $message
+ * @return string xml/json
+ */
+ private static function activityPut($format,$message) {
+ // not implemented in ownCloud
+ $user=OC_OCS::checkpassword();
+ echo(OC_OCS::generatexml($format,'ok',100,''));
+ }
+
+ // PRIVATEDATA API #############################################
+
+ /**
+ * get private data and create the xml for ocs
+ * @param string $format
+ * @param string $app
+ * @param string $key
+ * @return string xml/json
+ */
+ private static function privateDataGet($format,$app="",$key="") {
+ $user=OC_OCS::checkpassword();
+ $result=OC_OCS::getData($user,$app,$key);
+ $xml=array();
+ foreach($result as $i=>$log) {
+ $xml[$i]['key']=$log['key'];
+ $xml[$i]['app']=$log['app'];
+ $xml[$i]['value']=$log['value'];
+ }
+
+
+ $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'privatedata', 'full', 2, count($xml), 0);//TODO: replace 'privatedata' with 'attribute' once a new libattice has been released that works with it
+ echo($txt);
+ }
+
+ /**
+ * set private data referenced by $key to $value and generate the xml for ocs
+ * @param string $format
+ * @param string $app
+ * @param string $key
+ * @param string $value
+ * @return string xml/json
+ */
private static function privateDataSet($format, $app, $key, $value) {
$user=OC_OCS::checkpassword();
if(OC_OCS::setData($user,$app,$key,$value)){
@@ -529,4 +555,134 @@ class OC_OCS {
public static function deleteData($user, $app, $key) {
return OC_Preferences::deleteKey($user,$app,$key);
}
+
+
+ // CLOUD API #############################################
+
+ /**
+ * get a list of installed web apps
+ * @param string $format
+ * @return string xml/json
+ */
+ private static function systemWebApps($format) {
+ $login=OC_OCS::checkpassword();
+ $apps=OC_App::getEnabledApps();
+ $values=array();
+ foreach($apps as $app) {
+ $info=OC_App::getAppInfo($app);
+ if(isset($info['standalone'])) {
+ $newvalue=array('name'=>$info['name'],'url'=>OC_Helper::linkToAbsolute($app,''),'icon'=>'');
+ $values[]=$newvalue;
+ }
+
+ }
+ $txt=OC_OCS::generatexml($format, 'ok', 100, '', $values, 'cloud', '', 2, 0, 0);
+ echo($txt);
+
+ }
+
+
+ /**
+ * get the quota of a user
+ * @param string $format
+ * @param string $user
+ * @return string xml/json
+ */
+ private static function quotaGet($format,$user) {
+ $login=OC_OCS::checkpassword();
+ if(OC_Group::inGroup($login, 'admin') or ($login==$user)) {
+
+ if(OC_User::userExists($user)){
+ // calculate the disc space
+ $user_dir = '/'.$user.'/files';
+ OC_Filesystem::init($user_dir);
+ $rootInfo=OC_FileCache::get('');
+ $sharedInfo=OC_FileCache::get('/Shared');
+ $used=$rootInfo['size']-$sharedInfo['size'];
+ $free=OC_Filesystem::free_space();
+ $total=$free+$used;
+ if($total==0) $total=1; // prevent division by zero
+ $relative=round(($used/$total)*10000)/100;
+
+ $xml=array();
+ $xml['quota']=$total;
+ $xml['free']=$free;
+ $xml['used']=$used;
+ $xml['relative']=$relative;
+
+ $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0);
+ echo($txt);
+ }else{
+ echo self::generateXml('', 'fail', 300, 'User does not exist');
+ }
+ }else{
+ echo self::generateXml('', 'fail', 300, 'You don´t have permission to access this ressource.');
+ }
+ }
+
+ /**
+ * set the quota of a user
+ * @param string $format
+ * @param string $user
+ * @param string $quota
+ * @return string xml/json
+ */
+ private static function quotaSet($format,$user,$quota) {
+ $login=OC_OCS::checkpassword();
+ if(OC_Group::inGroup($login, 'admin')) {
+
+ // todo
+ // not yet implemented
+ // add logic here
+ error_log('OCS call: user:'.$user.' quota:'.$quota);
+
+ $xml=array();
+ $txt=OC_OCS::generatexml($format, 'ok', 100, '', $xml, 'cloud', '', 1, 0, 0);
+ echo($txt);
+ }else{
+ echo self::generateXml('', 'fail', 300, 'You don´t have permission to access this ressource.');
+ }
+ }
+
+ /**
+ * get the public key of a user
+ * @param string $format
+ * @param string $user
+ * @return string xml/json
+ */
+ private static function publicKeyGet($format,$user) {
+ $login=OC_OCS::checkpassword();
+
+ if(OC_User::userExists($user)){
+ // calculate the disc space
+ $txt='this is the public key of '.$user;
+ echo($txt);
+ }else{
+ echo self::generateXml('', 'fail', 300, 'User does not exist');
+ }
+ }
+
+ /**
+ * get the private key of a user
+ * @param string $format
+ * @param string $user
+ * @return string xml/json
+ */
+ private static function privateKeyGet($format,$user) {
+ $login=OC_OCS::checkpassword();
+ if(OC_Group::inGroup($login, 'admin') or ($login==$user)) {
+
+ if(OC_User::userExists($user)){
+ // calculate the disc space
+ $txt='this is the private key of '.$user;
+ echo($txt);
+ }else{
+ echo self::generateXml('', 'fail', 300, 'User does not exist');
+ }
+ }else{
+ echo self::generateXml('', 'fail', 300, 'You don´t have permission to access this ressource.');
+ }
+ }
+
+
}
diff --git a/lib/ocsclient.php b/lib/ocsclient.php
index 951d761d7e6..ae35470cff6 100644
--- a/lib/ocsclient.php
+++ b/lib/ocsclient.php
@@ -71,7 +71,7 @@ class OC_OCSClient{
$tmp=$data->data;
$cats=array();
- foreach($tmp->category as $key=>$value) {
+ foreach($tmp->category as $value) {
$id= (int) $value->id;
$name= (string) $value->name;
diff --git a/lib/preferences.php b/lib/preferences.php
index f72378ce94f..c91423e69bc 100644
--- a/lib/preferences.php
+++ b/lib/preferences.php
@@ -165,7 +165,7 @@ class OC_Preferences{
public static function deleteKey( $user, $app, $key ){
// No need for more comments
$query = OC_DB::prepare( 'DELETE FROM *PREFIX*preferences WHERE userid = ? AND appid = ? AND configkey = ?' );
- $result = $query->execute( array( $user, $app, $key ));
+ $query->execute( array( $user, $app, $key ));
return true;
}
@@ -181,7 +181,7 @@ class OC_Preferences{
public static function deleteApp( $user, $app ){
// No need for more comments
$query = OC_DB::prepare( 'DELETE FROM *PREFIX*preferences WHERE userid = ? AND appid = ?' );
- $result = $query->execute( array( $user, $app ));
+ $query->execute( array( $user, $app ));
return true;
}
@@ -196,7 +196,7 @@ class OC_Preferences{
public static function deleteUser( $user ){
// No need for more comments
$query = OC_DB::prepare( 'DELETE FROM *PREFIX*preferences WHERE userid = ?' );
- $result = $query->execute( array( $user ));
+ $query->execute( array( $user ));
return true;
}
@@ -211,7 +211,7 @@ class OC_Preferences{
public static function deleteAppFromAllUsers( $app ){
// No need for more comments
$query = OC_DB::prepare( 'DELETE FROM *PREFIX*preferences WHERE appid = ?' );
- $result = $query->execute( array( $app ));
+ $query->execute( array( $app ));
return true;
}
diff --git a/lib/public/app.php b/lib/public/app.php
index 38c51af9cdb..28411933beb 100644
--- a/lib/public/app.php
+++ b/lib/public/app.php
@@ -34,28 +34,6 @@ namespace OCP;
* This class provides functions to manage apps in ownCloud
*/
class App {
-
- /**
- * @brief Makes owncloud aware of this app
- * @brief This call is deprecated and not necessary to use.
- * @param $data array with all information
- * @returns true/false
- *
- * This function registers the application. $data is an associative array.
- * The following keys are required:
- * - id: id of the application, has to be unique ('addressbook')
- * - name: Human readable name ('Addressbook')
- * - version: array with Version (major, minor, bugfix) ( array(1, 0, 2))
- *
- * The following keys are optional:
- * - order: integer, that influences the position of your application in
- * a list of applications. Lower values come first.
- *
- */
- public static function register( $data ){
- }
-
-
/**
* @brief adds an entry to the navigation
* @param $data array containing the data
diff --git a/lib/public/groupinterface.php b/lib/public/groupinterface.php
new file mode 100644
index 00000000000..97833028118
--- /dev/null
+++ b/lib/public/groupinterface.php
@@ -0,0 +1,31 @@
+<?php
+/**
+* ownCloud
+*
+* @author Arthur Schiwon
+* @copyright 2012 Arthur Schiwon blizzz@owncloud.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/>.
+*
+*/
+
+/**
+ * Public interface of ownCloud for apps to use.
+ * Group Class.
+ *
+ */
+
+namespace OCP;
+
+interface GroupInterface extends \OC_Group_Interface {} \ No newline at end of file
diff --git a/lib/public/json.php b/lib/public/json.php
index 1bc1e3ab4d5..99df79173eb 100644
--- a/lib/public/json.php
+++ b/lib/public/json.php
@@ -35,70 +35,137 @@ namespace OCP;
*/
class JSON {
-
/**
* @brief Encode and print $data in JSON format
* @param array $data The data to use
* @param string $setContentType the optional content type
+ * @return string json formatted string.
*/
public static function encodedPrint( $data, $setContentType=true ){
return(\OC_JSON::encodedPrint( $data, $setContentType ));
}
-
/**
- * @brief Check if the user is logged in, send json error msg if not
+ * Check if the user is logged in, send json error msg if not.
+ *
+ * This method checks if a user is logged in. If not, a json error
+ * response will be return and the method will exit from execution
+ * of the script.
+ * The returned json will be in the format:
+ *
+ * {"status":"error","data":{"message":"Authentication error."}}
+ *
+ * Add this call to the start of all ajax method files that requires
+ * an authenticated user.
+ *
+ * @return string json formatted error string if not authenticated.
*/
public static function checkLoggedIn(){
return(\OC_JSON::checkLoggedIn());
}
/**
- * @brief Check an ajax get/post call if the request token is valid.
- * @return json Error msg if not valid.
- */
+ * Check an ajax get/post call if the request token is valid.
+ *
+ * This method checks for a valid variable 'requesttoken' in $_GET,
+ * $_POST and $_SERVER. If a valid token is not found, a json error
+ * response will be return and the method will exit from execution
+ * of the script.
+ * The returned json will be in the format:
+ *
+ * {"status":"error","data":{"message":"Token expired. Please reload page."}}
+ *
+ * Add this call to the start of all ajax method files that creates,
+ * updates or deletes anything.
+ * In cases where you e.g. use an ajax call to load a dialog containing
+ * a submittable form, you will need to add the requesttoken first as a
+ * parameter to the ajax call, then assign it to the template and finally
+ * add a hidden input field also named 'requesttoken' containing the value.
+ *
+ * @return string json formatted error string if not valid.
+ */
public static function callCheck(){
return(\OC_JSON::callCheck());
}
/**
- * @brief Send json success msg
+ * Send json success msg
+ *
+ * Return a json success message with optional extra data.
+ * @see OCP\JSON::error() for the format to use.
+ *
* @param array $data The data to use
+ * @return string json formatted string.
*/
public static function success( $data = array() ){
return(\OC_JSON::success( $data ));
}
-
/**
- * @brief Send json error msg
+ * Send json error msg
+ *
+ * Return a json error message with optional extra data for
+ * error message or app specific data.
+ *
+ * Example use:
+ *
+ * $id = [some value]
+ * OCP\JSON::error(array('data':array('message':'An error happened', 'id': $id)));
+ *
+ * Will return the json formatted string:
+ *
+ * {"status":"error","data":{"message":"An error happened", "id":[some value]}}
+ *
* @param array $data The data to use
+ * @return string json formatted error string.
*/
public static function error( $data = array() ){
return(\OC_JSON::error( $data ));
}
-
/**
- * @brief set Content-Type header to jsonrequest
- * @param array $type The contwnt type header
- */
+ * @brief set Content-Type header to jsonrequest
+ * @param array $type The contwnt type header
+ * @return string json formatted string.
+ */
public static function setContentTypeHeader( $type='application/json' ){
return(\OC_JSON::setContentTypeHeader( $type ));
}
-
/**
- * @brief Check if the App is enabled and send JSON error message instead
- * @param string $app The app to check
- */
+ * Check if the App is enabled and send JSON error message instead
+ *
+ * This method checks if a specific app is enabled. If not, a json error
+ * response will be return and the method will exit from execution
+ * of the script.
+ * The returned json will be in the format:
+ *
+ * {"status":"error","data":{"message":"Application is not enabled."}}
+ *
+ * Add this call to the start of all ajax method files that requires
+ * a specific app to be enabled.
+ *
+ * @param string $app The app to check
+ * @return string json formatted string if not enabled.
+ */
public static function checkAppEnabled( $app ){
return(\OC_JSON::checkAppEnabled( $app ));
}
-
/**
- * @brief Check if the user is a admin, send json error msg if not
+ * Check if the user is a admin, send json error msg if not
+ *
+ * This method checks if the current user has admin rights. If not, a json error
+ * response will be return and the method will exit from execution
+ * of the script.
+ * The returned json will be in the format:
+ *
+ * {"status":"error","data":{"message":"Authentication error."}}
+ *
+ * Add this call to the start of all ajax method files that requires
+ * administrative rights.
+ *
+ * @return string json formatted string if not admin user.
*/
public static function checkAdminUser(){
return(\OC_JSON::checkAdminUser());
diff --git a/lib/public/userinterface.php b/lib/public/userinterface.php
new file mode 100644
index 00000000000..b73a8f8d8b0
--- /dev/null
+++ b/lib/public/userinterface.php
@@ -0,0 +1,31 @@
+<?php
+/**
+* ownCloud
+*
+* @author Arthur Schiwon
+* @copyright 2012 Arthur Schiwon blizzz@owncloud.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/>.
+*
+*/
+
+/**
+ * Public interface of ownCloud for apps to use.
+ * User Class.
+ *
+ */
+
+namespace OCP;
+
+interface UserInterface extends \OC_User_Interface {} \ No newline at end of file
diff --git a/lib/public/util.php b/lib/public/util.php
index 43f9e3cee5d..75ca29f7129 100644
--- a/lib/public/util.php
+++ b/lib/public/util.php
@@ -320,4 +320,15 @@ class Util {
public static function mb_str_replace($search, $replace, $subject, $encoding = 'UTF-8', &$count = null) {
return(\OC_Helper::mb_str_replace($search, $replace, $subject, $encoding, $count));
}
+
+ /**
+ * @brief performs a search in a nested array
+ * @param haystack the array to be searched
+ * @param needle the search string
+ * @param $index optional, only search this key name
+ * @return the key of the matching field, otherwise false
+ */
+ public static function recursiveArraySearch($haystack, $needle, $index = null) {
+ return(\OC_Helper::recursiveArraySearch($haystack, $needle, $index));
+ }
}
diff --git a/lib/search/provider.php b/lib/search/provider.php
index 838ab696d04..b3ee79b4770 100644
--- a/lib/search/provider.php
+++ b/lib/search/provider.php
@@ -2,13 +2,17 @@
/**
* provides search functionalty
*/
-class OC_Search_Provider {
- public function __construct($options){}
+abstract class OC_Search_Provider {
+ private $options;
+
+ public function __construct($options){
+ $this->options=$options;
+ }
/**
* search for $query
* @param string $query
* @return array An array of OC_Search_Result's
*/
- public function search($query){}
+ abstract public function search($query);
}
diff --git a/lib/setup.php b/lib/setup.php
index 2f73c486c9c..4d71bed86e2 100644
--- a/lib/setup.php
+++ b/lib/setup.php
@@ -102,7 +102,6 @@ class OC_Setup {
}
else {
$oldUser=OC_Config::getValue('dbuser', false);
- $oldPassword=OC_Config::getValue('dbpassword', false);
$query="SELECT user FROM mysql.user WHERE user='$dbuser'"; //this should be enough to check for admin rights in mysql
if(mysql_query($query, $connection)) {
@@ -257,7 +256,7 @@ class OC_Setup {
OC_Installer::installShippedApps();
//create htaccess files for apache hosts
- if (strstr($_SERVER['SERVER_SOFTWARE'], 'Apache')) {
+ if (isset($_SERVER['SERVER_SOFTWARE']) && strstr($_SERVER['SERVER_SOFTWARE'], 'Apache')) {
self::createHtaccess();
}
diff --git a/lib/streamwrappers.php b/lib/streamwrappers.php
index f1e0fa0e1d9..f502c6170bd 100644
--- a/lib/streamwrappers.php
+++ b/lib/streamwrappers.php
@@ -1,6 +1,4 @@
<?php
-global $FAKEDIRS;
-$FAKEDIRS=array();
class OC_FakeDirStream{
public static $dirs=array();
@@ -8,8 +6,6 @@ class OC_FakeDirStream{
private $index;
public function dir_opendir($path,$options){
- global $FAKEDIRS;
- $url=parse_url($path);
$this->name=substr($path,strlen('fakedir://'));
$this->index=0;
if(!isset(self::$dirs[$this->name])){
@@ -161,7 +157,6 @@ class OC_StaticStreamWrapper {
public function stream_write($data) {
if (!$this->writable) return 0;
$size = strlen($data);
- $len = strlen(self::$data[$this->path]);
if ($this->stream_eof()) {
self::$data[$this->path] .= $data;
} else {
diff --git a/lib/template.php b/lib/template.php
index 3b48c27b9b4..5b6999af533 100644
--- a/lib/template.php
+++ b/lib/template.php
@@ -82,7 +82,6 @@ function relative_modified_date($timestamp) {
$diffhours = round($diffminutes/60);
$diffdays = round($diffhours/24);
$diffmonths = round($diffdays/31);
- $diffyears = round($diffdays/365);
if($timediff < 60) { return $l->t('seconds ago'); }
else if($timediff < 120) { return $l->t('1 minute ago'); }
diff --git a/lib/templatelayout.php b/lib/templatelayout.php
index d33a87e9e4c..588a7845997 100644
--- a/lib/templatelayout.php
+++ b/lib/templatelayout.php
@@ -123,7 +123,7 @@ class OC_TemplateLayout extends OC_Template {
elseif(self::appendIfExist($files, $apps_dir['path'], $apps_dir['url'], "$style.css")) { $append =true; break; }
}
if(! $append) {
- echo('css file not found: style:'.$script.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT);
+ echo('css file not found: style:'.$style.' formfactor:'.$fext.' webroot:'.OC::$WEBROOT.' serverroot:'.OC::$SERVERROOT);
die();
}
}
diff --git a/lib/user.php b/lib/user.php
index f1903093d6d..e3c9c23effa 100644
--- a/lib/user.php
+++ b/lib/user.php
@@ -21,7 +21,7 @@
*/
/**
- * This class provides wrapper methods for user management. Multiple backends are
+ * This class provides wrapper methods for user management. Multiple backends are
* supported. User management operations are delegated to the configured backend for
* execution.
*
@@ -50,8 +50,8 @@ class OC_User {
*
* Makes a list of backends that can be used by other modules
*/
- public static function registerBackend( $name ){
- self::$_backends[] = $name;
+ public static function registerBackend( $backend ){
+ self::$_backends[] = $backend;
return true;
}
@@ -83,28 +83,38 @@ class OC_User {
* Set the User Authentication Module
*/
public static function useBackend( $backend = 'database' ){
- // You'll never know what happens
- if( null === $backend OR !is_string( $backend )){
- $backend = 'database';
- }
+ if($backend instanceof OC_User_Interface){
+ self::$_usedBackends[get_class($backend)]=$backend;
+ }else{
+ // You'll never know what happens
+ if( null === $backend OR !is_string( $backend )){
+ $backend = 'database';
+ }
- // Load backend
- switch( $backend ){
- case 'database':
- case 'mysql':
- case 'sqlite':
- self::$_usedBackends[$backend] = new OC_User_Database();
- break;
- default:
- $className = 'OC_USER_' . strToUpper($backend);
- self::$_usedBackends[$backend] = new $className();
- break;
+ // Load backend
+ switch( $backend ){
+ case 'database':
+ case 'mysql':
+ case 'sqlite':
+ self::$_usedBackends[$backend] = new OC_User_Database();
+ break;
+ default:
+ $className = 'OC_USER_' . strToUpper($backend);
+ self::$_usedBackends[$backend] = new $className();
+ break;
+ }
}
-
true;
}
/**
+ * remove all used backends
+ */
+ public static function clearBackends(){
+ self::$_usedBackends=array();
+ }
+
+ /**
* @brief Create a new user
* @param $uid The username of the user to create
* @param $password The password of the new user
@@ -200,7 +210,7 @@ class OC_User {
if( $run ){
$uid=self::checkPassword( $uid, $password );
if($uid){
- session_regenerate_id();
+ session_regenerate_id(true);
self::setUserId($uid);
OC_Hook::emit( "OC_User", "post_login", array( "uid" => $uid, 'password'=>$password ));
return true;
diff --git a/lib/user/backend.php b/lib/user/backend.php
index be068a63ce0..daa942d261c 100644
--- a/lib/user/backend.php
+++ b/lib/user/backend.php
@@ -42,7 +42,7 @@ define('OC_USER_BACKEND_CHECK_PASSWORD', 0x000100);
*
* Subclass this for your own backends, and see OC_User_Example for descriptions
*/
-abstract class OC_User_Backend {
+abstract class OC_User_Backend implements OC_User_Interface {
protected $possibleActions = array(
OC_USER_BACKEND_CREATE_USER => 'createUser',
diff --git a/lib/user/database.php b/lib/user/database.php
index a48b8357d64..cc27b3ddbfd 100644
--- a/lib/user/database.php
+++ b/lib/user/database.php
@@ -39,7 +39,6 @@ require_once 'phpass/PasswordHash.php';
* Class for user management in a SQL Database (e.g. MySQL, SQLite)
*/
class OC_User_Database extends OC_User_Backend {
- static private $userGroupCache=array();
/**
* @var PasswordHash
*/
@@ -87,7 +86,7 @@ class OC_User_Database extends OC_User_Backend {
public function deleteUser( $uid ){
// Delete user-group-relation
$query = OC_DB::prepare( "DELETE FROM `*PREFIX*users` WHERE uid = ?" );
- $result = $query->execute( array( $uid ));
+ $query->execute( array( $uid ));
return true;
}
@@ -104,11 +103,10 @@ class OC_User_Database extends OC_User_Backend {
$hasher=$this->getHasher();
$hash = $hasher->HashPassword($password.OC_Config::getValue('passwordsalt', ''));
$query = OC_DB::prepare( "UPDATE *PREFIX*users SET password = ? WHERE uid = ?" );
- $result = $query->execute( array( $hash, $uid ));
+ $query->execute( array( $hash, $uid ));
return true;
- }
- else{
+ }else{
return false;
}
}
diff --git a/lib/user/example.php b/lib/user/example.php
index 7f3fd1b8578..77246d8136c 100644
--- a/lib/user/example.php
+++ b/lib/user/example.php
@@ -35,9 +35,7 @@ abstract class OC_User_Example extends OC_User_Backend {
* Creates a new user. Basic checking of username is done in OC_User
* itself, not in its subclasses.
*/
- public function createUser($uid, $password){
- return OC_USER_BACKEND_NOT_IMPLEMENTED;
- }
+ abstract public function createUser($uid, $password);
/**
* @brief Set password
@@ -47,9 +45,7 @@ abstract class OC_User_Example extends OC_User_Backend {
*
* Change the password of a user
*/
- public function setPassword($uid, $password){
- return OC_USER_BACKEND_NOT_IMPLEMENTED;
- }
+ abstract public function setPassword($uid, $password);
/**
* @brief Check if the password is correct
@@ -60,7 +56,5 @@ abstract class OC_User_Example extends OC_User_Backend {
* Check if the password is correct without logging in the user
* returns the user id or false
*/
- public function checkPassword($uid, $password){
- return OC_USER_BACKEND_NOT_IMPLEMENTED;
- }
+ abstract public function checkPassword($uid, $password);
}
diff --git a/lib/user/interface.php b/lib/user/interface.php
new file mode 100644
index 00000000000..dc3685dc20d
--- /dev/null
+++ b/lib/user/interface.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * ownCloud - user interface
+ *
+ * @author Arthur Schiwon
+ * @copyright 2012 Arthur Schiwon blizzz@owncloud.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/>.
+ *
+ */
+
+interface OC_User_Interface {
+
+ /**
+ * @brief Check if backend implements actions
+ * @param $actions bitwise-or'ed actions
+ * @returns boolean
+ *
+ * Returns the supported actions as int to be
+ * compared with OC_USER_BACKEND_CREATE_USER etc.
+ */
+ public function implementsActions($actions);
+
+ /**
+ * @brief delete a user
+ * @param $uid The username of the user to delete
+ * @returns true/false
+ *
+ * Deletes a user
+ */
+ public function deleteUser($uid);
+
+ /**
+ * @brief Get a list of all users
+ * @returns array with all uids
+ *
+ * Get a list of all users.
+ */
+ public function getUsers();
+
+ /**
+ * @brief check if a user exists
+ * @param string $uid the username
+ * @return boolean
+ */
+ public function userExists($uid);
+
+} \ No newline at end of file
diff --git a/lib/util.php b/lib/util.php
index 2a7b8a922f9..0c563278cc5 100755
--- a/lib/util.php
+++ b/lib/util.php
@@ -77,13 +77,13 @@ class OC_Util {
return '5 pre alpha';
}
- /**
- * 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 '';
- }
+ /**
+ * 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
@@ -131,12 +131,12 @@ class OC_Util {
self::$headers[]=array('tag'=>$tag,'attributes'=>$attributes,'text'=>$text);
}
- /**
- * formats a timestamp in the "right" way
- *
- * @param int timestamp $timestamp
- * @param bool dateOnly option to ommit time from the result
- */
+ /**
+ * formats a timestamp in the "right" way
+ *
+ * @param int timestamp $timestamp
+ * @param bool dateOnly option to ommit time from the result
+ */
public static function formatDate( $timestamp,$dateOnly=false){
if(isset($_SESSION['timezone'])){//adjust to clients timezone if we know it
$systemTimeZone = intval(date('O'));
@@ -189,8 +189,6 @@ class OC_Util {
if(!(is_callable('sqlite_open') or class_exists('SQLite3')) and !is_callable('mysql_connect') and !is_callable('pg_connect')){
$errors[]=array('error'=>'No database drivers (sqlite, mysql, or postgresql) installed.<br/>','hint'=>'');//TODO: sane hint
}
- $CONFIG_DBTYPE = OC_Config::getValue( "dbtype", "sqlite" );
- $CONFIG_DBNAME = OC_Config::getValue( "dbname", "owncloud" );
//common hint for all file permissons error messages
$permissionsHint="Permissions can usually be fixed by giving the webserver write access to the ownCloud directory";
@@ -440,26 +438,25 @@ class OC_Util {
}
- /**
- * Check if the htaccess file is working by creating a test file in the data directory and trying to access via http
- */
- public static function ishtaccessworking() {
-
+ /**
+ * Check if the htaccess file is working by creating a test file in the data directory and trying to access via http
+ */
+ public static function ishtaccessworking() {
// testdata
$filename='/htaccesstest.txt';
$testcontent='testcontent';
// creating a test file
- $testfile = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ).'/'.$filename;
- $fp = @fopen($testfile, 'w');
- @fwrite($fp, $testcontent);
- @fclose($fp);
+ $testfile = OC_Config::getValue( "datadirectory", OC::$SERVERROOT."/data" ).'/'.$filename;
+ $fp = @fopen($testfile, 'w');
+ @fwrite($fp, $testcontent);
+ @fclose($fp);
// accessing the file via http
- $url = OC_Helper::serverProtocol(). '://' . OC_Helper::serverHost() . OC::$WEBROOT.'/data'.$filename;
- $fp = @fopen($url, 'r');
- $content=@fread($fp, 2048);
- @fclose($fp);
+ $url = OC_Helper::serverProtocol(). '://' . OC_Helper::serverHost() . OC::$WEBROOT.'/data'.$filename;
+ $fp = @fopen($url, 'r');
+ $content=@fread($fp, 2048);
+ @fclose($fp);
// cleanup
@unlink($testfile);
@@ -469,13 +466,7 @@ class OC_Util {
return(false);
}else{
return(true);
-
}
-
- }
-
-
-
-
+ }
}
diff --git a/ocs/providers.php b/ocs/providers.php
index f9cafd20b01..cc6de32266b 100644
--- a/ocs/providers.php
+++ b/ocs/providers.php
@@ -35,7 +35,9 @@ echo('
<termsofuse></termsofuse>
<register></register>
<services>
- <activity ocsversion="1.5" />
+ <config ocsversion="1.7" />
+ <activity ocsversion="1.7" />
+ <cloud ocsversion="1.7" />
</services>
</provider>
</providers>
diff --git a/public.php b/public.php
index 8846769f467..8383f36a246 100644
--- a/public.php
+++ b/public.php
@@ -10,6 +10,7 @@ if(is_null($file)){
$parts=explode('/',$file,2);
$app=$parts[0];
+
OC_Util::checkAppEnabled($app);
OC_App::loadApp($app);
diff --git a/remote.php b/remote.php
index bdce867aaba..8461eea19ad 100644
--- a/remote.php
+++ b/remote.php
@@ -15,27 +15,27 @@ if (!$pos = strpos($path_info, '/', 1)) {
$pos = strlen($path_info);
}
$service=substr($path_info, 1, $pos-1);
+
$file = OC_AppConfig::getValue('core', 'remote_' . $service);
-$file = preg_replace('/apps\//','', $file); //Todo Remove after Multiappdir migration
if(is_null($file)){
OC_Response::setStatus(OC_Response::STATUS_NOT_FOUND);
exit;
}
-$file = ltrim ($file, '/');
+$file=ltrim($file,'/');
$parts=explode('/', $file, 2);
$app=$parts[0];
switch ($app) {
+ case 'core':
+ $file = OC::$SERVERROOT .'/'. $file;
+ break;
default:
OC_Util::checkAppEnabled($app);
OC_App::loadApp($app);
$file = OC_App::getAppPath($app) .'/'. $parts[1];
break;
- case 'core':
- $file = OC::$SERVERROOT .'/'. $file;
- break;
}
$baseuri = OC::$WEBROOT . '/remote.php/'.$service.'/';
require_once($file);
diff --git a/remote/.gitignore b/remote/.gitignore
new file mode 100644
index 00000000000..c96a04f008e
--- /dev/null
+++ b/remote/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore \ No newline at end of file
diff --git a/settings/js/users.js b/settings/js/users.js
index 771ed51f5fb..63ad426ecf4 100644
--- a/settings/js/users.js
+++ b/settings/js/users.js
@@ -47,25 +47,24 @@ UserList={
if( !UserList.deleteCanceled && UserList.deleteUid ){
// Delete user via ajax
- $.post(
- OC.filePath('settings','ajax','removeuser.php'),
- {username:UserList.deleteUid},
- function(result){
-
- // Remove undo option, & remove user from table
- boolOperationFinished(
- data, function(){
- $('#notification').fadeOut();
- $('tr').filterAttr( 'data-uid', username ).remove();
- UserList.deleteCanceled=true;
- UserList.deleteFiles=null;
- if( ready ){
- ready();
- }
+ $.ajax({
+ type: 'POST',
+ url: OC.filePath('settings', 'ajax', 'removeuser.php'),
+ async: false,
+ data: { username: UserList.deleteUid },
+ success: function(result) {
+ if (result.status == 'success') {
+ // Remove undo option, & remove user from table
+ $('#notification').fadeOut();
+ $('tr').filterAttr('data-uid', UserList.deleteUid).remove();
+ UserList.deleteCanceled = true;
+ UserList.deleteFiles = null;
+ if (ready) {
+ ready();
}
- );
+ }
}
- );
+ });
}
}
}
diff --git a/sharing.patch b/sharing.patch
new file mode 100644
index 00000000000..ac1693cd2d3
--- /dev/null
+++ b/sharing.patch
@@ -0,0 +1,63 @@
+diff -u -r -N owncloud/apps/files_sharing/css/sharing.css owncloud/apps/files_sharing/css/sharing.css
+--- owncloud/apps/files_sharing/css/sharing.css 2012-01-29 14:50:19.000000000 +0100
++++ owncloud/apps/files_sharing/css/sharing.css 2012-03-28 11:45:02.000000000 +0200
+@@ -5,4 +5,7 @@
+ #shared_list { padding:0.5em; list-style-type: none; }
+ #public { border-top:1px solid #ddd; padding-top:0.5em; }
+ a.unshare { float:right; display:inline; margin:0 .5em; padding:.3em .3em 0 .3em !important; opacity:.5; }
+-a.unshare:hover { opacity:1; }
+\ Kein Zeilenumbruch am Dateiende.
++a.unshare:hover { opacity:1; }
++
++div.actionsSharing { font-weight: bold; padding-left: .65em; padding-top: .65em; color: #555; }
++table td.source, table td.uid_shared_with, table td.permissions { padding: .5em ; }
+\ Kein Zeilenumbruch am Dateiende.
+diff -u -r -N owncloud/apps/files_sharing/list.php owncloud/apps/files_sharing/list.php
+--- owncloud/apps/files_sharing/list.php 2012-01-29 14:50:19.000000000 +0100
++++ owncloud/apps/files_sharing/list.php 2012-03-28 11:43:31.000000000 +0200
+@@ -29,6 +29,7 @@
+ OC_App::setActiveNavigationEntry("files_sharing_list");
+
+ OC_Util::addScript("files_sharing", "list");
++OC_Util::addStyle("files", "files");
+
+ $tmpl = new OC_Template("files_sharing", "list", "user");
+ $tmpl->assign("shared_items", OC_Share::getMySharedItems());
+diff -u -r -N owncloud/apps/files_sharing/templates/list.php owncloud/apps/files_sharing/templates/list.php
+--- owncloud/apps/files_sharing/templates/list.php 2012-01-29 14:50:19.000000000 +0100
++++ owncloud/apps/files_sharing/templates/list.php 2012-03-28 11:44:11.000000000 +0200
+@@ -1,5 +1,10 @@
+-<fieldset>
+- <legend><?php echo $l->t('Your Shared Files');?></legend>
++<div id="controls">
++ <div class="actionsSharing">
++ <?php echo $l->t('My shared files'); ?>
++ </div>
++</div>
++
++<div id='list'>
+ <table id="itemlist">
+ <thead>
+ <tr>
+@@ -14,17 +19,17 @@
+ <td class="source"><?php echo substr($item['source'], strlen("/".$_SESSION['user_id']."/files/"));?></td>
+ <td class="uid_shared_with"><?php echo $item['uid_shared_with'];?></td>
+ <td class="permissions"><?php echo $l->t('Read'); echo($item['permissions'] & OC_SHARE::WRITE ? ", ".$l->t('Edit') : ""); echo($item['permissions'] & OC_SHARE::DELETE ? ", ".$l->t('Delete') : "");?></td>
+- <td><button class="delete" data-source="<?php echo $item['source'];?>" data-uid_shared_with="<?php echo $item['uid_shared_with'];?>"><?php echo $l->t('Delete');?></button></td>
++<!-- <td><button class="delete" data-source="<?php echo $item['source'];?>" data-uid_shared_with="<?php echo $item['uid_shared_with'];?>"><?php echo $l->t('Delete');?></button></td> //-->
+ </tr>
+ <?php endforeach;?>
+- <tr id="share_item_row">
++<!-- <tr id="share_item_row">
+ <form action="#" id="share_item">
+ <td class="source"><input placeholder="Item" id="source" /></td>
+ <td class="uid_shared_with"><input placeholder="Share With" id="uid_shared_with" /></td>
+ <td class="permissions"><input placeholder="Permissions" id="permissions" /></td>
+ <td><input type="submit" value="Share" /></td>
+ </form>
+- </tr>
++ </tr> //-->
+ </tbody>
+ </table>
+-</fieldset>
++</div>
diff --git a/tests/index.php b/tests/index.php
index 691bf2a5d45..0be27ee3fd7 100644
--- a/tests/index.php
+++ b/tests/index.php
@@ -26,39 +26,64 @@ require_once 'simpletest/mock_objects.php';
require_once 'simpletest/collector.php';
require_once 'simpletest/default_reporter.php';
+$testSuiteName="ownCloud Unit Test Suite";
+
+// prepare the reporter
+if(OC::$CLI){
+ $reporter=new TextReporter;
+ $test=isset($_SERVER['argv'][1])?$_SERVER['argv'][1]:false;
+ if($test=='xml'){
+ $reporter= new XmlReporter;
+ $test=false;
+
+ if(isset($_SERVER['argv'][2])){
+ $testSuiteName=$testSuiteName." (".$_SERVER['argv'][2].")";
+ }
+ }
+}else{
+ $reporter=new HtmlReporter;
+ $test=isset($_GET['test'])?$_GET['test']:false;
+}
+
+// test suite instance
+$testSuite=new TestSuite($testSuiteName);
+
//load core test cases
-loadTests(dirname(__FILE__));
+loadTests(dirname(__FILE__), $testSuite, $test);
//load app test cases
+
+//
+// TODO: define a list of apps to be enabled + enable them
+//
+
$apps=OC_App::getEnabledApps();
foreach($apps as $app){
if(is_dir(OC::$SERVERROOT.'/apps/'.$app.'/tests')){
- loadTests(OC::$SERVERROOT.'/apps/'.$app.'/tests');
+ loadTests(OC::$SERVERROOT.'/apps/'.$app.'/tests', $testSuite, $test);
}
}
-function loadTests($dir=''){
- if(OC::$CLI){
- $reporter='TextReporter';
- $test=isset($_SERVER['argv'][1])?$_SERVER['argv'][1]:false;
- }else{
- $reporter='HtmlReporter';
- $test=isset($_GET['test'])?$_GET['test']:false;
- }
+// run the suite
+if($testSuite->getSize()>0){
+ $testSuite->run($reporter);
+}
+
+// helper below
+function loadTests($dir,$testSuite, $test){
if($dh=opendir($dir)){
while($name=readdir($dh)){
if($name[0]!='.'){//no hidden files, '.' or '..'
$file=$dir.'/'.$name;
if(is_dir($file)){
- loadTests($file);
+ loadTests($file, $testSuite, $test);
}elseif(substr($file,-4)=='.php' and $file!=__FILE__){
$name=getTestName($file);
if($test===false or $test==$name or substr($name,0,strlen($test))==$test){
- $testCase=new TestSuite($name);
- $testCase->addFile($file);
- if($testCase->getSize()>0){
- $testCase->run(new $reporter());
- }
+ $extractor = new SimpleFileLoader();
+ $loadedSuite=$extractor->load($file);
+ if ($loadedSuite->getSize() > 0)
+ $testSuite->add($loadedSuite);
}
}
}
diff --git a/tests/lib/cache.php b/tests/lib/cache.php
index bb5cfc6ee19..511999be956 100644
--- a/tests/lib/cache.php
+++ b/tests/lib/cache.php
@@ -42,6 +42,27 @@ abstract class Test_Cache extends UnitTestCase {
$this->assertNull($this->instance->get('not_set'),'Unset value not equal to null');
$this->assertTrue($this->instance->remove('value1'));
+ $this->assertFalse($this->instance->hasKey('value1'));
+ }
+
+ function testClear(){
+ $value='ipsum lorum';
+ $this->instance->set('1_value1',$value);
+ $this->instance->set('1_value2',$value);
+ $this->instance->set('2_value1',$value);
+ $this->instance->set('3_value1',$value);
+
+ $this->assertTrue($this->instance->clear('1_'));
+ $this->assertFalse($this->instance->hasKey('1_value1'));
+ $this->assertFalse($this->instance->hasKey('1_value2'));
+ $this->assertTrue($this->instance->hasKey('2_value1'));
+ $this->assertTrue($this->instance->hasKey('3_value1'));
+
+ $this->assertTrue($this->instance->clear());
+ $this->assertFalse($this->instance->hasKey('1_value1'));
+ $this->assertFalse($this->instance->hasKey('1_value2'));
+ $this->assertFalse($this->instance->hasKey('2_value1'));
+ $this->assertFalse($this->instance->hasKey('3_value1'));
}
function testTTL(){
diff --git a/tests/lib/cache/file.php b/tests/lib/cache/file.php
index 54e60e6569d..28778efb68c 100644
--- a/tests/lib/cache/file.php
+++ b/tests/lib/cache/file.php
@@ -21,8 +21,10 @@
*/
class Test_Cache_File extends Test_Cache {
+ private $user;
+
function skip() {
- $this->skipUnless(OC_User::isLoggedIn());
+ //$this->skipUnless(OC_User::isLoggedIn());
}
public function setUp(){
@@ -30,17 +32,32 @@ class Test_Cache_File extends Test_Cache {
OC_FileProxy::clearProxies();
OC_Hook::clear('OC_Filesystem');
- //enable only the encryption hook
- OC_FileProxy::register(new OC_FileProxy_Encryption());
+ //enable only the encryption hook if needed
+ if(OC_App::isEnabled('files_encryption')){
+ OC_FileProxy::register(new OC_FileProxy_Encryption());
+ }
//set up temporary storage
OC_Filesystem::clearMounts();
OC_Filesystem::mount('OC_Filestorage_Temporary',array(),'/');
+ OC_User::clearBackends();
+ OC_User::useBackend(new OC_User_Dummy());
+
+ //login
+ OC_User::createUser('test', 'test');
+
+ $this->user=OC_User::getUser();
+ OC_User::setUserId('test');
+
//set up the users dir
$rootView=new OC_FilesystemView('');
- $rootView->mkdir('/'.OC_User::getUser());
+ $rootView->mkdir('/test');
$this->instance=new OC_Cache_File();
}
+
+ public function tearDown(){
+ OC_User::setUserId($this->user);
+ }
}
diff --git a/tests/lib/share/backend.php b/tests/lib/share/backend.php
new file mode 100644
index 00000000000..4f2fcce235e
--- /dev/null
+++ b/tests/lib/share/backend.php
@@ -0,0 +1,66 @@
+<?php
+/**
+* ownCloud
+*
+* @author Michael Gapczynski
+* @copyright 2012 Michael Gapczynski mtgap@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/>.
+*/
+
+abstract class Test_Share_Backend extends UnitTestCase {
+
+ protected $userBackend;
+ protected $user1;
+ protected $user2;
+ protected $groupBackend;
+ protected $group;
+ protected $itemType;
+ protected $item;
+
+ public function setUp() {
+ OC_User::clearBackends();
+ OC_User::useBackend('dummy');
+ $this->user1 = uniqid('user_');
+ $this->user2 = uniqid('user_');
+ OC_User::createUser($this->user1, 'pass1');
+ OC_User::createUser($this->user2, 'pass2');
+ OC_Group::clearBackends();
+ OC_Group::useBackend(new OC_Group_Dummy);
+ $this->group = uniqid('group_');
+ OC_Group::createGroup($this->group);
+ }
+
+ public function testShareWithUserNonExistentItem() {
+ $this->assertFalse(OCP\Share::share($this->itemType, uniqid('foobar_'), OCP\Share::SHARETYPE_USER, $this->user2, OCP\Share::PERMISSION_READ));
+ }
+
+ public function testShareWithUserItem() {
+ $this->assertTrue(OCP\Share::share($this->itemType, $this->item, OCP\Share::SHARETYPE_USER, $this->user2, OCP\Share::PERMISSION_READ));
+ }
+
+ public function testShareWithGroupNonExistentItem() {
+ $this->assertFalse(OCP\Share::share($this->itemType, uniqid('foobar_'), OCP\Share::SHARETYPE_GROUP, $this->group, OCP\Share::PERMISSION_READ));
+ }
+
+ public function testShareWithGroupItem() {
+ $this->assertTrue(OCP\Share::share($this->itemType, $this->item, OCP\Share::SHARETYPE_GROUP, $this->group, OCP\Share::PERMISSION_READ));
+ }
+
+ public function testShareWithUserAlreadySharedWith() {
+ $this->assertTrue(OCP\Share::share($this->itemType, $this->item, OCP\Share::SHARETYPE_USER, $this->user2, OCP\Share::PERMISSION_READ));
+ $this->assertFalse(OCP\Share::share($this->itemType, $this->item, OCP\Share::SHARETYPE_USER, $this->user2, OCP\Share::PERMISSION_READ));
+ }
+
+}
diff --git a/tests/lib/share/share.php b/tests/lib/share/share.php
new file mode 100644
index 00000000000..eca2c9bc58a
--- /dev/null
+++ b/tests/lib/share/share.php
@@ -0,0 +1,161 @@
+<?php
+/**
+* ownCloud
+*
+* @author Michael Gapczynski
+* @copyright 2012 Michael Gapczynski mtgap@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/>.
+*/
+
+class Test_Share_Base extends UnitTestCase {
+
+ protected $itemType;
+ protected $userBackend;
+ protected $user1;
+ protected $user2;
+ protected $groupBackend;
+ protected $group1;
+ protected $group2;
+
+
+ public function setUp() {
+ OC_User::clearBackends();
+ OC_User::useBackend('dummy');
+ $this->user1 = uniqid('user_');
+ $this->user2 = uniqid('user_');
+ OC_User::createUser($this->user1, 'pass1');
+ OC_User::createUser($this->user2, 'pass2');
+ OC_Group::clearBackends();
+ OC_Group::useBackend(new OC_Group_Dummy);
+ $this->group1 = uniqid('group_');
+ $this->group2 = uniqid('group_');
+ OC_Group::createGroup($this->group1);
+ OC_Group::createGroup($this->group2);
+ }
+
+ public function testShareInvalidShareType() {
+ $this->assertFalse(OCP\Share::share('file', 'test.txt', 'foobar', $this->user1, OCP\Share::PERMISSION_READ));
+ }
+
+ public function testShareInvalidItemType() {
+ $this->assertFalse(OCP\Share::share('foobar', 'test.txt', OCP\Share::SHARETYPE_USER, $this->user1, OCP\Share::PERMISSION_READ));
+ }
+
+ public function testShareWithSelf() {
+ OC_User::setUserId($this->user1);
+ $this->assertFalse(OCP\Share::share('file', 'test.txt', OCP\Share::SHARETYPE_USER, $this->user1, OCP\Share::PERMISSION_READ));
+ }
+
+ public function testShareWithNonExistentUser() {
+ $this->assertFalse(OCP\Share::share('file', 'test.txt', OCP\Share::SHARETYPE_USER, 'foobar', OCP\Share::PERMISSION_READ));
+ }
+
+ public function testShareWithUserOwnerNotPartOfGroup() {
+
+ }
+
+ public function testShareWithUserAlreadySharedWith() {
+
+ }
+
+ public function testShareWithNonExistentGroup() {
+ $this->assertFalse(OCP\Share::share('file', 'test.txt', OCP\Share::SHARETYPE_GROUP, 'foobar', OCP\Share::PERMISSION_READ));
+ }
+
+ public function testShareWithGroupOwnerNotPartOfGroup() {
+
+ }
+
+
+ public function testShareWithGroupItem() {
+
+ }
+
+ public function testUnshareInvalidShareType() {
+
+ }
+
+ public function testUnshareNonExistentItem() {
+
+ }
+
+ public function testUnshareFromUserItem() {
+
+ }
+
+ public function testUnshareFromGroupItem() {
+
+ }
+
+
+
+
+
+ // Test owner not in same group (false)
+
+
+
+ // Test non-existant item type
+
+ // Test valid item
+
+ // Test existing shared item (false)
+
+ // Test unsharing item
+
+ // Test setting permissions
+
+ // Test setting permissions not as owner (false)
+
+ // Test setting target
+
+ // Test setting target as owner (false)
+
+ // Spam reshares
+
+
+
+ // Test non-existant group
+
+
+ // Test owner not part of group
+
+ // Test existing shared item with group
+
+ // Test valid item, valid name for all users
+
+ // Test unsharing item
+
+ // Test item with name conflicts
+
+ // Test unsharing item
+
+ // Test setting permissions
+
+ // Test setting target no name conflicts
+
+ // Test setting target with conflicts
+
+ // Spam reshares
+
+
+
+
+
+ public function testPrivateLink() {
+
+ }
+
+}
diff --git a/tests/lib/user/database.php b/tests/lib/user/database.php
index b2fcce93c5b..f484ffa78f7 100644
--- a/tests/lib/user/database.php
+++ b/tests/lib/user/database.php
@@ -21,7 +21,6 @@
*/
class Test_User_Database extends Test_User_Backend {
- private $user=array();
/**
* get a new unique user name
* test cases can override this in order to clean up created user
diff --git a/webapps.php b/webapps.php
deleted file mode 100644
index 82e677a51c7..00000000000
--- a/webapps.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-
-/**
-* ownCloud status page. usefull if you want to check from the outside if an owncloud installation exists
-*
-* @author Frank Karlitschek
-* @copyright 2012 Frank Karlitschek frank@owncloud.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/>.
-*
-*/
-
-$RUNTIME_NOAPPS = TRUE; //no apps, yet
-
-require_once('lib/base.php');
-
-
-//valid user account
-if(isset($_SERVER['PHP_AUTH_USER'])) $authuser=$_SERVER['PHP_AUTH_USER']; else $authuser='';
-if(isset($_SERVER['PHP_AUTH_PW'])) $authpw=$_SERVER['PHP_AUTH_PW']; else $authpw='';
-
-if(!OC_User::login($authuser,$authpw)){
- header('WWW-Authenticate: Basic realm="your valid user account"');
- header('HTTP/1.0 401 Unauthorized');
- exit;
-}else{
-
- $apps=OC_App::getEnabledApps();
- $values=array();
- foreach($apps as $app) {
- $info=OC_App::getAppInfo($app);
- if(isset($info['standalone'])) {
- $newvalue=array('name'=>$info['name'],'url'=>OC_Helper::linkToAbsolute($app,''),'icon'=>'');
- $values[]=$newvalue;
- }
-
- }
-
- echo(json_encode($values));
- exit;
-
-
-}