[Pkg-owncloud-commits] [owncloud] 66/70: Imported Upstream version 7.0.0~rc2+dfsg

David Prévot taffit at moszumanska.debian.org
Mon Jul 14 17:38:10 UTC 2014


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository owncloud.

commit 3967a0d27ceb541c6b0cd6667aacabc5c879fa42
Merge: fe5a04b e3d5080
Author: David Prévot <taffit at debian.org>
Date:   Mon Jul 14 12:34:51 2014 -0400

    Imported Upstream version 7.0.0~rc2+dfsg

 apps/activity/README.md                            |   14 +-
 apps/activity/lib/data.php                         |   72 +-
 apps/activity/lib/datahelper.php                   |   22 +-
 apps/activity/lib/display.php                      |    4 +-
 apps/activity/lib/grouphelper.php                  |    4 +-
 apps/activity/lib/navigation.php                   |    7 +-
 apps/activity/lib/parameterhelper.php              |    5 +-
 apps/activity/lib/usersettings.php                 |    4 +
 apps/activity/tests/datahelpertest.php             |    2 +-
 apps/activity/tests/datatest.php                   |   52 +
 apps/calendar/appinfo/info.xml                     |    1 +
 apps/calendar/appinfo/version                      |    2 +-
 apps/calendar/js/on-event.js                       |    2 +-
 apps/calendar/lib/object.php                       |    6 +-
 .../templates/part.choosecalendar.rowfields.php    |    3 +-
 apps/documents/ajax/admin.php                      |   46 +-
 apps/documents/ajax/download.php                   |    9 +-
 apps/documents/js/admin.js                         |   13 +-
 apps/documents/lib/file.php                        |    6 +-
 apps/documents/templates/admin.php                 |    3 +-
 apps/files/appinfo/remote.php                      |    1 -
 apps/files/js/app.js                               |   34 +-
 apps/files/js/fileactions.js                       |  109 +-
 apps/files/js/filelist.js                          |   25 +-
 apps/files/lib/helper.php                          |   13 +
 apps/files/tests/js/appSpec.js                     |    4 +-
 apps/files/tests/js/fileactionsSpec.js             |  156 ++-
 apps/files/tests/js/filelistSpec.js                |   26 +
 apps/files_encryption/appinfo/info.xml             |    1 +
 apps/files_encryption/appinfo/version              |    2 +-
 apps/files_encryption/hooks/hooks.php              |   14 +-
 apps/files_encryption/lib/keymanager.php           |   13 +-
 apps/files_encryption/tests/keymanager.php         |    4 +-
 apps/files_external/ajax/addMountPoint.php         |   19 +-
 apps/files_external/appinfo/info.xml               |    1 +
 apps/files_external/appinfo/version                |    2 +-
 apps/files_external/js/mountsfilelist.js           |   10 +-
 apps/files_external/js/settings.js                 |    9 +-
 apps/files_external/lib/amazons3.php               |   30 +-
 apps/files_external/lib/sftp.php                   |   15 +
 apps/files_external/templates/settings.php         |    1 +
 apps/files_pdfviewer/3rdparty/pdfjs/viewer.js      |    6 +-
 apps/files_pdfviewer/appinfo/info.xml              |    1 +
 apps/files_pdfviewer/appinfo/version               |    2 +-
 apps/files_pdfviewer/js/loader.js                  |    4 +-
 apps/files_sharing/appinfo/info.xml                |    1 +
 apps/files_sharing/appinfo/version                 |    2 +-
 apps/files_sharing/css/public.css                  |   37 +-
 apps/files_sharing/js/app.js                       |   40 +
 apps/files_sharing/js/external.js                  |   35 +-
 apps/files_sharing/js/public.js                    |    2 +-
 apps/files_sharing/js/sharedfilelist.js            |   28 +-
 apps/files_sharing/lib/api.php                     |    6 +
 apps/files_sharing/lib/proxy.php                   |   24 +-
 apps/files_sharing/lib/sharedmount.php             |    2 +
 apps/files_sharing/lib/updater.php                 |    4 +-
 apps/files_sharing/publicwebdav.php                |    1 -
 apps/files_sharing/templates/public.php            |    4 +-
 apps/files_sharing/tests/js/appSpec.js             |    7 +-
 apps/files_sharing/tests/js/sharedfilelistSpec.js  |   42 +
 apps/files_sharing/tests/proxy.php                 |   27 +-
 apps/files_sharing/tests/update.php                |   30 +-
 apps/files_sharing/tests/updater.php               |   14 +-
 apps/files_trashbin/appinfo/info.xml               |    1 +
 apps/files_trashbin/appinfo/version                |    2 +-
 apps/files_versions/appinfo/info.xml               |    1 +
 apps/files_versions/appinfo/routes.php             |    6 +-
 apps/files_versions/appinfo/version                |    2 +-
 apps/gallery/appinfo/info.xml                      |    1 +
 apps/gallery/appinfo/version                       |    2 +-
 apps/gallery/css/public.css                        |    8 +-
 apps/tasks/ajax/addtask.php                        |   28 -
 apps/tasks/ajax/delete.php                         |   32 -
 apps/tasks/ajax/gettasks.php                       |   36 -
 apps/tasks/ajax/update_property.php                |   69 -
 apps/tasks/appinfo/app.php                         |   10 -
 apps/tasks/appinfo/info.xml                        |   11 -
 apps/tasks/css/style.css                           |   86 --
 apps/tasks/img/tasks.png                           |  Bin 959 -> 0 bytes
 apps/tasks/img/tasks.svg                           |    8 -
 apps/tasks/index.php                               |   36 -
 apps/tasks/js/categories.php                       |   17 -
 apps/tasks/js/tasks.js                             |  526 --------
 apps/tasks/l10n/ar.php                             |   30 -
 apps/tasks/l10n/ast.php                            |   30 -
 apps/tasks/l10n/bg_BG.php                          |   20 -
 apps/tasks/l10n/bn_BD.php                          |   30 -
 apps/tasks/l10n/ca.php                             |   30 -
 apps/tasks/l10n/cs_CZ.php                          |   30 -
 apps/tasks/l10n/cy_GB.php                          |    7 -
 apps/tasks/l10n/da.php                             |   30 -
 apps/tasks/l10n/de.php                             |   30 -
 apps/tasks/l10n/de_AT.php                          |    6 -
 apps/tasks/l10n/de_CH.php                          |   30 -
 apps/tasks/l10n/de_DE.php                          |   30 -
 apps/tasks/l10n/el.php                             |   30 -
 apps/tasks/l10n/en_GB.php                          |   30 -
 apps/tasks/l10n/eo.php                             |   30 -
 apps/tasks/l10n/es.php                             |   30 -
 apps/tasks/l10n/es_AR.php                          |   30 -
 apps/tasks/l10n/es_MX.php                          |   30 -
 apps/tasks/l10n/et_EE.php                          |   30 -
 apps/tasks/l10n/eu.php                             |   30 -
 apps/tasks/l10n/eu_ES.php                          |    6 -
 apps/tasks/l10n/fa.php                             |   30 -
 apps/tasks/l10n/fi_FI.php                          |   30 -
 apps/tasks/l10n/fr.php                             |   30 -
 apps/tasks/l10n/gl.php                             |   30 -
 apps/tasks/l10n/he.php                             |   30 -
 apps/tasks/l10n/hr.php                             |    7 -
 apps/tasks/l10n/hu_HU.php                          |   30 -
 apps/tasks/l10n/hy.php                             |    5 -
 apps/tasks/l10n/ia.php                             |    7 -
 apps/tasks/l10n/id.php                             |   30 -
 apps/tasks/l10n/is.php                             |   30 -
 apps/tasks/l10n/it.php                             |   30 -
 apps/tasks/l10n/ja.php                             |   30 -
 apps/tasks/l10n/ja_JP.php                          |   28 -
 apps/tasks/l10n/jv.php                             |    5 -
 apps/tasks/l10n/ka_GE.php                          |   30 -
 apps/tasks/l10n/km.php                             |   30 -
 apps/tasks/l10n/ko.php                             |   30 -
 apps/tasks/l10n/ku_IQ.php                          |    5 -
 apps/tasks/l10n/lb.php                             |   16 -
 apps/tasks/l10n/lt_LT.php                          |   30 -
 apps/tasks/l10n/lv.php                             |   30 -
 apps/tasks/l10n/mk.php                             |   30 -
 apps/tasks/l10n/ms_MY.php                          |    7 -
 apps/tasks/l10n/my_MM.php                          |    5 -
 apps/tasks/l10n/nb_NO.php                          |   30 -
 apps/tasks/l10n/nl.php                             |   30 -
 apps/tasks/l10n/nn_NO.php                          |   30 -
 apps/tasks/l10n/oc.php                             |    7 -
 apps/tasks/l10n/pa.php                             |    5 -
 apps/tasks/l10n/pl.php                             |   30 -
 apps/tasks/l10n/pt_BR.php                          |   30 -
 apps/tasks/l10n/pt_PT.php                          |   30 -
 apps/tasks/l10n/ro.php                             |   30 -
 apps/tasks/l10n/ru.php                             |   30 -
 apps/tasks/l10n/si_LK.php                          |   30 -
 apps/tasks/l10n/sk.php                             |    6 -
 apps/tasks/l10n/sk_SK.php                          |   30 -
 apps/tasks/l10n/sl.php                             |   30 -
 apps/tasks/l10n/sq.php                             |   12 -
 apps/tasks/l10n/sr.php                             |   11 -
 apps/tasks/l10n/sr at latin.php                       |    6 -
 apps/tasks/l10n/sv.php                             |   30 -
 apps/tasks/l10n/ta_LK.php                          |   30 -
 apps/tasks/l10n/te.php                             |   10 -
 apps/tasks/l10n/th_TH.php                          |   30 -
 apps/tasks/l10n/tr.php                             |   30 -
 apps/tasks/l10n/ug.php                             |   17 -
 apps/tasks/l10n/uk.php                             |   30 -
 apps/tasks/l10n/ur_PK.php                          |   30 -
 apps/tasks/l10n/vi.php                             |   30 -
 apps/tasks/l10n/zh_CN.php                          |   30 -
 apps/tasks/l10n/zh_HK.php                          |    5 -
 apps/tasks/l10n/zh_TW.php                          |   30 -
 apps/tasks/lib/app.php                             |  188 ---
 apps/tasks/lib/vtodo.php                           |   27 -
 apps/tasks/templates/no-calendar-app.php           |   10 -
 apps/tasks/templates/tasks.php                     |   32 -
 apps/templateeditor/lib/mailtemplate.php           |   11 +-
 apps/user_ldap/appinfo/info.xml                    |    1 +
 apps/user_ldap/appinfo/version                     |    2 +-
 apps/user_ldap/group_ldap.php                      |  235 +++-
 apps/user_ldap/lib/access.php                      |   94 +-
 apps/user_ldap/lib/connection.php                  |    7 +
 apps/user_ldap/lib/ildapwrapper.php                |    9 +
 apps/user_ldap/lib/ldap.php                        |   11 +
 apps/user_ldap/tests/access.php                    |   52 +-
 apps/user_ldap/tests/data/sid.dat                  |  Bin 0 -> 24 bytes
 apps/user_ldap/tests/group_ldap.php                |  152 ++-
 apps/user_ldap/user_ldap.php                       |    1 -
 apps/user_webdavauth/appinfo/info.xml              |    1 +
 apps/user_webdavauth/appinfo/version               |    2 +-
 config/config.sample.php                           |    8 +-
 core/css/header.css                                |    6 +
 core/css/jquery.ocdialog.css                       |   11 +
 core/css/styles.css                                |   17 +-
 .../_sources/installation/installation_windows.txt |   39 +-
 .../admin/installation/installation_windows.html   |   27 +-
 core/js/jquery.ocdialog.js                         |    7 +
 core/js/js.js                                      |    8 +-
 core/js/oc-dialogs.js                              |   42 +-
 core/js/setup.js                                   |    4 +
 core/js/snap.js                                    | 1349 ++++++++++++--------
 core/setup/controller.php                          |    2 +-
 core/templates/layout.user.php                     |    1 +
 l10n/ach/settings.po                               |    2 +-
 l10n/ady/settings.po                               |    2 +-
 l10n/af/settings.po                                |    2 +-
 l10n/af_ZA/settings.po                             |    2 +-
 l10n/ak/settings.po                                |    2 +-
 l10n/am_ET/settings.po                             |    2 +-
 l10n/ar/settings.po                                |    2 +-
 l10n/ast/settings.po                               |    2 +-
 l10n/az/settings.po                                |    2 +-
 l10n/be/settings.po                                |    2 +-
 l10n/bg_BG/settings.po                             |    2 +-
 l10n/bn_BD/settings.po                             |    2 +-
 l10n/bn_IN/settings.po                             |    2 +-
 l10n/bs/settings.po                                |    2 +-
 l10n/ca/settings.po                                |    2 +-
 l10n/ca at valencia/settings.po                       |    2 +-
 l10n/cs_CZ/settings.po                             |    2 +-
 l10n/cy_GB/settings.po                             |    2 +-
 l10n/da/settings.po                                |    2 +-
 l10n/de/settings.po                                |    2 +-
 l10n/de_AT/settings.po                             |    2 +-
 l10n/de_CH/settings.po                             |    2 +-
 l10n/de_DE/settings.po                             |    2 +-
 l10n/el/settings.po                                |    2 +-
 l10n/en at pirate/settings.po                         |    2 +-
 l10n/en_GB/settings.po                             |    2 +-
 l10n/en_NZ/settings.po                             |    2 +-
 l10n/eo/settings.po                                |    2 +-
 l10n/es/settings.po                                |    2 +-
 l10n/es_AR/settings.po                             |    2 +-
 l10n/es_BO/settings.po                             |    2 +-
 l10n/es_CL/settings.po                             |    2 +-
 l10n/es_CO/settings.po                             |    2 +-
 l10n/es_CR/settings.po                             |    2 +-
 l10n/es_EC/settings.po                             |    2 +-
 l10n/es_MX/settings.po                             |    2 +-
 l10n/es_PE/settings.po                             |    2 +-
 l10n/es_PY/settings.po                             |    2 +-
 l10n/es_US/settings.po                             |    2 +-
 l10n/es_UY/settings.po                             |    2 +-
 l10n/et_EE/settings.po                             |    2 +-
 l10n/eu/settings.po                                |    2 +-
 l10n/eu_ES/settings.po                             |    2 +-
 l10n/fa/settings.po                                |    2 +-
 l10n/fi_FI/settings.po                             |    2 +-
 l10n/fr/settings.po                                |    2 +-
 l10n/fr_CA/settings.po                             |    2 +-
 l10n/gl/settings.po                                |    2 +-
 l10n/he/settings.po                                |    2 +-
 l10n/hi/settings.po                                |    2 +-
 l10n/hi_IN/settings.po                             |    2 +-
 l10n/hr/settings.po                                |    2 +-
 l10n/hu_HU/settings.po                             |    2 +-
 l10n/hy/settings.po                                |    2 +-
 l10n/ia/settings.po                                |    2 +-
 l10n/id/settings.po                                |    2 +-
 l10n/is/settings.po                                |    2 +-
 l10n/it/settings.po                                |    2 +-
 l10n/ja/settings.po                                |    2 +-
 l10n/jv/settings.po                                |    2 +-
 l10n/ka/settings.po                                |    2 +-
 l10n/ka_GE/settings.po                             |    2 +-
 l10n/km/settings.po                                |    2 +-
 l10n/kn/settings.po                                |    2 +-
 l10n/ko/settings.po                                |    2 +-
 l10n/ku_IQ/settings.po                             |    2 +-
 l10n/lb/settings.po                                |    2 +-
 l10n/lt_LT/settings.po                             |    2 +-
 l10n/lv/settings.po                                |    2 +-
 l10n/mk/settings.po                                |    2 +-
 l10n/ml/settings.po                                |    2 +-
 l10n/ml_IN/settings.po                             |    2 +-
 l10n/mn/settings.po                                |    2 +-
 l10n/ms_MY/settings.po                             |    2 +-
 l10n/my_MM/settings.po                             |    2 +-
 l10n/nb_NO/settings.po                             |    2 +-
 l10n/nds/settings.po                               |    2 +-
 l10n/ne/settings.po                                |    2 +-
 l10n/nl/settings.po                                |    2 +-
 l10n/nn_NO/settings.po                             |    2 +-
 l10n/nqo/settings.po                               |    2 +-
 l10n/oc/settings.po                                |    2 +-
 l10n/or_IN/settings.po                             |    2 +-
 l10n/pa/settings.po                                |    2 +-
 l10n/pl/settings.po                                |    2 +-
 l10n/pt_BR/settings.po                             |    2 +-
 l10n/pt_PT/settings.po                             |    2 +-
 l10n/ro/settings.po                                |    2 +-
 l10n/ru/settings.po                                |    2 +-
 l10n/si_LK/settings.po                             |    2 +-
 l10n/sk/settings.po                                |    2 +-
 l10n/sk_SK/settings.po                             |    2 +-
 l10n/sl/settings.po                                |    2 +-
 l10n/sq/settings.po                                |    2 +-
 l10n/sr/settings.po                                |    2 +-
 l10n/sr at latin/settings.po                          |    2 +-
 l10n/su/settings.po                                |    2 +-
 l10n/sv/settings.po                                |    2 +-
 l10n/sw_KE/settings.po                             |    2 +-
 l10n/ta_IN/settings.po                             |    2 +-
 l10n/ta_LK/settings.po                             |    2 +-
 l10n/te/settings.po                                |    2 +-
 l10n/templates/settings.pot                        |    2 +-
 l10n/th_TH/settings.po                             |    2 +-
 l10n/tr/settings.po                                |    2 +-
 l10n/tzm/settings.po                               |    2 +-
 l10n/ug/settings.po                                |    2 +-
 l10n/uk/settings.po                                |    2 +-
 l10n/ur/settings.po                                |    2 +-
 l10n/ur_PK/settings.po                             |    2 +-
 l10n/uz/settings.po                                |    2 +-
 l10n/vi/settings.po                                |    2 +-
 l10n/zh_CN/settings.po                             |    2 +-
 l10n/zh_HK/settings.po                             |    2 +-
 l10n/zh_TW/settings.po                             |    2 +-
 lib/base.php                                       |   14 +-
 lib/private/activitymanager.php                    |  194 +++
 lib/private/api.php                                |    2 +
 lib/private/app.php                                |  209 +--
 lib/private/appconfig.php                          |    4 +
 .../sabre/aborteduploaddetectionplugin.php         |   98 --
 lib/private/connector/sabre/file.php               |   20 +-
 lib/private/db/mdb2schemareader.php                |    1 +
 lib/private/files/cache/cache.php                  |   26 +-
 lib/private/files/view.php                         |   16 +-
 lib/private/installer.php                          |    3 +-
 lib/private/json.php                               |   12 +-
 lib/private/l10n.php                               |    3 +-
 lib/private/legacy/search/provider.php             |    4 +
 lib/private/preferences.php                        |   11 +-
 lib/private/repair.php                             |    2 +
 lib/private/request.php                            |    2 +-
 lib/private/route/router.php                       |   16 +
 lib/private/setup/mysql.php                        |    2 +-
 lib/private/share/share.php                        |   27 +-
 lib/public/activity/iextension.php                 |  120 ++
 lib/public/activity/imanager.php                   |   72 +-
 lib/public/route/irouter.php                       |    7 +
 lib/repair/collation.php                           |   75 ++
 lib/repair/innodb.php                              |   51 +
 search/js/result.js                                |    3 +
 settings/ajax/userlist.php                         |    3 +
 settings/js/apps.js                                |   12 +-
 settings/js/log.js                                 |    1 +
 settings/js/users/groups.js                        |   25 +-
 settings/js/users/users.js                         |  115 +-
 settings/l10n/ar.php                               |    2 +-
 settings/l10n/ast.php                              |    2 +-
 settings/l10n/ca.php                               |    2 +-
 settings/l10n/cs_CZ.php                            |    2 +-
 settings/l10n/da.php                               |    2 +-
 settings/l10n/de.php                               |    2 +-
 settings/l10n/de_DE.php                            |    2 +-
 settings/l10n/el.php                               |    2 +-
 settings/l10n/en_GB.php                            |    2 +-
 settings/l10n/es.php                               |    2 +-
 settings/l10n/es_AR.php                            |    2 +-
 settings/l10n/es_MX.php                            |    2 +-
 settings/l10n/et_EE.php                            |    2 +-
 settings/l10n/eu.php                               |    2 +-
 settings/l10n/fa.php                               |    2 +-
 settings/l10n/fi_FI.php                            |    2 +-
 settings/l10n/fr.php                               |    2 +-
 settings/l10n/gl.php                               |    2 +-
 settings/l10n/hu_HU.php                            |    2 +-
 settings/l10n/id.php                               |    2 +-
 settings/l10n/it.php                               |    2 +-
 settings/l10n/ja.php                               |    2 +-
 settings/l10n/ja_JP.php                            |    2 +-
 settings/l10n/ko.php                               |    2 +-
 settings/l10n/lt_LT.php                            |    2 +-
 settings/l10n/nb_NO.php                            |    2 +-
 settings/l10n/nl.php                               |    2 +-
 settings/l10n/pl.php                               |    2 +-
 settings/l10n/pt_BR.php                            |    2 +-
 settings/l10n/pt_PT.php                            |    2 +-
 settings/l10n/ru.php                               |    2 +-
 settings/l10n/sk_SK.php                            |    2 +-
 settings/l10n/sl.php                               |    2 +-
 settings/l10n/sv.php                               |    2 +-
 settings/l10n/tr.php                               |    2 +-
 settings/l10n/zh_CN.php                            |    2 +-
 settings/l10n/zh_TW.php                            |    2 +-
 settings/templates/admin.php                       |    2 +-
 settings/templates/apps.php                        |    2 +-
 settings/templates/users/part.grouplist.php        |    4 +-
 version.php                                        |    6 +-
 376 files changed, 3289 insertions(+), 4295 deletions(-)

diff --cc apps/activity/README.md
index b8079f4,0000000..325b32b
mode 100644,000000..100644
--- a/apps/activity/README.md
+++ b/apps/activity/README.md
@@@ -1,12 -1,0 +1,20 @@@
- activity
- ========
++# ownCloud Activity App
 +
 +The activity app for ownCloud
 +
- Provides an activity feed showing your file changes and other interesting things going on in your ownCloud.
++Provides an activity feed showing your file changes and other interesting things
++going on in your ownCloud.
 +
 +[![Build Status](https://travis-ci.org/owncloud/activity.svg?branch=master)](https://travis-ci.org/owncloud/activity)
 +
 +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/owncloud/activity/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/owncloud/activity/?branch=master)
 +
 +[![Code Coverage](https://scrutinizer-ci.com/g/owncloud/activity/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/owncloud/activity/?branch=master)
++
++# Add new activities / types for other apps
++
++With the activity manager extensions can be registered which allow any app to extend the activity behavior.
++
++In order to implement an extension create a class which implements the interface `\OCP\Activity\IExtension`.
++
++The PHPDoc comments on each method should give enough information to the developer on how to implement them.
diff --cc apps/activity/lib/data.php
index 730d70d,0000000..36fb545
mode 100644,000000..100644
--- a/apps/activity/lib/data.php
+++ b/apps/activity/lib/data.php
@@@ -1,317 -1,0 +1,339 @@@
 +<?php
 +
 +/**
 + * ownCloud - Activity App
 + *
 + * @author Frank Karlitschek
 + * @copyright 2013 Frank Karlitschek frank at 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/>.
 + *
 + */
 +
 +namespace OCA\Activity;
 +
 +/**
 + * @brief Class for managing the data in the activities
 + */
 +class Data
 +{
 +	const PRIORITY_VERYLOW 	= 10;
 +	const PRIORITY_LOW	= 20;
 +	const PRIORITY_MEDIUM	= 30;
 +	const PRIORITY_HIGH	= 40;
 +	const PRIORITY_VERYHIGH	= 50;
 +
 +	const TYPE_SHARED = 'shared';
 +	const TYPE_SHARE_EXPIRED = 'share_expired';
 +	const TYPE_SHARE_UNSHARED = 'share_unshared';
 +
 +	const TYPE_SHARE_CREATED = 'file_created';
 +	const TYPE_SHARE_CHANGED = 'file_changed';
 +	const TYPE_SHARE_DELETED = 'file_deleted';
 +	const TYPE_SHARE_RESHARED = 'file_reshared';
 +
 +	const TYPE_SHARE_DOWNLOADED = 'file_downloaded';
 +	const TYPE_SHARE_UPLOADED = 'file_uploaded';
 +
 +	const TYPE_STORAGE_QUOTA_90 = 'storage_quota_90';
 +	const TYPE_STORAGE_FAILURE = 'storage_failure';
 +
++	static protected $notificationTypes = array();
++
++	/**
++	 * @param \OC_L10N $l
++	 * @return array Array "stringID of the type" => "translated string description for the setting"
++	 */
 +	public static function getNotificationTypes(\OC_L10N $l) {
- 		return array(
++		if (isset(self::$notificationTypes[$l->getLanguageCode()]))
++		{
++			return self::$notificationTypes[$l->getLanguageCode()];
++		}
++
++		$notificationTypes = array(
 +			\OCA\Activity\Data::TYPE_SHARED => $l->t('A file or folder has been <strong>shared</strong>'),
 +//			\OCA\Activity\Data::TYPE_SHARE_UNSHARED => $l->t('Previously shared file or folder has been <strong>unshared</strong>'),
 +//			\OCA\Activity\Data::TYPE_SHARE_EXPIRED => $l->t('Expiration date of shared file or folder <strong>expired</strong>'),
 +			\OCA\Activity\Data::TYPE_SHARE_CREATED => $l->t('A new file or folder has been <strong>created</strong> in a shared folder'),
 +			\OCA\Activity\Data::TYPE_SHARE_CHANGED => $l->t('A file or folder has been <strong>changed</strong> in a shared folder'),
 +			\OCA\Activity\Data::TYPE_SHARE_DELETED => $l->t('A file or folder has been <strong>deleted</strong> from a shared folder'),
 +//			\OCA\Activity\Data::TYPE_SHARE_RESHARED => $l->t('A file or folder has been <strong>reshared</strong>'),
 +//			\OCA\Activity\Data::TYPE_SHARE_DOWNLOADED => $l->t('A file or folder shared via link has been <strong>downloaded</strong>'),
 +//			\OCA\Activity\Data::TYPE_SHARE_UPLOADED => $l->t('A file has been <strong>uploaded</strong> into a folder shared via link'),
 +//			\OCA\Activity\Data::TYPE_STORAGE_QUOTA_90 => $l->t('<strong>Storage usage</strong> is at 90%%'),
 +//			\OCA\Activity\Data::TYPE_STORAGE_FAILURE => $l->t('An <strong>external storage</strong> has an error'),
 +		);
++
++		// Allow other apps to add new notification types
++		$additionalNotificationTypes = \OC::$server->getActivityManager()->getNotificationTypes($l->getLanguageCode());
++		$notificationTypes = array_merge($notificationTypes, $additionalNotificationTypes);
++
++		self::$notificationTypes[$l->getLanguageCode()] = $notificationTypes;
++
++		return $notificationTypes;
 +	}
 +
 +	/**
 +	 * Send an event into the activity stream
 +	 *
 +	 * @param string $app The app where this event is associated with
 +	 * @param string $subject A short description of the event
 +	 * @param array  $subjectparams Array with parameters that are filled in the subject
 +	 * @param string $message A longer description of the event
 +	 * @param array  $messageparams Array with parameters that are filled in the message
 +	 * @param string $file The file including path where this event is associated with. (optional)
 +	 * @param string $link A link where this event is associated with (optional)
 +	 * @param string $affecteduser If empty the current user will be used
 +	 * @param string $type Type of the notification
 +	 * @param int    $prio Priority of the notification
 +	 * @return bool
 +	 */
 +	public static function send($app, $subject, $subjectparams = array(), $message = '', $messageparams = array(), $file = '', $link = '', $affecteduser = '', $type = '', $prio = Data::PRIORITY_MEDIUM) {
 +		$timestamp = time();
 +		$user = \OCP\User::getUser();
 +		
 +		if ($affecteduser === '') {
 +			$auser = \OCP\User::getUser();
 +		} else {
 +			$auser = $affecteduser;
 +		}
 +
 +		// store in DB
 +		$query = \OCP\DB::prepare('INSERT INTO `*PREFIX*activity`(`app`, `subject`, `subjectparams`, `message`, `messageparams`, `file`, `link`, `user`, `affecteduser`, `timestamp`, `priority`, `type`)' . ' VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )');
 +		$query->execute(array($app, $subject, serialize($subjectparams), $message, serialize($messageparams), $file, $link, $user, $auser, $timestamp, $prio, $type));
 +
 +		// fire a hook so that other apps like notification systems can connect
 +		\OCP\Util::emitHook('OC_Activity', 'post_event', array('app' => $app, 'subject' => $subject, 'user' => $user, 'affecteduser' => $affecteduser, 'message' => $message, 'file' => $file, 'link'=> $link, 'prio' => $prio, 'type' => $type));
 +
 +		return true;
 +	}
 +
 +	/**
 +	 * @brief Send an event into the activity stream
 +	 *
 +	 * @param string $app The app where this event is associated with
 +	 * @param string $subject A short description of the event
 +	 * @param array  $subjectParams Array of parameters that are filled in the placeholders
 +	 * @param string $affectedUser Name of the user we are sending the activity to
 +	 * @param string $type Type of notification
 +	 * @param int $latestSendTime Activity time() + batch setting of $affecteduser
 +	 * @return bool
 +	 */
 +	public static function storeMail($app, $subject, array $subjectParams, $affectedUser, $type, $latestSendTime) {
 +		$timestamp = time();
 +
 +		// store in DB
 +		$query = \OCP\DB::prepare('INSERT INTO `*PREFIX*activity_mq` '
 +			. ' (`amq_appid`, `amq_subject`, `amq_subjectparams`, `amq_affecteduser`, `amq_timestamp`, `amq_type`, `amq_latest_send`) '
 +			. ' VALUES(?, ?, ?, ?, ?, ?, ?)');
 +		$query->execute(array(
 +			$app,
 +			$subject,
 +			serialize($subjectParams),
 +			$affectedUser,
 +			$timestamp,
 +			$type,
 +			$latestSendTime,
 +		));
 +
 +		// fire a hook so that other apps like notification systems can connect
 +		\OCP\Util::emitHook('OC_Activity', 'post_email', array(
 +			'app'			=> $app,
 +			'subject'		=> $subject,
 +			'subjectparams'	=> $subjectParams,
 +			'affecteduser'	=> $affectedUser,
 +			'timestamp'		=> $timestamp,
 +			'type'			=> $type,
 +			'latest_send'	=> $latestSendTime,
 +		));
 +
 +		return true;
 +	}
 +
 +	/**
 +	 * Filter the activity types
 +	 *
 +	 * @param array $types
 +	 * @param string $filter
 +	 * @return array
 +	 */
 +	public static function filterNotificationTypes($types, $filter) {
 +		switch ($filter) {
 +			case 'shares':
 +				return array_intersect(array(
 +					Data::TYPE_SHARED,
 +				), $types);
 +		}
- 		return $types;
++
++		// Allow other apps to add new notification types
++		return \OC::$server->getActivityManager()->filterNotificationTypes($types, $filter);
 +	}
 +
 +	/**
 +	 * @brief Read a list of events from the activity stream
 +	 * @param int $start The start entry
 +	 * @param int $count The number of statements to read
 +	 * @param string $filter Filter the activities
 +	 * @param bool $allowGrouping Allow activities to be grouped
 +	 * @return array
 +	 */
 +	public static function read($start, $count, $filter = 'all', $allowGrouping = true) {
 +		// get current user
 +		$user = \OCP\User::getUser();
 +		$enabledNotifications = UserSettings::getNotificationTypes($user, 'stream');
 +		$enabledNotifications = Data::filterNotificationTypes($enabledNotifications, $filter);
 +
 +		// We don't want to display any activities
 +		if (empty($enabledNotifications)) {
 +			return array();
 +		}
 +
 +		$parameters = array($user);
 +		$limitActivities = " AND `type` IN ('" . implode("','", $enabledNotifications) . "')";
 +
 +		if ($filter === 'self') {
 +			$limitActivities .= ' AND `user` = ?';
 +			$parameters[] = $user;
 +		}
 +		else if ($filter === 'by') {
 +			$limitActivities .= ' AND `user` <> ?';
 +			$parameters[] = $user;
 +		}
 +		else if ($filter !== 'all') {
 +			switch ($filter) {
 +				case 'files':
 +					$limitActivities .= ' AND `app` = ?';
 +					$parameters[] = 'files';
 +				break;
 +
 +				default:
- 					\OCP\Util::emitHook('OC_Activity', 'get_filter', array(
- 						'filter'			=> $filter,
- 						'limitActivities'	=> &$limitActivities,
- 						'parameters'		=> &$parameters,
- 					));
++					list($condition, $params) = \OC::$server->getActivityManager()->getQueryForFilter($filter);
++					if (!is_null($condition)) {
++						$limitActivities .= ' ';
++						$limitActivities .= $condition;
++						if (is_array($params)) {
++							$parameters = array_merge($parameters, $params);
++						}
++					}
 +			}
 +		}
 +
 +		// fetch from DB
 +		$query = \OCP\DB::prepare(
 +			'SELECT * '
 +			. ' FROM `*PREFIX*activity` '
 +			. ' WHERE `affecteduser` = ? ' . $limitActivities
 +			. ' ORDER BY `timestamp` desc',
 +			$count, $start);
 +		$result = $query->execute($parameters);
 +
 +		return self::getActivitiesFromQueryResult($result, $allowGrouping);
 +	}
 +
 +	/**
 +	 * Process the result and return the activities
 +	 *
 +	 * @param \OC_DB_StatementWrapper|int $result
 +	 * @param bool $allowGrouping Allow activities to be grouped
 +	 * @return array
 +	 */
 +	public static function getActivitiesFromQueryResult($result, $allowGrouping = true) {
 +		$helper = new \OCA\Activity\GroupHelper($allowGrouping);
 +		if (\OCP\DB::isError($result)) {
 +			\OCP\Util::writeLog('Activity', \OC_DB::getErrorMessage($result), \OC_Log::ERROR);
 +		} else {
 +			while ($row = $result->fetchRow()) {
 +				$helper->addActivity($row);
 +			}
 +		}
 +
 +		return $helper->getActivities();
 +	}
 +
 +	/**
 +	 * Get the casted page number from $_GET
- 	 * @param string $paramName
 +	 * @return int
 +	 */
- 	public static function getPageFromParam($paramName = 'page') {
- 		if (isset($_GET[$paramName])) {
- 			return (int) $_GET[$paramName];
++	public static function getPageFromParam() {
++		if (isset($_GET['page'])) {
++			return (int) $_GET['page'];
 +		}
 +
 +		return 1;
 +	}
 +
 +	/**
- 	 * Get the casted page number from $_GET
- 	 * @param string $paramName
- 	 * @return int
++	 * Get the filter from $_GET
++	 * @return string
 +	 */
- 	public static function getFilterFromParam($paramName = 'filter') {
- 		switch ($_GET[$paramName]) {
++	public static function getFilterFromParam() {
++		if (!isset($_GET['filter']))
++			return 'all';
++
++		$filterValue = $_GET['filter'];
++		switch ($filterValue) {
 +			case 'by':
 +			case 'self':
 +			case 'shares':
 +			case 'all':
 +			case 'files':
- 				return $_GET[$paramName];
++				return $filterValue;
 +			default:
- 				$filter = 'all';
- 
- 				\OCP\Util::emitHook('OC_Activity', 'get_filter', array(
- 					'paramName'		=> $paramName,
- 					'filte'		=> &$filter,
- 				));
- 
- 				return $filter;
++				if (\OC::$server->getActivityManager()->isFilterValid($filterValue)) {
++					return $filterValue;
++				}
++				return 'all';
 +		}
 +	}
 +
 +	/**
 +	 * Delete old events
 +	 *
 +	 * @param int $expireDays Minimum 1 day
 +	 * @return null
 +	 */
 +	public static function expire($expireDays = 365) {
 +		$ttl = (60 * 60 * 24 * max(1, $expireDays));
 +
 +		$timelimit = time() - $ttl;
 +		self::deleteActivities(array(
 +			'timestamp' => array($timelimit, '<'),
 +		));
 +	}
 +
 +	/**
 +	 * Delete activities that match certain conditions
 +	 *
 +	 * @param array $conditions Array with conditions that have to be met
 +	 *                      'field' => 'value'  => `field` = 'value'
 +	 *    'field' => array('value', 'operator') => `field` operator 'value'
 +	 * @return null
 +	 */
 +	public static function deleteActivities($conditions) {
 +		$sqlWhere = '';
 +		$sqlParameters = $sqlWhereList = array();
 +		foreach ($conditions as $column => $comparison) {
 +			$sqlWhereList[] = " `$column` " . ((is_array($comparison) && isset($comparison[1])) ? $comparison[1] : '=') . ' ? ';
 +			$sqlParameters[] = (is_array($comparison)) ? $comparison[0] : $comparison;
 +		}
 +
 +		if (!empty($sqlWhereList)) {
 +			$sqlWhere = ' WHERE ' . implode(' AND ', $sqlWhereList);
 +		}
 +
 +		$query = \OCP\DB::prepare(
 +			'DELETE FROM `*PREFIX*activity`' . $sqlWhere);
 +		$query->execute($sqlParameters);
 +	}
 +}
diff --cc apps/activity/lib/datahelper.php
index 407c13d,0000000..f9a13d0
mode 100644,000000..100644
--- a/apps/activity/lib/datahelper.php
+++ b/apps/activity/lib/datahelper.php
@@@ -1,145 -1,0 +1,153 @@@
 +<?php
 +
 +/**
 + * ownCloud - Activity App
 + *
 + * @author Joas Schilling
 + * @copyright 2014 Joas Schilling nickvergessen at 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 Lesser General Public
 + * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 + *
 + */
 +
 +namespace OCA\Activity;
 +
 +class DataHelper
 +{
 +	/**
 +	 * @brief Translate an event string with the translations from the app where it was send from
 +	 * @param string $app The app where this event comes from
 +	 * @param string $text The text including placeholders
 +	 * @param array $params The parameter for the placeholder
 +	 * @param bool $stripPath Shall we strip the path from file names?
 +	 * @param bool $highlightParams Shall we highlight the parameters in the string?
 +	 *             They will be highlighted with `<strong>`, all data will be passed through
 +	 *             \OC_Util::sanitizeHTML() before, so no XSS is possible.
 +	 * @param \OC_L10N $l Language object, if you want to use a different language (f.e. to send an email)
 +	 * @return string translated
 +	 */
 +	public static function translation($app, $text, $params, $stripPath = false, $highlightParams = false, \OC_L10N $l = null) {
 +		if (!$text) {
 +			return '';
 +		}
 +		if ($l === null) {
 +			$l = \OCP\Util::getL10N('activity');
 +		}
 +
 +		if ($app === 'files') {
 +			$preparedParams = ParameterHelper::prepareParameters(
- 				$l, $app, $text,
++				$l, $text,
 +				$params, ParameterHelper::getSpecialParameterList($app, $text),
 +				$stripPath, $highlightParams
 +			);
 +			switch ($text) {
 +				case 'created_self':
 +					return $l->t('You created %1$s', $preparedParams);
 +				case 'created_by':
 +					return $l->t('%2$s created %1$s', $preparedParams);
 +				case 'changed_self':
 +					return $l->t('You changed %1$s', $preparedParams);
 +				case 'changed_by':
 +					return $l->t('%2$s changed %1$s', $preparedParams);
 +				case 'deleted_self':
 +					return $l->t('You deleted %1$s', $preparedParams);
 +				case 'deleted_by':
 +					return $l->t('%2$s deleted %1$s', $preparedParams);
 +				case 'shared_user_self':
 +					return $l->t('You shared %1$s with %2$s', $preparedParams);
 +				case 'shared_group_self':
 +					return $l->t('You shared %1$s with group %2$s', $preparedParams);
 +				case 'shared_with_by':
 +					return $l->t('%2$s shared %1$s with you', $preparedParams);
 +				case 'shared_link_self':
 +					return $l->t('You shared %1$s via link', $preparedParams);
- 				default:
- 					return $l->t($text, $preparedParams);
 +			}
- 		} else {
- 			$l = \OCP\Util::getL10N($app);
- 			return $l->t($text, $params);
 +		}
++
++		// Allow other apps to correctly translate their activities
++		$translation = \OC::$server->getActivityManager()->translate(
++			$app, $text, $params, $stripPath, $highlightParams, $l->getLanguageCode());
++
++		if ($translation !== false) {
++			return $translation;
++		}
++
++		$l = \OCP\Util::getL10N($app);
++		return $l->t($text, $params);
 +	}
 +
 +	/**
 +	 * Process the rows from the database and also groups them if requested
 +	 *
 +	 * @param array $activities
 +	 * @param bool $allowGrouping
 +	 * @return array
 +	 */
 +	public static function prepareActivities($activities, $allowGrouping = true) {
 +		$helper = new \OCA\Activity\GroupHelper($allowGrouping);
 +
 +		foreach ($activities as $row) {
 +			$helper->addActivity($row);
 +		}
 +
 +		return $helper->getActivities();
 +	}
 +
 +	/**
 +	 * Format strings for display
 +	 *
 +	 * @param array $activity
 +	 * @param string $message 'subject' or 'message'
 +	 * @return array Modified $activity
 +	 */
 +	public static function formatStrings($activity, $message) {
 +		$activity[$message . 'params'] = $activity[$message . 'params_array'];
 +		unset($activity[$message . 'params_array']);
 +
 +		$activity[$message . 'formatted'] = array(
 +			'trimmed'	=> self::translation($activity['app'], $activity[$message], $activity[$message . 'params'], true),
 +			'full'		=> self::translation($activity['app'], $activity[$message], $activity[$message . 'params']),
 +			'markup'	=> array(
 +				'trimmed'	=> self::translation($activity['app'], $activity[$message], $activity[$message . 'params'], true, true),
 +				'full'		=> self::translation($activity['app'], $activity[$message], $activity[$message . 'params'], false, true),
 +			),
 +		);
 +
 +		return $activity;
 +	}
 +
 +	/**
 +	 * Get the icon for a given activity type
 +	 *
 +	 * @param string $type
 +	 * @return string CSS class which adds the icon
 +	 */
 +	public static function getTypeIcon($type)
 +	{
 +		switch ($type)
 +		{
 +			case Data::TYPE_SHARE_CHANGED:
 +				return 'icon-change';
 +			case Data::TYPE_SHARE_CREATED:
 +				return 'icon-add-color';
 +			case Data::TYPE_SHARE_DELETED:
 +				return 'icon-delete-color';
 +			case Data::TYPE_SHARED:
 +				return 'icon-share';
 +		}
- 		return '';
++
++		// Allow other apps to add a icon for their notifications
++		return \OC::$server->getActivityManager()->getTypeIcon($type);
 +	}
 +}
diff --cc apps/activity/lib/display.php
index 25e11c8,0000000..4884f1c
mode 100644,000000..100644
--- a/apps/activity/lib/display.php
+++ b/apps/activity/lib/display.php
@@@ -1,78 -1,0 +1,78 @@@
 +<?php
 +
 +/**
 + * ownCloud - Activity App
 + *
 + * @author Joas Schilling
 + * @copyright 2014 Joas Schilling nickvergessen at 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 Lesser General Public
 + * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 + *
 + */
 +
 +namespace OCA\Activity;
 +
 +/**
 + * Class Display
 + *
 + * @package OCA\Activity
 + */
 +class Display
 +{
 +	/**
 +	 * Get the template for a specific activity-event in the activities
 +	 *
 +	 * @param array $activity An array with all the activity data in it
 +	 * @param return string
 +	 */
 +	public static function show($activity) {
 +		$tmpl = new \OCP\Template('activity', 'activity.box');
 +		$tmpl->assign('formattedDate', \OCP\Util::formatDate($activity['timestamp']));
 +		$tmpl->assign('formattedTimestamp', \OCP\relative_modified_date($activity['timestamp']));
 +		$tmpl->assign('user', $activity['user']);
 +		$tmpl->assign('displayName', \OCP\User::getDisplayName($activity['user']));
 +
 +		if ($activity['app'] === 'files') {
 +			// We do not link the subject as we create links for the parameters instead
 +			$activity['link'] = '';
 +		}
 +
 +		$tmpl->assign('event', $activity);
 +
- 		$rootView = new \OC\Files\View('');
- 		if ($activity['file'] !== null){
++		if ($activity['file']) {
++			$rootView = new \OC\Files\View('');
 +			$exist = $rootView->file_exists('/' . $activity['user'] . '/files' . $activity['file']);
 +			$is_dir = $rootView->is_dir('/' . $activity['user'] . '/files' . $activity['file']);
 +			unset($rootView);
 +
 +			// show a preview image if the file still exists
 +			if (!$is_dir && $exist) {
 +				$tmpl->assign('previewLink', \OCP\Util::linkTo('files', 'index.php', array('dir' => dirname($activity['file']))));
 +				$tmpl->assign('previewImageLink',
 +					\OCP\Util::linkToRoute('core_ajax_preview', array(
 +						'file' => $activity['file'],
 +						'x' => 150,
 +						'y' => 150,
 +					))
 +				);
 +			} else if ($exist) {
 +				$tmpl->assign('previewLink', \OCP\Util::linkTo('files', 'index.php', array('dir' => dirname($activity['file']))));
 +				$tmpl->assign('previewImageLink', \OC_Helper::mimetypeIcon('dir'));
 +				$tmpl->assign('previewLinkIsDir', true);
 +			}
 +		}
 +
 +		return $tmpl->fetchPage();
 +	}
 +}
diff --cc apps/activity/lib/grouphelper.php
index 55643f5,0000000..d9cd33b
mode 100644,000000..100644
--- a/apps/activity/lib/grouphelper.php
+++ b/apps/activity/lib/grouphelper.php
@@@ -1,153 -1,0 +1,155 @@@
 +<?php
 +
 +/**
 + * ownCloud - Activity App
 + *
 + * @author Joas Schilling
 + * @copyright 2014 Joas Schilling nickvergessen at 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 Lesser General Public
 + * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 + *
 + */
 +
 +namespace OCA\Activity;
 +
 +class GroupHelper
 +{
 +	/** @var array */
 +	protected $activities = array();
 +
 +	/** @var array */
 +	protected $openGroup = array();
 +
 +	/** @var string */
 +	protected $groupKey = '';
 +
 +	/** @var int */
 +	protected $groupTime = 0;
 +
 +	/** @var bool */
 +	protected $allowGrouping;
 +
 +	/**
 +	 * @param bool $allowGrouping
 +	 */
 +	public function __construct($allowGrouping) {
 +		$this->allowGrouping = $allowGrouping;
 +	}
 +
 +	/**
 +	 * Add an activity to the internal array
 +	 *
 +	 * @param array $activity
 +	 */
 +	public function addActivity($activity) {
 +		$activity['subjectparams_array'] = unserialize($activity['subjectparams']);
 +		if (!is_array($activity['subjectparams_array'])) {
 +			$activity['subjectparams_array'] = array($activity['subjectparams_array']);
 +		}
 +
 +		$activity['messageparams_array'] = unserialize($activity['messageparams']);
 +		if (!is_array($activity['messageparams_array'])) {
 +			$activity['messageparams_array'] = array($activity['messageparams_array']);
 +		}
 +
 +		if (!$this->getGroupKey($activity)) {
 +			$this->activities[] = $activity;
 +			return;
 +		}
 +
 +		// Only group when the event has the same group key
 +		// and the time difference is not bigger than 3 days.
 +		if ($this->getGroupKey($activity) === $this->groupKey &&
 +			abs($activity['timestamp'] - $this->groupTime) < (3 * 24 * 60 * 60)
 +		) {
 +			$parameter = $this->getGroupParameter($activity);
 +			if ($parameter !== false) {
 +				if (!is_array($this->openGroup['subjectparams_array'][$parameter])) {
 +					$this->openGroup['subjectparams_array'][$parameter] = array($this->openGroup['subjectparams_array'][$parameter]);
 +				}
 +				if (!isset($this->openGroup['activity_ids'])) {
 +					$this->openGroup['activity_ids'] = array((int) $this->openGroup['activity_id']);
 +				}
 +
 +				$this->openGroup['subjectparams_array'][$parameter][] = $activity['subjectparams_array'][$parameter];
 +				$this->openGroup['subjectparams_array'][$parameter] = array_unique($this->openGroup['subjectparams_array'][$parameter]);
 +				$this->openGroup['activity_ids'][] = (int) $activity['activity_id'];
 +			}
 +		} else {
 +			if (!empty($this->openGroup)) {
 +				$this->activities[] = $this->openGroup;
 +			}
 +
 +			$this->groupKey = $this->getGroupKey($activity);
 +			$this->groupTime = $activity['timestamp'];
 +			$this->openGroup = $activity;
 +		}
 +	}
 +
 +	/**
 +	 * Get grouping key for an activity
 +	 *
 +	 * @param array $activity
 +	 * @return false|string False, if grouping is not allowed, grouping key otherwise
 +	 */
 +	protected function getGroupKey($activity) {
 +		if ($this->getGroupParameter($activity) === false) {
 +			return false;
 +		}
 +		return $activity['app'] . '|' . $activity['user'] . '|' . $activity['subject'];
 +	}
 +
 +	protected function getGroupParameter($activity) {
 +		if (!$this->allowGrouping) {
 +			return false;
 +		}
 +
 +		if ($activity['app'] === 'files') {
 +			switch ($activity['subject']) {
 +				case 'created_self':
 +				case 'created_by':
 +				case 'changed_self':
 +				case 'changed_by':
 +				case 'deleted_self':
 +				case 'deleted_by':
 +					return 0;
 +			}
 +		}
- 		return false;
++
++		// Allow other apps to group their notifications
++		return \OC::$server->getActivityManager()->getGroupParameter($activity);
 +	}
 +
 +	/**
 +	 * Get the prepared activities
 +	 *
 +	 * @return array translated activities ready for use
 +	 */
 +	public function getActivities() {
 +		if (!empty($this->openGroup)) {
 +			$this->activities[] = $this->openGroup;
 +		}
 +
 +		$return = array();
 +		foreach ($this->activities as $activity) {
 +			$activity = DataHelper::formatStrings($activity, 'subject');
 +			$activity = DataHelper::formatStrings($activity, 'message');
 +
 +			$activity['typeicon'] = DataHelper::getTypeIcon($activity['type']);
 +			$return[] = $activity;
 +		}
 +
 +		return $return;
 +	}
 +}
diff --cc apps/activity/lib/navigation.php
index 9389a53,0000000..6456f71
mode 100644,000000..100644
--- a/apps/activity/lib/navigation.php
+++ b/apps/activity/lib/navigation.php
@@@ -1,134 -1,0 +1,133 @@@
 +<?php
 +
 +/**
 + * ownCloud - Activity App
 + *
 + * @author Joas Schilling
 + * @copyright 2014 Joas Schilling nickvergessen at 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 Lesser General Public
 + * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 + *
 + */
 +
 +namespace OCA\Activity;
 +
 +/**
 + * Class Navigation
 + *
 + * @package OCA\Activity
 + */
 +class Navigation {
 +	/** @var \OC_L10N */
 +	protected $l;
 +
 +	/** @var string */
 +	protected $active;
 +
 +	/** @var string */
 +	protected $rssLink;
 +
 +	/**
 +	 * Construct
 +	 *
 +	 * @param \OC_L10N $l
 +	 * @param null|string $active Navigation entry that should be marked as active
 +	 */
 +	public function __construct(\OC_L10N $l, $active = 'all') {
 +		$this->l = $l;
 +		$this->active = $active;
 +		$this->rssLink = '';
 +	}
 +
 +	/**
 +	 * Get the users we want to send an email to
 +	 *
 +	 * @param null|string $forceActive Navigation entry that should be marked as active
 +	 * @return \OCP\Template
 +	 */
 +	public function getTemplate($forceActive = null) {
 +		$active = $forceActive ?: $this->active;
 +
 +		$template = new \OCP\Template('activity', 'navigation', '');
 +		$entries = $this->getLinkList();
 +
- 		\OCP\Util::emitHook('OC_Activity', 'get_navigation', array(
- 			'active'		=> &$active,
- 			'entries'		=> &$entries,
- 		));
++		$additionalEntries = \OC::$server->getActivityManager()->getNavigation();
++		$entries['apps'] = array_merge($entries['apps'], $additionalEntries['apps']);
++		$entries['top'] = array_merge($entries['top'], $additionalEntries['top']);
 +
 +		if (sizeof($entries['apps']) === 1) {
 +			// If there is only the files app, we simply do not show it,
 +			// as it is the same as the 'all' filter.
 +			$entries['apps'] = array();
 +		}
 +
 +		$template->assign('activeNavigation', $active);
 +		$template->assign('navigations', $entries);
 +		$template->assign('rssLink', $this->rssLink);
 +
 +		return $template;
 +	}
 +
 +	public function setRSSToken($rssToken) {
 +		if ($rssToken) {
 +			$this->rssLink = \OCP\Util::linkToAbsolute('activity', 'rss.php', array('token' => $rssToken));
 +		}
 +		else {
 +			$this->rssLink = '';
 +		}
 +	}
 +
 +	/**
 +	 * Get all items for the users we want to send an email to
 +	 *
 +	 * @return array Notification data (user => array of rows from the table)
 +	 */
 +	protected function getLinkList() {
 +		$topEntries = array(
 +			array(
 +				'id' => 'all',
 +				'name' => (string) $this->l->t('All Activities'),
 +				'url' => \OCP\Util::linkToAbsolute('activity', 'index.php'),
 +			),
 +			array(
 +				'id' => 'self',
 +				'name' => (string) $this->l->t('Activities by you'),
 +				'url' => \OCP\Util::linkToAbsolute('activity', 'index.php', array('filter' => 'self')),
 +			),
 +			array(
 +				'id' => 'by',
 +				'name' => (string) $this->l->t('Activities by others'),
 +				'url' => \OCP\Util::linkToAbsolute('activity', 'index.php', array('filter' => 'by')),
 +			),
 +			array(
 +				'id' => 'shares',
 +				'name' => (string) $this->l->t('Shares'),
 +				'url' => \OCP\Util::linkToAbsolute('activity', 'index.php', array('filter' => 'shares')),
 +			),
 +		);
 +
 +		$appFilterEntries = array(
 +			array(
 +				'id' => 'files',
 +				'name' => (string) $this->l->t('Files'),
 +				'url' => \OCP\Util::linkToAbsolute('activity', 'index.php', array('filter' => 'files')),
 +			),
 +		);
 +
 +		return array(
 +			'top'		=> $topEntries,
 +			'apps'		=> $appFilterEntries,
 +		);
 +	}
 +}
diff --cc apps/activity/lib/parameterhelper.php
index 66472ab,0000000..8622c07
mode 100644,000000..100644
--- a/apps/activity/lib/parameterhelper.php
+++ b/apps/activity/lib/parameterhelper.php
@@@ -1,258 -1,0 +1,257 @@@
 +<?php
 +
 +/**
 + * ownCloud - Activity App
 + *
 + * @author Joas Schilling
 + * @copyright 2014 Joas Schilling nickvergessen at 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 Lesser General Public
 + * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 + *
 + */
 +
 +namespace OCA\Activity;
 +
 +class ParameterHelper
 +{
 +	/**
 +	 * Prepares the parameters before we use them in the subject or message
 +	 * @param \OC_L10N $l Language object, if you want to use a different language (f.e. to send an email)
- 	 * @param string $app
 +	 * @param string $text
 +	 * @param array $params
 +	 * @param array $paramTypes Type of parameters, if they need special handling
 +	 * @param bool $stripPath Shall we remove the path from the filename
 +	 * @param bool $highlightParams
 +	 * @return array
 +	 */
- 	public static function prepareParameters(\OC_L10N $l, $app, $text, $params, $paramTypes = array(), $stripPath = false, $highlightParams = false) {
- 		if ($app !== 'files' || !$text) {
++	public static function prepareParameters(\OC_L10N $l, $text, $params, $paramTypes = array(), $stripPath = false, $highlightParams = false) {
++		if (!$text) {
 +			return $params;
 +		}
 +
 +		$preparedParams = array();
 +		foreach ($params as $i => $param) {
 +			if (is_array($param)) {
 +				$preparedParams[] = self::prepareArrayParameter($l, $param, $paramTypes[$i], $stripPath, $highlightParams);
 +			} else {
 +				$preparedParams[] = self::prepareStringParameter($param, isset($paramTypes[$i]) ? $paramTypes[$i] : '', $stripPath, $highlightParams);
 +			}
 +		}
 +		return $preparedParams;
 +	}
 +
 +	/**
 +	 * Prepares a string parameter before we use it in the subject or message
 +	 *
 +	 * @param string $param
 +	 * @param string $paramType Type of parameter, if it needs special handling
 +	 * @param bool $stripPath Shall we remove the path from the filename
 +	 * @param bool $highlightParams
 +	 * @return string
 +	 */
 +	public static function prepareStringParameter($param, $paramType, $stripPath, $highlightParams) {
 +		if ($paramType === 'file') {
 +			return self::prepareFileParam($param, $stripPath, $highlightParams);
 +		} else if ($paramType === 'username') {
 +			return self::prepareUserParam($param, $highlightParams);
 +		}
 +		return self::prepareParam($param, $highlightParams);
 +	}
 +
 +	/**
 +	 * Prepares an array parameter before we use it in the subject or message
 +	 *
 +	 * @param \OC_L10N $l Language object, if you want to use a different language (f.e. to send an email)
 +	 * @param array $params
 +	 * @param string $paramType Type of parameters, if it needs special handling
 +	 * @param bool $stripPath Shall we remove the path from the filename
 +	 * @param bool $highlightParams
 +	 * @return string
 +	 */
 +	public static function prepareArrayParameter(\OC_L10N $l, $params, $paramType, $stripPath, $highlightParams) {
 +		$parameterList = $plainParameterList = array();
 +		foreach ($params as $parameter) {
 +			if ($paramType === 'file') {
 +				$parameterList[] = self::prepareFileParam($parameter, $stripPath, $highlightParams);
 +				$plainParameterList[] = self::prepareFileParam($parameter, false, false);
 +			} else {
 +				$parameterList[] = self::prepareParam($parameter, $highlightParams);
 +				$plainParameterList[] = self::prepareParam($parameter, false);
 +			}
 +		}
 +		return self::joinParameterList($l, $parameterList, $plainParameterList, $highlightParams);
 +	}
 +
 +	/**
 +	 * Prepares a parameter for usage by adding highlights
 +	 *
 +	 * @param string $param
 +	 * @param bool $highlightParams
 +	 * @return string
 +	 */
 +	protected static function prepareParam($param, $highlightParams) {
 +		if ($highlightParams) {
 +			return '<strong>' . \OC_Util::sanitizeHTML($param) . '</strong>';
 +		} else {
 +			return $param;
 +		}
 +	}
 +
 +	/**
 +	 * Prepares a user name parameter for usage
 +	 *
 +	 * Add an avatar to usernames
 +	 *
 +	 * @param string $param
 +	 * @param bool $highlightParams
 +	 * @return string
 +	 */
 +	protected static function prepareUserParam($param, $highlightParams) {
 +		if ($highlightParams) {
 +			$param = \OC_Util::sanitizeHTML($param);
 +			return '<div class="avatar" data-user="' . $param . '"></div>'
 +				. '<strong>' . \OC_User::getDisplayName($param) . '</strong>';
 +		} else {
 +			return \OC_User::getDisplayName($param);
 +		}
 +	}
 +
 +	/**
 +	 * Prepares a file parameter for usage
 +	 *
 +	 * Removes the path from filenames and adds highlights
 +	 *
 +	 * @param string $param
 +	 * @param bool $stripPath Shall we remove the path from the filename
 +	 * @param bool $highlightParams
 +	 * @return string
 +	 */
 +	protected static function prepareFileParam($param, $stripPath, $highlightParams) {
 +		$param = self::fixLegacyFilename($param);
 +
 +		$parent_dir = (substr_count($param, '/') == 1) ? '/' : dirname($param);
 +		$fileLink = \OCP\Util::linkTo('files', 'index.php', array('dir' => $parent_dir));
 +		$param = trim($param, '/');
 +
 +		if (!$stripPath) {
 +			if (!$highlightParams) {
 +				return $param;
 +			}
 +			return '<a class="filename" href="' . $fileLink . '">' . \OC_Util::sanitizeHTML($param) . '</a>';
 +		}
 +
 +		if (!$highlightParams) {
 +			return self::stripPathFromFilename($param);
 +		}
 +
 +		$title = ' title="' . \OC_Util::sanitizeHTML($param) . '"';
 +		$newParam = self::stripPathFromFilename($param);
 +		return '<a class="filename tooltip" href="' . $fileLink . '"' . $title . '>' . \OC_Util::sanitizeHTML($newParam) . '</a>';
 +	}
 +
 +	/**
 +	 * Prepend leading slash to filenames of legacy activities
 +	 * @param string $filename
 +	 * @return string
 +	 */
 +	protected static function fixLegacyFilename($filename) {
 +		if (strpos($filename, '/') !== 0) {
 +			return '/' . $filename;
 +		}
 +		return $filename;
 +	}
 +
 +	/**
 +	 * Remove the path from the file string
 +	 * @param string $filename
 +	 * @return string
 +	 */
 +	protected static function stripPathFromFilename($filename) {
 +		if (strrpos($filename, '/') !== false) {
 +			// Remove the path from the file string
 +			return substr($filename, strrpos($filename, '/') + 1);
 +		}
 +		return $filename;
 +	}
 +
 +	/**
 +	 * Returns a list of grouped parameters
 +	 *
 +	 * 2 parameters are joined by "and":
 +	 * => A and B
 +	 * Up to 5 parameters are joined by "," and "and":
 +	 * => A, B, C, D and E
 +	 * More than 5 parameters are joined by "," and trimmed:
 +	 * => A, B, C and #n more
 +	 *
 +	 * @param \OC_L10N $l
 +	 * @param array $parameterList
 +	 * @param array $plainParameterList
 +	 * @param bool $highlightParams
 +	 * @return string
 +	 */
 +	protected static function joinParameterList(\OC_L10N $l, $parameterList, $plainParameterList, $highlightParams) {
 +		if (empty($parameterList)) {
 +			return '';
 +		}
 +
 +		$count = sizeof($parameterList);
 +		$lastItem = array_pop($parameterList);
 +
 +		if ($count == 1)
 +		{
 +			return $lastItem;
 +		}
 +		else if ($count == 2)
 +		{
 +			$firstItem = array_pop($parameterList);
 +			return $l->t('%s and %s', array($firstItem, $lastItem));
 +		}
 +		else if ($count <= 5)
 +		{
 +			$list = implode($l->t(', '), $parameterList);
 +			return $l->t('%s and %s', array($list, $lastItem));
 +		}
 +
 +		$firstParams = array_slice($parameterList, 0, 3);
 +		$firstList = implode($l->t(', '), $firstParams);
 +		$trimmedParams = array_slice($plainParameterList, 3);
 +		$trimmedList = implode($l->t(', '), $trimmedParams);
 +		if ($highlightParams) {
 +			return $l->n(
 +				'%s and <strong class="tooltip" title="%s">%n more</strong>',
 +				'%s and <strong class="tooltip" title="%s">%n more</strong>',
 +				$count - 3,
 +				array($firstList, $trimmedList));
 +		}
 +		return $l->n('%s and %n more', '%s and %n more', $count - 3, array($firstList));
 +	}
 +
 +	/**
 +	 * List with special parameters for the message
 +	 *
 +	 * @param string $app
 +	 * @param string $text
 +	 * @return array
 +	 */
 +	public static function getSpecialParameterList($app, $text) {
 +		if ($app === 'files' && $text === 'shared_group_self') {
 +			return array(0 => 'file');
 +		}
 +		else if ($app === 'files') {
 +			return array(0 => 'file', 1 => 'username');
 +		}
 +		return array();
 +	}
 +}
diff --cc apps/activity/lib/usersettings.php
index f633fb8,0000000..e0e2b99
mode 100644,000000..100644
--- a/apps/activity/lib/usersettings.php
+++ b/apps/activity/lib/usersettings.php
@@@ -1,177 -1,0 +1,181 @@@
 +<?php
 +
 +/**
 + * ownCloud - Activity App
 + *
 + * @author Joas Schilling
 + * @copyright 2014 Joas Schilling nickvergessen at 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 Lesser General Public
 + * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 + *
 + */
 +
 +namespace OCA\Activity;
 +
 +use \OCP\Config;
 +
 +/**
 + * Class UserSettings
 + *
 + * @package OCA\Activity
 + */
 +class UserSettings
 +{
 +	const EMAIL_SEND_HOURLY = 0;
 +	const EMAIL_SEND_DAILY = 1;
 +	const EMAIL_SEND_WEEKLY = 2;
 +
 +	/**
 +	 * Get a setting for a user
 +	 *
 +	 * Falls back to some good default values if the user does not have a preference
 +	 *
 +	 * @param string $user
 +	 * @param string $method Should be one of 'stream', 'email' or 'setting'
 +	 * @param string $type One of the activity types or 'batchtime'
 +	 * @return mixed
 +	 */
 +	public static function getUserSetting($user, $method, $type) {
 +		return Config::getUserValue(
 +			$user,
 +			'activity',
 +			'notify_' . $method . '_' . $type,
 +			self::getDefaultSetting($method, $type)
 +		);
 +	}
 +
 +	/**
 +	 * Get a good default setting for a preference
 +	 *
 +	 * @param string $method Should be one of 'stream', 'email' or 'setting'
 +	 * @param string $type One of the activity types or 'batchtime'
 +	 * @return bool|int
 +	 */
 +	public static function getDefaultSetting($method, $type) {
 +		if ($method == 'setting' && $type == 'batchtime') {
 +			return 3600;
 +		}
 +
 +		$settings = self::getDefaultTypes($method);
 +		return in_array($type, $settings);
 +	}
 +
 +	/**
 +	 * Get the default selection of types for a method
 +	 *
 +	 * @param string $method Should be one of 'stream' or 'email'
 +	 * @return array Array of strings
 +	 */
 +	public static function getDefaultTypes($method) {
 +		$settings = array();
 +		switch ($method) {
 +			case 'stream':
 +				$settings[] = Data::TYPE_SHARE_CREATED;
 +				$settings[] = Data::TYPE_SHARE_CHANGED;
 +				$settings[] = Data::TYPE_SHARE_DELETED;
 +//				$settings[] = Data::TYPE_SHARE_RESHARED;
 +//
 +//				$settings[] = Data::TYPE_SHARE_DOWNLOADED;
 +
 +			case 'email':
 +				$settings[] = Data::TYPE_SHARED;
 +//				$settings[] = Data::TYPE_SHARE_EXPIRED;
 +//				$settings[] = Data::TYPE_SHARE_UNSHARED;
 +//
 +//				$settings[] = Data::TYPE_SHARE_UPLOADED;
 +//
 +//				$settings[] = Data::TYPE_STORAGE_QUOTA_90;
 +//				$settings[] = Data::TYPE_STORAGE_FAILURE;
 +		}
 +
++		// Allow other apps to add notification types to the default setting
++		$additionalSettings = \OC::$server->getActivityManager()->getDefaultTypes($method);
++		$settings = array_merge($settings, $additionalSettings);
++
 +		return $settings;
 +	}
 +
 +	/**
 +	 * Get a list with enabled notification types for a user
 +	 *
 +	 * @param string	$user	Name of the user
 +	 * @param string	$method	Should be one of 'stream' or 'email'
 +	 * @return array
 +	 */
 +	public static function getNotificationTypes($user, $method) {
 +		$l = \OC_L10N::get('activity');
 +		$types = Data::getNotificationTypes($l);
 +
 +		$notificationTypes = array();
 +		foreach ($types as $type => $desc) {
 +			if (self::getUserSetting($user, $method, $type)) {
 +				$notificationTypes[] = $type;
 +			}
 +		}
 +
 +		return $notificationTypes;
 +	}
 +
 +	/**
 +	 * Filters the given user array by their notification setting
 +	 *
 +	 * @param array $users
 +	 * @param string $method
 +	 * @param string $type
 +	 * @return array Returns a "username => b:true" Map for method = stream
 +	 *               Returns a "username => i:batchtime" Map for method = email
 +	 */
 +	public static function filterUsersBySetting($users, $method, $type) {
 +		if (empty($users) || !is_array($users)) {
 +			return array();
 +		}
 +
 +		$preferences = new \OC\Preferences(\OC_DB::getConnection());
 +		$filteredUsers = array();
 +
 +		$potentialUsers = $preferences->getValueForUsers('activity', 'notify_' . $method . '_' . $type, $users);
 +		foreach ($potentialUsers as $user => $value) {
 +			if ($value) {
 +				$filteredUsers[$user] = true;
 +			}
 +			unset($users[array_search($user, $users)]);
 +		}
 +
 +		// Get the batch time setting from the database
 +		if ($method == 'email') {
 +			$potentialUsers = $preferences->getValueForUsers('activity', 'notify_setting_batchtime', array_keys($filteredUsers));
 +			foreach ($potentialUsers as $user => $value) {
 +				$filteredUsers[$user] = $value;
 +			}
 +		}
 +
 +		if (empty($users)) {
 +			return $filteredUsers;
 +		}
 +
 +		// If the setting is enabled by default,
 +		// we add all users that didn't set the preference yet.
 +		if (UserSettings::getDefaultSetting($method, $type)) {
 +			foreach ($users as $user) {
 +				if ($method == 'stream') {
 +					$filteredUsers[$user] = true;
 +				} else {
 +					$filteredUsers[$user] = self::getDefaultSetting('setting', 'batchtime');
 +				}
 +			}
 +		}
 +
 +		return $filteredUsers;
 +	}
 +}
diff --cc apps/activity/tests/datahelpertest.php
index 7142552,0000000..9746b24
mode 100644,000000..100644
--- a/apps/activity/tests/datahelpertest.php
+++ b/apps/activity/tests/datahelpertest.php
@@@ -1,198 -1,0 +1,198 @@@
 +<?php
 +
 +/**
 + * ownCloud - Activity App
 + *
 + * @author Joas Schilling
 + * @copyright 2014 Joas Schilling nickvergessen at 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\Activity\Tests;
 +
 +class DataHelperTest extends \PHPUnit_Framework_TestCase {
 +	protected $originalWEBROOT;
 +
 +	public function setUp() {
 +		parent::setUp();
 +		$this->originalWEBROOT =\OC::$WEBROOT;
 +		\OC::$WEBROOT = '';
 +	}
 +
 +	public function tearDown() {
 +		\OC::$WEBROOT = $this->originalWEBROOT;
 +		parent::tearDown();
 +	}
 +
 +	public function prepareParametersData() {
 +		return array(
 +			array(array(), false, false, false, array()),
 +
 +			// No file position: no path strip
 +			array(array('/foo/bar.file'), array(), false, false, array('/foo/bar.file')),
 +			array(array('/foo/bar.file'), array(), true, false, array('/foo/bar.file')),
 +			array(array('/foo/bar.file'), array(), false, true, array('<strong>/foo/bar.file</strong>')),
 +			array(array('/foo/bar.file'), array(), true, true, array('<strong>/foo/bar.file</strong>')),
 +
 +			// Valid file position
 +			array(array('/foo/bar.file'), array(0 => 'file'), true, false, array('bar.file')),
 +			array(array('/folder/trailingslash/fromsharing/'), array(0 => 'file'), true, false, array('fromsharing')),
 +			array(array('/foo/bar.file'), array(0 => 'file'), false, false, array('foo/bar.file')),
 +			array(array('/folder/trailingslash/fromsharing/'), array(0 => 'file'), false, false, array('folder/trailingslash/fromsharing')),
 +			array(array('/foo/bar.file'), array(0 => 'file'), true, true, array(
 +				'<a class="filename tooltip" href="/index.php/apps/files?dir=%2Ffoo" title="foo/bar.file">bar.file</a>',
 +			)),
 +			array(array('/foo/bar.file'), array(1 => 'file'), true, false, array('/foo/bar.file')),
 +			array(array('/foo/bar.file'), array(1 => 'file'), true, true, array('<strong>/foo/bar.file</strong>')),
 +
 +			// Legacy: stored without leading slash
 +			array(array('foo/bar.file'), array(0 => 'file'), false, false, array('foo/bar.file')),
 +			array(array('foo/bar.file'), array(0 => 'file'), false, true, array(
 +				'<a class="filename" href="/index.php/apps/files?dir=%2Ffoo">foo/bar.file</a>',
 +			)),
 +			array(array('foo/bar.file'), array(0 => 'file'), true, false, array('bar.file')),
 +			array(array('foo/bar.file'), array(0 => 'file'), true, true, array(
 +				'<a class="filename tooltip" href="/index.php/apps/files?dir=%2Ffoo" title="foo/bar.file">bar.file</a>',
 +			)),
 +
 +			// Valid file position
 +			array(array('UserA', '/foo/bar.file'), array(1 => 'file'), true, false, array('UserA', 'bar.file')),
 +			array(array('UserA', '/foo/bar.file'), array(1 => 'file'), true, true, array(
 +				'<strong>UserA</strong>',
 +				'<a class="filename tooltip" href="/index.php/apps/files?dir=%2Ffoo" title="foo/bar.file">bar.file</a>',
 +			)),
 +			array(array('UserA', '/foo/bar.file'), array(2 => 'file'), true, false, array('UserA', '/foo/bar.file')),
 +			array(array('UserA', '/foo/bar.file'), array(2 => 'file'), true, true, array(
 +				'<strong>UserA</strong>',
 +				'<strong>/foo/bar.file</strong>',
 +			)),
 +			array(array('UserA', '/foo/bar.file'), array(0 => 'username'), true, true, array(
 +				'<div class="avatar" data-user="UserA"></div><strong>UserA</strong>',
 +				'<strong>/foo/bar.file</strong>',
 +			)),
 +
 +			array(array('UserA', '/foo/bar.file'), array(0 => 'username', 1 => 'file'), true, true, array(
 +				'<div class="avatar" data-user="UserA"></div><strong>UserA</strong>',
 +				'<a class="filename tooltip" href="/index.php/apps/files?dir=%2Ffoo" title="foo/bar.file">bar.file</a>',
 +			)),
 +		);
 +	}
 +
 +	/**
 +	 * @dataProvider prepareParametersData
 +	 */
 +	public function testPrepareParameters($params, $filePosition, $stripPath, $highlightParams, $expected) {
 +		$l = \OC_L10N::get('activity');
 +		$this->assertEquals(
 +			$expected,
- 			\OCA\Activity\ParameterHelper::prepareParameters($l, 'files', 'action', $params, $filePosition, $stripPath, $highlightParams)
++			\OCA\Activity\ParameterHelper::prepareParameters($l, 'action', $params, $filePosition, $stripPath, $highlightParams)
 +		);
 +	}
 +
 +	public function translationData() {
 +		return array(
 +			array(
 +				'created_self', array('/SubFolder/A.txt'), false, false,
 +				'You created SubFolder/A.txt',
 +			),
 +			array(
 +				'created_self', array('/SubFolder/A.txt'), true, false,
 +				'You created A.txt',
 +			),
 +			array(
 +				'created_self', array('/SubFolder/A.txt'), false, true,
 +				'You created <a class="filename" href="/index.php/apps/files?dir=%2FSubFolder">SubFolder/A.txt</a>',
 +			),
 +			array(
 +				'created_self', array('/SubFolder/A.txt'), true, true,
 +				'You created <a class="filename tooltip" href="/index.php/apps/files?dir=%2FSubFolder" title="SubFolder/A.txt">A.txt</a>',
 +			),
 +
 +			array('created_by', array('/SubFolder/A.txt', 'UserB'), false, false, 'UserB created SubFolder/A.txt'),
 +			array('created_by', array('/SubFolder/A.txt', 'UserB'), true, false, 'UserB created A.txt'),
 +			array(
 +				'created_by', array('/SubFolder/A.txt', 'UserB'), false, true,
 +				'<div class="avatar" data-user="UserB"></div><strong>UserB</strong> created '
 +				. '<a class="filename" href="/index.php/apps/files?dir=%2FSubFolder">SubFolder/A.txt</a>',
 +			),
 +			array(
 +				'created_by', array('/SubFolder/A.txt', 'UserB'), true, true,
 +				'<div class="avatar" data-user="UserB"></div><strong>UserB</strong> created '
 +				. '<a class="filename tooltip" href="/index.php/apps/files?dir=%2FSubFolder" title="SubFolder/A.txt">A.txt</a>',
 +			),
 +			array(
 +				'created_by', array('/A.txt', 'UserB'), true, true,
 +				'<div class="avatar" data-user="UserB"></div><strong>UserB</strong> created '
 +				. '<a class="filename tooltip" href="/index.php/apps/files?dir=%2F" title="A.txt">A.txt</a>',
 +			),
 +
 +			array(
 +				'created_self',
 +				array(array('/SubFolder/A.txt')),
 +				false,
 +				false,
 +				'You created SubFolder/A.txt',
 +			),
 +			array(
 +				'created_self',
 +				array(array('/SubFolder/A.txt', '/SubFolder/B.txt')),
 +				false,
 +				false,
 +				'You created SubFolder/A.txt and SubFolder/B.txt',
 +			),
 +			array(
 +				'created_self',
 +				array(array('/SubFolder/A.txt', '/SubFolder/B.txt', '/SubFolder/C.txt', '/SubFolder/D.txt', '/SubFolder/E.txt')),
 +				false,
 +				false,
 +				'You created SubFolder/A.txt, SubFolder/B.txt, SubFolder/C.txt, SubFolder/D.txt and SubFolder/E.txt',
 +			),
 +			array(
 +				'created_self',
 +				array(array('/SubFolder/A.txt', '/SubFolder/B.txt', '/SubFolder/C.txt', '/SubFolder/D.txt', '/SubFolder/E.txt', '/SubFolder/F.txt')),
 +				false,
 +				false,
 +				'You created SubFolder/A.txt, SubFolder/B.txt, SubFolder/C.txt and 3 more',
 +			),
 +			array(
 +				'created_self',
 +				array(array('/SubFolder/A.txt', '/SubFolder/B.txt', '/SubFolder/C.txt', '/SubFolder/D.txt', '/SubFolder/E.txt', '/SubFolder/F.txt')),
 +				true,
 +				false,
 +				'You created A.txt, B.txt, C.txt and 3 more',
 +			),
 +			array(
 +				'created_self',
 +				array(array('/SubFolder/A.txt', '/SubFolder/B.txt', '/SubFolder/C.txt', '/SubFolder/D.txt', '/SubFolder/E.txt', '/SubFolder/F.txt')),
 +				false,
 +				true,
 +				'You created <a class="filename" href="/index.php/apps/files?dir=%2FSubFolder">SubFolder/A.txt</a>,'
 +				. ' <a class="filename" href="/index.php/apps/files?dir=%2FSubFolder">SubFolder/B.txt</a>,'
 +				. ' <a class="filename" href="/index.php/apps/files?dir=%2FSubFolder">SubFolder/C.txt</a>'
 +				. ' and <strong class="tooltip" title="SubFolder/D.txt, SubFolder/E.txt, SubFolder/F.txt">3 more</strong>',
 +			),
 +		);
 +	}
 +
 +	/**
 +	 * @dataProvider translationData
 +	 */
 +	public function testTranslation($text, $params, $stripPath, $highlightParams, $expected) {
 +		$this->assertEquals(
 +			$expected,
 +			(string) \OCA\Activity\DataHelper::translation('files', $text, $params, $stripPath, $highlightParams)
 +		);
 +	}
 +}
diff --cc apps/activity/tests/datatest.php
index 0000000,0000000..05e8380
new file mode 100644
--- /dev/null
+++ b/apps/activity/tests/datatest.php
@@@ -1,0 -1,0 +1,52 @@@
++<?php
++
++/**
++ * ownCloud - Activity App
++ *
++ * @author Joas Schilling
++ * @copyright 2014 Joas Schilling nickvergessen at 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\Activity\Tests;
++
++class DataTest extends \PHPUnit_Framework_TestCase {
++	public function getFilterFromParamData() {
++		return array(
++			array('test', 'all'),
++			array('all', 'all'),
++			array('by', 'by'),
++			array('files', 'files'),
++			array('self', 'self'),
++			array('shares', 'shares'),
++			array('test', 'all'),
++			array(null, 'all'),
++		);
++	}
++
++	/**
++	 * @dataProvider getFilterFromParamData
++	 */
++	public function testGetFilterFromParam($globalValue, $expected) {
++		if ($globalValue !== null) {
++			$_GET['filter'] = $globalValue;
++		}
++
++		$this->assertEquals(
++			$expected,
++			\OCA\Activity\Data::getFilterFromParam()
++		);
++	}
++}
diff --cc apps/calendar/appinfo/info.xml
index 5507053,0000000..168258e
mode 100644,000000..100644
--- a/apps/calendar/appinfo/info.xml
+++ b/apps/calendar/appinfo/info.xml
@@@ -1,19 -1,0 +1,20 @@@
 +<?xml version="1.0"?> 
 +<info>
 +	<id>calendar</id>
 +	<name>Calendar</name>
 +	<licence>AGPL</licence>
 +	<author>Georg Ehrke, Bart Visscher, Jakob Sack</author>
 +	<require>4.93</require>
 +	<shipped>true</shipped>
 +	<description>Calendar with CalDAV support</description>
 +	<default_enable/>
 +	<remote>
 +		<calendar>appinfo/remote.php</calendar>
 +		<caldav>appinfo/remote.php</caldav>
 +	</remote>
 +	<public>
 +		<calendar>share.php</calendar>
 +		<caldav>share.php</caldav>
 +	</public>
++	<ocsid>166043</ocsid>
 +</info>
diff --cc apps/calendar/appinfo/version
index 844f6a9,0000000..d2b13eb
mode 100644,000000..100644
--- a/apps/calendar/appinfo/version
+++ b/apps/calendar/appinfo/version
@@@ -1,1 -1,0 +1,1 @@@
- 0.6.3
++0.6.4
diff --cc apps/calendar/js/on-event.js
index a326c9f,0000000..accb0e1
mode 100644,000000..100644
--- a/apps/calendar/js/on-event.js
+++ b/apps/calendar/js/on-event.js
@@@ -1,86 -1,0 +1,86 @@@
 +$(document).on('click', '#newCalendar', function () {
 +	Calendar.UI.Calendar.newCalendar(this);
 +});
 +$(document).on('click', '#caldav_url_close', function () {
 +	$('#caldav_url').hide();$('#caldav_url_close').hide();
 +});
 +$(document).on('mouseover', '#caldav_url', function () {
 +	$('#caldav_url').select();
 +});
- $(document).on('click', document).on('click', '#primarycaldav', function () {
++$(document).on('click', '#primarycaldav', function () {
 +	$('#primarycaldav').select();
 +});
 +$(document).on('click', '#ioscaldav', function () {
 +	$('#ioscaldav').select();
 +});
 +$(document).on('click', '#editCategories', function () {
 +	$(this).tipsy('hide');OC.Tags.edit('event');
 +});
 +$(document).on('click', '#allday_checkbox', function () {
 +	Calendar.UI.lockTime();
 +});
 +$(document).on('click', '#advanced_options_button', function () {
 +	Calendar.UI.showadvancedoptions();
 +});
 +$(document).on('click', '#advanced_options_button_repeat', function () {
 +	Calendar.UI.showadvancedoptionsforrepeating();
 +});
 +$(document).on('click', '#submitNewEvent', function () {
 +	Calendar.UI.validateEventForm($(this).data('link'));
 +});
 +$(document).on('click', '#chooseCalendar', function () {
 +	Calendar.UI.Calendar.newCalendar(this);
 +});
 +$(document).on('change', '.activeCalendar', function () {
 +	Calendar.UI.Calendar.activation(this,$(this).data('id'));
 +});
 +$(document).on('change', '#active_shared_events', function () {
 +	Calendar.UI.Calendar.sharedEventsActivation(this);
 +});
 +$(document).on('click', '#allday_checkbox', function () {
 +	Calendar.UI.lockTime();
 +});
 +$(document).on('click', '#editEvent-submit', function () {
 +	console.log('submit-event');
 +	Calendar.UI.validateEventForm($(this).data('link'));
 +});
 +$(document).on('click', '#editEvent-delete', function () {
 +	Calendar.UI.submitDeleteEventForm($(this).data('link'));
 +});
 +$(document).on('click', '#editEvent-export', function () {
 +	window.location = $(this).data('link');
 +});
 +$(document).on('click', '#chooseCalendar-showCalDAVURL', function () {
 +	Calendar.UI.showCalDAVUrl($(this).data('user'), $(this).data('caldav'));
 +});
 +$(document).on('click', '#chooseCalendar-edit', function () {
 +	Calendar.UI.Calendar.edit($(this), $(this).data('id'));
 +});
 +$(document).on('click', '#chooseCalendar-delete', function () {
 +	Calendar.UI.Calendar.deleteCalendar($(this).data('id'));
 +});
 +$(document).on('click', '#editCalendar-submit', function () {
 +	Calendar.UI.Calendar.submit($(this), $(this).data('id'));
 +});
 +$(document).on('click', '#editCalendar-cancel', function () {
 +	Calendar.UI.Calendar.cancel($(this), $(this).data('id'));
 +});
 +$(document).on('click', '.choosecalendar-rowfield-active', function () {
 +	Calendar.UI.Share.activation($(this), $(this).data('id'));
 +});
 +$(document).on('focus', "#event-location:not(.ui-autocomplete-input)", function (event) {
 +	$(this).autocomplete({
 +		source: OC.linkTo('calendar', 'ajax/search-location.php'),
 +		minLength: 2
 +	});
 +});
 +$(document).on('keydown', '#newcalendar_dialog #displayname_new', function(event){
 +	if (event.which == 13){
 +		$('#newcalendar_dialog #editCalendar-submit').click();
 +	}
 +});
 +$(document).on('keydown', '#editcalendar_dialog > span > input:text', function(event){
 +	if (event.which == 13){
 +		$('#editcalendar_dialog #editCalendar-submit').click();
 +	}
 +});
diff --cc apps/calendar/lib/object.php
index 49b43bc,0000000..7193094
mode 100644,000000..100644
--- a/apps/calendar/lib/object.php
+++ b/apps/calendar/lib/object.php
@@@ -1,1145 -1,0 +1,1147 @@@
 +<?php
 +/**
 + * Copyright (c) 2011 Jakob Sack <mail at jakobsack.de>
 + * Copyright (c) 2012 Bart Visscher <bartv at thisnet.nl>
 + * 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.
 + */
 + /**
 + *
 + * The following SQL statement is just a help for developers and will not be
 + * executed!
 + *
 + * CREATE TABLE clndr_objects (
 + *     id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
 + *     calendarid INTEGER UNSIGNED NOT NULL,
 + *     objecttype VARCHAR(40) NOT NULL,
 + *     startdate DATETIME,
 + *     enddate DATETIME,
 + *     repeating INT(1),
 + *     summary VARCHAR(255),
 + *     calendardata TEXT,
 + *     uri VARCHAR(100),
 + *     lastmodified INT(11)
 + * );
 + *
 + */
 +
 +/**
 + * This class manages our calendar objects
 + */
 +class OC_Calendar_Object{
 +	/**
 +	 * @brief Returns all objects of a calendar
 +	 * @param integer $id
 +	 * @return array
 +	 *
 +	 * The objects are associative arrays. You'll find the original vObject in
 +	 * ['calendardata']
 +	 */
 +	public static function all($id) {
 +		$stmt = OCP\DB::prepare( 'SELECT * FROM `*PREFIX*clndr_objects` WHERE `calendarid` = ?' );
 +		$result = $stmt->execute(array($id));
 +
 +		$calendarobjects = array();
 +		while( $row = $result->fetchRow()) {
 +			$calendarobjects[] = $row;
 +		}
 +
 +		return $calendarobjects;
 +	}
 +
 +	/**
 +	 * @brief Returns all objects of a calendar between $start and $end
 +	 * @param integer $id
 +	 * @param DateTime $start
 +	 * @param DateTime $end
 +	 * @return array
 +	 *
 +	 * The objects are associative arrays. You'll find the original vObject
 +	 * in ['calendardata']
 +	 */
 +	public static function allInPeriod($id, $start, $end) {
 +		$stmt = OCP\DB::prepare( 'SELECT * FROM `*PREFIX*clndr_objects` WHERE `calendarid` = ? AND `objecttype`= ?' 
 +		.' AND ((`startdate` >= ? AND `enddate` <= ? AND `repeating` = 0)'
 +		.' OR (`enddate` >= ? AND `startdate` <= ? AND `repeating` = 0)'
 +		.' OR (`startdate` <= ? AND `repeating` = 1) )' );
 +		$start = self::getUTCforMDB($start);
 +		$end = self::getUTCforMDB($end);
 +		$result = $stmt->execute(array($id,'VEVENT',
 +					$start, $end,					
 +					$start, $end,
 +					$end));
 +
 +		$calendarobjects = array();
 +		while( $row = $result->fetchRow()) {
 +			$calendarobjects[] = $row;
 +		}
 +
 +		return $calendarobjects;
 +	}
 +
 +	/**
 +	 * @brief Returns an object
 +	 * @param integer $id
 +	 * @return associative array
 +	 */
 +	public static function find($id) {
 +		$stmt = OCP\DB::prepare( 'SELECT * FROM `*PREFIX*clndr_objects` WHERE `id` = ?' );
 +		$result = $stmt->execute(array($id));
 +
 +		return $result->fetchRow();
 +	}
 +
 +	/**
 +	 * @brief finds an object by its DAV Data
 +	 * @param integer $cid Calendar id
 +	 * @param string $uri the uri ('filename')
 +	 * @return associative array
 +	 */
 +	public static function findWhereDAVDataIs($cid,$uri) {
 +		$stmt = OCP\DB::prepare( 'SELECT * FROM `*PREFIX*clndr_objects` WHERE `calendarid` = ? AND `uri` = ?' );
 +		$result = $stmt->execute(array($cid,$uri));
 +
 +		return $result->fetchRow();
 +	}
 +
 +	/**
 +	 * @brief Adds an object
 +	 * @param integer $id Calendar id
 +	 * @param string $data  object
 +	 * @return insertid
 +	 */
 +	public static function add($id,$data) {
 +		$calendar = OC_Calendar_Calendar::find($id);
 +		if ($calendar['userid'] != OCP\User::getUser()) {
 +			$sharedCalendar = OCP\Share::getItemSharedWithBySource('calendar', $id);
 +			if (!$sharedCalendar || !($sharedCalendar['permissions'] & OCP\PERMISSION_CREATE)) {
 +				throw new Exception(
 +					OC_Calendar_App::$l10n->t(
 +						'You do not have the permissions to add events to this calendar.'
 +					)
 +				);
 +			}
 +		}
 +		$object = OC_VObject::parse($data);
 +		list($type,$startdate,$enddate,$summary,$repeating,$uid) = self::extractData($object);
 +
 +		if(is_null($uid)) {
 +			$object->setUID();
 +			$data = $object->serialize();
 +		}
 +
 +		$uri = 'owncloud-'.md5($data.rand().time()).'.ics';
 +
 +		$stmt = OCP\DB::prepare( 'INSERT INTO `*PREFIX*clndr_objects` (`calendarid`,`objecttype`,`startdate`,`enddate`,`repeating`,`summary`,`calendardata`,`uri`,`lastmodified`) VALUES(?,?,?,?,?,?,?,?,?)' );
 +		$stmt->execute(array($id,$type,$startdate,$enddate,$repeating,$summary,$data,$uri,time()));
 +		$object_id = OCP\DB::insertid('*PREFIX*clndr_objects');
 +
 +		OC_Calendar_App::loadCategoriesFromVCalendar($object_id, $object);
 +
 +		OC_Calendar_Calendar::touchCalendar($id);
 +		OCP\Util::emitHook('OC_Calendar', 'addEvent', $object_id);
 +		return $object_id;
 +	}
 +
 +	/**
 +	 * @brief Adds an object with the data provided by sabredav
 +	 * @param integer $id Calendar id
 +	 * @param string $uri   the uri the card will have
 +	 * @param string $data  object
 +	 * @return insertid
 +	 */
 +	public static function addFromDAVData($id,$uri,$data) {
 +		$shared = false;
 +		$calendar = OC_Calendar_Calendar::find($id);
 +		if ($calendar['userid'] != OCP\User::getUser()) {
 +			$sharedCalendar = OCP\Share::getItemSharedWithBySource('calendar', $id);
 +			if (!$sharedCalendar || !($sharedCalendar['permissions'] & OCP\PERMISSION_CREATE)) {
 +				throw new \Sabre\DAV\Exception\Forbidden(
 +					OC_Calendar_App::$l10n->t(
 +						'You do not have the permissions to add events to this calendar.'
 +					)
 +				);
 +			}
 +			$shared = true;
 +		}
 +		$object = OC_VObject::parse($data);
 +		$vevent = self::getElement($object);
 +
 +		if($shared && isset($vevent->CLASS) && (string)$vevent->CLASS !== 'PUBLIC') {
 +			throw new \Sabre\DAV\Exception\PreconditionFailed(
 +					OC_Calendar_App::$l10n->t(
 +						'You cannot add non-public events to a shared calendar.'
 +					)
 +			);
 +		}
 +
 +		list($type,$startdate,$enddate,$summary,$repeating,$uid) = self::extractData($object);
 +
 +		$stmt = OCP\DB::prepare( 'INSERT INTO `*PREFIX*clndr_objects` (`calendarid`,`objecttype`,`startdate`,`enddate`,`repeating`,`summary`,`calendardata`,`uri`,`lastmodified`) VALUES(?,?,?,?,?,?,?,?,?)' );
 +		$stmt->execute(array($id,$type,$startdate,$enddate,$repeating,$summary,$data,$uri,time()));
 +		$object_id = OCP\DB::insertid('*PREFIX*clndr_objects');
 +
 +		OC_Calendar_Calendar::touchCalendar($id);
 +		OCP\Util::emitHook('OC_Calendar', 'addEvent', $object_id);
 +		return $object_id;
 +	}
 +
 +	/**
 +	 * @brief edits an object
 +	 * @param integer $id id of object
 +	 * @param string $data  object
 +	 * @return boolean
 +	 */
 +	public static function edit($id, $data) {
 +		$oldobject = self::find($id);
 +		$calid = self::getCalendarid($id);
 +		
 +		$calendar = OC_Calendar_Calendar::find($calid);
 +		$oldvobject = OC_VObject::parse($oldobject['calendardata']);
 +		if ($calendar['userid'] != OCP\User::getUser()) {
 +			$sharedCalendar = OCP\Share::getItemSharedWithBySource('calendar', $calid); //calid, not objectid !!!! 1111 one one one eleven
 +			$sharedAccessClassPermissions = OC_Calendar_Object::getAccessClassPermissions($oldvobject);
 +			if (!$sharedCalendar || !($sharedCalendar['permissions'] & OCP\PERMISSION_UPDATE) || !($sharedAccessClassPermissions & OCP\PERMISSION_UPDATE)) {
 +				throw new Exception(
 +					OC_Calendar_App::$l10n->t(
 +						'You do not have the permissions to edit this event.'
 +					)
 +				);
 +			}
 +		}
 +		$object = OC_VObject::parse($data);
 +		OC_Calendar_App::loadCategoriesFromVCalendar($id, $object);
 +		list($type,$startdate,$enddate,$summary,$repeating,$uid) = self::extractData($object);
 +
 +		$stmt = OCP\DB::prepare( 'UPDATE `*PREFIX*clndr_objects` SET `objecttype`=?,`startdate`=?,`enddate`=?,`repeating`=?,`summary`=?,`calendardata`=?,`lastmodified`= ? WHERE `id` = ?' );
 +		$stmt->execute(array($type,$startdate,$enddate,$repeating,$summary,$data,time(),$id));
 +
 +		OC_Calendar_Calendar::touchCalendar($oldobject['calendarid']);
 +		OCP\Util::emitHook('OC_Calendar', 'editEvent', $id);
 +
 +		return true;
 +	}
 +
 +	/**
 +	 * @brief edits an object with the data provided by sabredav
 +	 * @param integer $id calendar id
 +	 * @param string $uri   the uri of the object
 +	 * @param string $data  object
 +	 * @return boolean
 +	 */
 +	public static function editFromDAVData($cid,$uri,$data) {
 +		$oldobject = self::findWhereDAVDataIs($cid,$uri);
 +
 +		$calendar = OC_Calendar_Calendar::find($cid);
 +		$oldvobject = OC_VObject::parse($oldobject['calendardata']);
 +		if ($calendar['userid'] != OCP\User::getUser()) {
 +			$sharedCalendar = OCP\Share::getItemSharedWithBySource('calendar', $cid);
 +			$sharedAccessClassPermissions = OC_Calendar_Object::getAccessClassPermissions($oldvobject);
 +			if (!$sharedCalendar || !($sharedCalendar['permissions'] & OCP\PERMISSION_UPDATE) || !($sharedAccessClassPermissions & OCP\PERMISSION_UPDATE)) {
 +				throw new \Sabre\DAV\Exception\Forbidden(
 +					OC_Calendar_App::$l10n->t(
 +						'You do not have the permissions to edit this event.'
 +					)
 +				);
 +			}
 +		}
 +		$object = OC_VObject::parse($data);
 +		list($type,$startdate,$enddate,$summary,$repeating,$uid) = self::extractData($object);
 +
 +		$stmt = OCP\DB::prepare( 'UPDATE `*PREFIX*clndr_objects` SET `objecttype`=?,`startdate`=?,`enddate`=?,`repeating`=?,`summary`=?,`calendardata`=?,`lastmodified`= ? WHERE `id` = ?' );
 +		$stmt->execute(array($type,$startdate,$enddate,$repeating,$summary,$data,time(),$oldobject['id']));
 +
 +		OC_Calendar_Calendar::touchCalendar($oldobject['calendarid']);
 +		OCP\Util::emitHook('OC_Calendar', 'editEvent', $oldobject['id']);
 +
 +		return true;
 +	}
 +
 +	/**
 +	 * @brief deletes an object
 +	 * @param integer $id id of object
 +	 * @return boolean
 +	 */
 +	public static function delete($id) {
 +		$oldobject = self::find($id);
 +		$calid = self::getCalendarid($id);
 +		
 +		$calendar = OC_Calendar_Calendar::find($calid);
 +		$oldvobject = OC_VObject::parse($oldobject['calendardata']);
 +		if ($calendar['userid'] != OCP\User::getUser()) {
 +			$sharedCalendar = OCP\Share::getItemSharedWithBySource('calendar',  $calid);
 +			$sharedAccessClassPermissions = OC_Calendar_Object::getAccessClassPermissions($oldvobject);
 +			if (!$sharedCalendar || !($sharedCalendar['permissions'] & OCP\PERMISSION_DELETE) || !($sharedAccessClassPermissions & OCP\PERMISSION_DELETE)) {
 +				throw new Exception(
 +					OC_Calendar_App::$l10n->t(
 +						'You do not have the permissions to delete this event.'
 +					)
 +				);
 +			}
 +		}
 +		$stmt = OCP\DB::prepare( 'DELETE FROM `*PREFIX*clndr_objects` WHERE `id` = ?' );
 +		$stmt->execute(array($id));
 +		OC_Calendar_Calendar::touchCalendar($oldobject['calendarid']);
 +
 +		OCP\Share::unshareAll('event', $id);
 +
 +		OCP\Util::emitHook('OC_Calendar', 'deleteEvent', $id);
 +
 +		return true;
 +	}
 +
 +	/**
 +	 * @brief deletes an  object with the data provided by sabredav
 +	 * @param integer $cid calendar id
 +	 * @param string $uri the uri of the object
 +	 * @return boolean
 +	 */
 +	public static function deleteFromDAVData($cid,$uri) {
 +		$oldobject = self::findWhereDAVDataIs($cid, $uri);
 +		$calendar = OC_Calendar_Calendar::find($cid);
 +		if ($calendar['userid'] != OCP\User::getUser()) {
 +			$sharedCalendar = OCP\Share::getItemSharedWithBySource('calendar', $cid);
 +			if (!$sharedCalendar || !($sharedCalendar['permissions'] & OCP\PERMISSION_DELETE)) {
 +				throw new \Sabre\DAV\Exception\Forbidden(
 +					OC_Calendar_App::$l10n->t(
 +						'You do not have the permissions to delete this event.'
 +					)
 +				);
 +			}
 +		}
 +		$stmt = OCP\DB::prepare( 'DELETE FROM `*PREFIX*clndr_objects` WHERE `calendarid`= ? AND `uri`=?' );
 +		$stmt->execute(array($cid,$uri));
 +		OC_Calendar_Calendar::touchCalendar($cid);
 +		OCP\Util::emitHook('OC_Calendar', 'deleteEvent', $oldobject['id']);
 +
 +		return true;
 +	}
 +
 +	public static function moveToCalendar($id, $calendarid) {
 +		$calendar = OC_Calendar_Calendar::find($calendarid);
 +		if ($calendar['userid'] != OCP\User::getUser()) {
 +			$sharedCalendar = OCP\Share::getItemSharedWithBySource('calendar', $calendarid);
 +			if (!$sharedCalendar || !($sharedCalendar['permissions'] & OCP\PERMISSION_DELETE)) {
 +				throw new Exception(
 +					OC_Calendar_App::$l10n->t(
 +						'You do not have the permissions to add events to this calendar.'
 +					)
 +				);
 +			}
 +		}
 +		$stmt = OCP\DB::prepare( 'UPDATE `*PREFIX*clndr_objects` SET `calendarid`=? WHERE `id`=?' );
 +		$stmt->execute(array($calendarid,$id));
 +
 +		OC_Calendar_Calendar::touchCalendar($calendarid);
 +		OCP\Util::emitHook('OC_Calendar', 'moveEvent', $id);
 +
 +		return true;
 +	}
 +
 +	/**
 +     * @brief Creates a UID
 +     * @return string
 +     */
 +    protected static function createUID() {
 +        return substr(md5(rand().time()),0,10);
 +    }
 +
 +	/**
 +	 * @brief Extracts data from a vObject-Object
 +	 * @param Sabre_VObject $object
 +	 * @return array
 +	 *
 +	 * [type, start, end, summary, repeating, uid]
 +	 */
 +	protected static function extractData($object) {
 +		$return = array('',null,null,'',0,null);
 +
 +		// Child to use
 +		$children = 0;
 +		$use = null;
 +		foreach($object->children as $property) {
 +			if($property->name == 'VEVENT') {
 +				$children++;
 +				$thisone = true;
 +
 +				foreach($property->children as &$element) {
 +					if($element->name == 'RECURRENCE-ID') {
 +						$thisone = false;
 +					}
 +				} unset($element);
 +
 +				if($thisone) {
 +					$use = $property;
 +				}
 +			}
 +			elseif($property->name == 'VTODO' || $property->name == 'VJOURNAL') {
 +				$return[0] = $property->name;
 +				foreach($property->children as &$element) {
 +					if($element->name == 'SUMMARY') {
 +						$return[3] = $element->value;
 +					}
 +					elseif($element->name == 'UID') {
 +						$return[5] = $element->value;
 +					}
 +				};
 +
 +				// Only one VTODO or VJOURNAL per object
 +				// (only one UID per object but a UID is required by a VTODO =>
 +				//    one VTODO per object)
 +				break;
 +			}
 +		}
 +
 +		// find the data
 +		if(!is_null($use)) {
 +			$return[0] = $use->name;
 +			foreach($use->children as $property) {
 +				if($property->name == 'DTSTART') {
 +					$return[1] = self::getUTCforMDB($property->getDateTime());
 +				}
 +				elseif($property->name == 'DTEND') {
 +					$return[2] = self::getUTCforMDB($property->getDateTime());
 +				}
 +				elseif($property->name == 'SUMMARY') {
 +					$return[3] = $property->value;
 +				}
 +				elseif($property->name == 'RRULE') {
 +					$return[4] = 1;
 +				}
 +				elseif($property->name == 'UID') {
 +					$return[5] = $property->value;
 +				}
 +			}
 +		}
 +
 +		// More than one child means reoccuring!
 +		if($children > 1) {
 +			$return[4] = 1;
 +		}
 +		return $return;
 +	}
 +
 +	/**
 +	 * @brief DateTime to UTC string
 +	 * @param DateTime $datetime The date to convert
 +	 * @returns date as YYYY-MM-DD hh:mm
 +	 *
 +	 * This function creates a date string that can be used by MDB2.
 +	 * Furthermore it converts the time to UTC.
 +	 */
 +	public static function getUTCforMDB($datetime) {
 +		return date('Y-m-d H:i', $datetime->format('U'));
 +	}
 +
 +	/**
 +	 * @brief returns the DTEND of an $vevent object
 +	 * @param object $vevent vevent object
 +	 * @return object
 +	 */
 +	public static function getDTEndFromVEvent($vevent) {
 +		if ($vevent->DTEND) {
 +			$dtend = $vevent->DTEND;
 +		}else{
 +			$dtend = clone $vevent->DTSTART;
 +			// clone creates a shallow copy, also clone DateTime
 +			$dtend->setDateTime(clone $dtend->getDateTime(), $dtend->getDateType());
 +			if ($vevent->DURATION) {
 +				$duration = strval($vevent->DURATION);
 +				$invert = 0;
 +				if ($duration[0] == '-') {
 +					$duration = substr($duration, 1);
 +					$invert = 1;
 +				}
 +				if ($duration[0] == '+') {
 +					$duration = substr($duration, 1);
 +				}
 +				$interval = new DateInterval($duration);
 +				$interval->invert = $invert;
 +				$dtend->getDateTime()->add($interval);
 +			}
 +		}
 +		return $dtend;
 +	}
 +
 +	/**
 +	 * @brief Remove all properties which should not be exported for the AccessClass Confidential
 +	 * @param string $id Event ID
 +	 * @param Sabre_VObject $vobject Sabre VObject
 +	 * @return object
 +	 */
 +	public static function cleanByAccessClass($id, $vobject) {
 +
 +		// Do not clean your own calendar
 +		if(OC_Calendar_Object::getowner($id) === OCP\USER::getUser()) {
 +			return $vobject;
 +		}
 +
 +		if(isset($vobject->VEVENT)) {
 +			$velement = $vobject->VEVENT;
 +		}
 +		elseif(isset($vobject->VJOURNAL)) {
 +			$velement = $vobject->VJOURNAL;
 +		}
 +		elseif(isset($vobject->VTODO)) {
 +			$velement = $vobject->VTODO;
 +		}
 +
 +		if(isset($velement->CLASS) && $velement->CLASS->value == 'CONFIDENTIAL') {
 +			foreach ($velement->children as &$property) {
 +				switch($property->name) {
 +					case 'CREATED':
 +					case 'DTSTART':
 +					case 'RRULE':
 +					case 'DURATION':
 +					case 'DTEND':
 +					case 'CLASS':
 +					case 'UID':
 +						break;
 +					case 'SUMMARY':
 +						$property->value = OC_Calendar_App::$l10n->t('Busy');
 +						break;
 +					default:
 +						$velement->__unset($property->name);
 +						unset($property);
 +						break;
 +				}
 +			}
 +		}
 +		return $vobject;
 +	}
 +
 +	/**
 +	 * Get the contained element VEVENT, VJOURNAL, VTODO
 +	 *
 +	 * @param Sabre_VObject $vobject
 +	 * @return Sabre_VObject|null
 +	 */
 +	public static function getElement($vobject) {
 +		if(isset($vobject->VEVENT)) {
 +			return $vobject->VEVENT;
 +		}
 +		elseif(isset($vobject->VJOURNAL)) {
 +			return $vobject->VJOURNAL;
 +		}
 +		elseif(isset($vobject->VTODO)) {
 +			return $vobject->VTODO;
 +		}
 +	}
 +
 +	/**
 +	 * @brief Get the permissions determined by the access class of an event/todo/journal
 +	 * @param Sabre_VObject $vobject Sabre VObject
 +	 * @return (int) $permissions - CRUDS permissions
 +	 * @see OCP\Share
 +	 */
 +	public static function getAccessClassPermissions($vobject) {
 +		$velement = self::getElement($vobject);
 +
 +		$accessclass = $velement->getAsString('CLASS');
 +
 +		return OC_Calendar_App::getAccessClassPermissions($accessclass);
 +	}
 +
 +	/**
 +	 * @brief returns the options for the access class of an event
 +	 * @return array - valid inputs for the access class of an event
 +	 */
 +	public static function getAccessClassOptions($l10n) {
 +		return array(
 +			'PUBLIC'       => (string)$l10n->t('Show full event'),
 +			'CONFIDENTIAL' => (string)$l10n->t('Show only busy'),
 +			'PRIVATE'      => (string)$l10n->t('Hide event')
 +		);
 +	}
 +
 +	/**
 +	 * @brief returns the options for the repeat rule of an repeating event
 +	 * @return array - valid inputs for the repeat rule of an repeating event
 +	 */
 +	public static function getRepeatOptions($l10n) {
 +		return array(
 +			'doesnotrepeat' => (string)$l10n->t('Does not repeat'),
 +			'daily'         => (string)$l10n->t('Daily'),
 +			'weekly'        => (string)$l10n->t('Weekly'),
 +			'weekday'       => (string)$l10n->t('Every Weekday'),
 +			'biweekly'      => (string)$l10n->t('Bi-Weekly'),
 +			'monthly'       => (string)$l10n->t('Monthly'),
 +			'yearly'        => (string)$l10n->t('Yearly')
 +		);
 +	}
 +
 +	/**
 +	 * @brief returns the options for the end of an repeating event
 +	 * @return array - valid inputs for the end of an repeating events
 +	 */
 +	public static function getEndOptions($l10n) {
 +		return array(
 +			'never' => (string)$l10n->t('never'),
 +			'count' => (string)$l10n->t('by occurrences'),
 +			'date'  => (string)$l10n->t('by date')
 +		);
 +	}
 +
 +	/**
 +	 * @brief returns the options for an monthly repeating event
 +	 * @return array - valid inputs for monthly repeating events
 +	 */
 +	public static function getMonthOptions($l10n) {
 +		return array(
 +			'monthday' => (string)$l10n->t('by monthday'),
 +			'weekday'  => (string)$l10n->t('by weekday')
 +		);
 +	}
 +
 +	/**
 +	 * @brief returns the options for an weekly repeating event
 +	 * @return array - valid inputs for weekly repeating events
 +	 */
 +	public static function getWeeklyOptions($l10n) {
 +		return array(
 +			'MO' => (string)$l10n->t('Monday'),
 +			'TU' => (string)$l10n->t('Tuesday'),
 +			'WE' => (string)$l10n->t('Wednesday'),
 +			'TH' => (string)$l10n->t('Thursday'),
 +			'FR' => (string)$l10n->t('Friday'),
 +			'SA' => (string)$l10n->t('Saturday'),
 +			'SU' => (string)$l10n->t('Sunday')
 +		);
 +	}
 +
 +	/**
 +	 * @brief returns the options for an monthly repeating event which occurs on specific weeks of the month
 +	 * @return array - valid inputs for monthly repeating events
 +	 */
 +	public static function getWeekofMonth($l10n) {
 +		return array(
 +			'auto' => (string)$l10n->t('events week of month'),
 +			'1' => (string)$l10n->t('first'),
 +			'2' => (string)$l10n->t('second'),
 +			'3' => (string)$l10n->t('third'),
 +			'4' => (string)$l10n->t('fourth'),
 +			'5' => (string)$l10n->t('fifth'),
 +			'-1' => (string)$l10n->t('last')
 +		);
 +	}
 +
 +	/**
 +	 * @brief returns the options for an yearly repeating event which occurs on specific days of the year
 +	 * @return array - valid inputs for yearly repeating events
 +	 */
 +	public static function getByYearDayOptions() {
 +		$return = array();
 +		foreach(range(1,366) as $num) {
 +			$return[(string) $num] = (string) $num;
 +		}
 +		return $return;
 +	}
 +
 +	/**
 +	 * @brief returns the options for an yearly or monthly repeating event which occurs on specific days of the month
 +	 * @return array - valid inputs for yearly or monthly repeating events
 +	 */
 +	public static function getByMonthDayOptions() {
 +		$return = array();
 +		foreach(range(1,31) as $num) {
 +			$return[(string) $num] = (string) $num;
 +		}
 +		return $return;
 +	}
 +
 +	/**
 +	 * @brief returns the options for an yearly repeating event which occurs on specific month of the year
 +	 * @return array - valid inputs for yearly repeating events
 +	 */
 +	public static function getByMonthOptions($l10n) {
 +		return array(
 +			'1'  => (string)$l10n->t('January'),
 +			'2'  => (string)$l10n->t('February'),
 +			'3'  => (string)$l10n->t('March'),
 +			'4'  => (string)$l10n->t('April'),
 +			'5'  => (string)$l10n->t('May'),
 +			'6'  => (string)$l10n->t('June'),
 +			'7'  => (string)$l10n->t('July'),
 +			'8'  => (string)$l10n->t('August'),
 +			'9'  => (string)$l10n->t('September'),
 +			'10' => (string)$l10n->t('October'),
 +			'11' => (string)$l10n->t('November'),
 +			'12' => (string)$l10n->t('December')
 +		);
 +	}
 +
 +	/**
 +	 * @brief returns the options for an yearly repeating event
 +	 * @return array - valid inputs for yearly repeating events
 +	 */
 +	public static function getYearOptions($l10n) {
 +		return array(
 +			'bydate' => (string)$l10n->t('by events date'),
 +			'byyearday' => (string)$l10n->t('by yearday(s)'),
 +			'byweekno'  => (string)$l10n->t('by weeknumber(s)'),
 +			'bydaymonth'  => (string)$l10n->t('by day and month')
 +		);
 +	}
 +
 +	/**
 +	 * @brief returns the options for an yearly repeating event which occurs on specific week numbers of the year
 +	 * @return array - valid inputs for yearly repeating events
 +	 */
 +	public static function getByWeekNoOptions() {
 +		return range(1, 52);
 +	}
 +
 +	/**
 +	 * @brief validates a request
 +	 * @param array $request
 +	 * @return mixed (array / boolean)
 +	 */
 +	public static function validateRequest($request) {
 +		$errnum = 0;
 +		$errarr = array('title'=>'false', 'cal'=>'false', 'from'=>'false', 'fromtime'=>'false', 'to'=>'false', 'totime'=>'false', 'endbeforestart'=>'false');
 +		if($request['title'] == '') {
 +			$errarr['title'] = 'true';
 +			$errnum++;
 +		}
 +
 +		$fromday = substr($request['from'], 0, 2);
 +		$frommonth = substr($request['from'], 3, 2);
 +		$fromyear = substr($request['from'], 6, 4);
 +		if(!checkdate($frommonth, $fromday, $fromyear)) {
 +			$errarr['from'] = 'true';
 +			$errnum++;
 +		}
 +		$allday = isset($request['allday']);
 +		if(!$allday && self::checkTime(urldecode($request['fromtime']))) {
 +			$errarr['fromtime'] = 'true';
 +			$errnum++;
 +		}
 +
 +		$today = substr($request['to'], 0, 2);
 +		$tomonth = substr($request['to'], 3, 2);
 +		$toyear = substr($request['to'], 6, 4);
 +		if(!checkdate($tomonth, $today, $toyear)) {
 +			$errarr['to'] = 'true';
 +			$errnum++;
 +		}
 +		if($request['repeat'] != 'doesnotrepeat') {
 +			if(($request['interval'] !== strval(intval($request['interval']))) || intval($request['interval']) < 1) {
 +				$errarr['interval'] = 'true';
 +				$errnum++;
 +			}
 +			if(array_key_exists('repeat', $request) && !array_key_exists($request['repeat'], self::getRepeatOptions(OC_Calendar_App::$l10n))) {
 +				$errarr['repeat'] = 'true';
 +				$errnum++;
 +			}
 +			if(array_key_exists('advanced_month_select', $request) && !array_key_exists($request['advanced_month_select'], self::getMonthOptions(OC_Calendar_App::$l10n))) {
 +				$errarr['advanced_month_select'] = 'true';
 +				$errnum++;
 +			}
 +			if(array_key_exists('advanced_year_select', $request) && !array_key_exists($request['advanced_year_select'], self::getYearOptions(OC_Calendar_App::$l10n))) {
 +				$errarr['advanced_year_select'] = 'true';
 +				$errnum++;
 +			}
 +			if(array_key_exists('weekofmonthoptions', $request) && !array_key_exists($request['weekofmonthoptions'], self::getWeekofMonth(OC_Calendar_App::$l10n))) {
 +				$errarr['weekofmonthoptions'] = 'true';
 +				$errnum++;
 +			}
 +			if($request['end'] != 'never') {
 +				if(!array_key_exists($request['end'], self::getEndOptions(OC_Calendar_App::$l10n))) {
 +					$errarr['end'] = 'true';
 +					$errnum++;
 +				}
 +				if($request['end'] == 'count' && is_nan($request['byoccurrences'])) {
 +					$errarr['byoccurrences'] = 'true';
 +					$errnum++;
 +				}
 +				if($request['end'] == 'date') {
 +					list($bydate_day, $bydate_month, $bydate_year) = explode('-', $request['bydate']);
 +					if(!checkdate($bydate_month, $bydate_day, $bydate_year)) {
 +						$errarr['bydate'] = 'true';
 +						$errnum++;
 +					}
 +				}
 +			}
 +			if(array_key_exists('weeklyoptions', $request)) {
 +				foreach($request['weeklyoptions'] as $option) {
 +					if(!in_array($option, self::getWeeklyOptions(OC_Calendar_App::$l10n))) {
 +						$errarr['weeklyoptions'] = 'true';
 +						$errnum++;
 +					}
 +				}
 +			}
 +			if(array_key_exists('byyearday', $request)) {
 +				foreach($request['byyearday'] as $option) {
 +					if(!array_key_exists($option, self::getByYearDayOptions())) {
 +						$errarr['byyearday'] = 'true';
 +						$errnum++;
 +					}
 +				}
 +			}
 +			if(array_key_exists('weekofmonthoptions', $request)) {
 +				if(is_nan((double)$request['weekofmonthoptions'])) {
 +					$errarr['weekofmonthoptions'] = 'true';
 +					$errnum++;
 +				}
 +			}
 +			if(array_key_exists('bymonth', $request)) {
 +				foreach($request['bymonth'] as $option) {
 +					if(!in_array($option, self::getByMonthOptions(OC_Calendar_App::$l10n))) {
 +						$errarr['bymonth'] = 'true';
 +						$errnum++;
 +					}
 +				}
 +			}
 +			if(array_key_exists('byweekno', $request)) {
 +				foreach($request['byweekno'] as $option) {
 +					if(!array_key_exists($option, self::getByWeekNoOptions())) {
 +						$errarr['byweekno'] = 'true';
 +						$errnum++;
 +					}
 +				}
 +			}
 +			if(array_key_exists('bymonthday', $request)) {
 +				foreach($request['bymonthday'] as $option) {
 +					if(!array_key_exists($option, self::getByMonthDayOptions())) {
 +						$errarr['bymonthday'] = 'true';
 +						$errnum++;
 +					}
 +				}
 +			}
 +		}
 +		if(!$allday && self::checkTime(urldecode($request['totime']))) {
 +			$errarr['totime'] = 'true';
 +			$errnum++;
 +		}
 +		if($today < $fromday && $frommonth == $tomonth && $fromyear == $toyear) {
 +			$errarr['endbeforestart'] = 'true';
 +			$errnum++;
 +		}
 +		if($today == $fromday && $frommonth > $tomonth && $fromyear == $toyear) {
 +			$errarr['endbeforestart'] = 'true';
 +			$errnum++;
 +		}
 +		if($today == $fromday && $frommonth == $tomonth && $fromyear > $toyear) {
 +			$errarr['endbeforestart'] = 'true';
 +			$errnum++;
 +		}
 +		if(!$allday && $fromday == $today && $frommonth == $tomonth && $fromyear == $toyear) {
 +			list($tohours, $tominutes) = explode(':', $request['totime']);
 +			list($fromhours, $fromminutes) = explode(':', $request['fromtime']);
 +			if($tohours < $fromhours) {
 +				$errarr['endbeforestart'] = 'true';
 +				$errnum++;
 +			}
 +			if($tohours == $fromhours && $tominutes < $fromminutes) {
 +				$errarr['endbeforestart'] = 'true';
 +				$errnum++;
 +			}
 +		}
 +		if ($errnum)
 +		{
 +			return $errarr;
 +		}
 +		return false;
 +	}
 +
 +	/**
 +	 * @brief validates time
 +	 * @param string $time
 +	 * @return boolean
 +	 */
 +	protected static function checkTime($time) {
 +		if(strpos($time, ':') === false ) {
 +			return true;
 +		}
 +		list($hours, $minutes) = explode(':', $time);
 +		return empty($time)
 +			|| $hours < 0 || $hours > 24
 +			|| $minutes < 0 || $minutes > 60;
 +	}
 +
 +	/**
 +	 * @brief creates an VCalendar Object from the request data
 +	 * @param array $request
 +	 * @return object created $vcalendar
 +	 */	public static function createVCalendarFromRequest($request) {
 +		$vcalendar = new OC_VObject('VCALENDAR');
 +		$vcalendar->add('PRODID', 'ownCloud Calendar');
 +		$vcalendar->add('VERSION', '2.0');
 +
 +		$vevent = new OC_VObject('VEVENT');
 +		$vcalendar->add($vevent);
 +
 +		$vevent->setDateTime('CREATED', 'now', Sabre\VObject\Property\DateTime::UTC);
 +
 +		$vevent->setUID();
 +		return self::updateVCalendarFromRequest($request, $vcalendar);
 +	}
 +
 +	/**
 +	 * @brief updates an VCalendar Object from the request data
 +	 * @param array $request
 +	 * @param object $vcalendar
 +	 * @return object updated $vcalendar
 +	 */
 +	public static function updateVCalendarFromRequest($request, $vcalendar) {
- 		$accessclass = $request["accessclass"];
++		$accessclass = isset($request["accessclass"]) ? $request["accessclass"] : null;
 +		$title = $request["title"];
 +		$location = $request["location"];
 +		$categories = $request["categories"];
 +		$allday = isset($request["allday"]);
 +		$from = $request["from"];
 +		$to  = $request["to"];
 +		if (!$allday) {
 +			$fromtime = $request['fromtime'];
 +			$totime = $request['totime'];
 +		}
 +		$vevent = $vcalendar->VEVENT;
 +		$description = $request["description"];
 +		$repeat = $request["repeat"];
 +		if($repeat != 'doesnotrepeat') {
 +			$rrule = '';
 +			$interval = $request['interval'];
 +			$end = $request['end'];
 +			$byoccurrences = $request['byoccurrences'];
 +			switch($repeat) {
 +				case 'daily':
 +					$rrule .= 'FREQ=DAILY';
 +					break;
 +				case 'weekly':
 +					$rrule .= 'FREQ=WEEKLY';
 +					if(array_key_exists('weeklyoptions', $request)) {
 +						$byday = '';
 +						$daystrings = array_flip(self::getWeeklyOptions(OC_Calendar_App::$l10n));
 +						foreach($request['weeklyoptions'] as $days) {
 +							if($byday == '') {
 +								$byday .= $daystrings[$days];
 +							}else{
 +								$byday .= ',' .$daystrings[$days];
 +							}
 +						}
 +						$rrule .= ';BYDAY=' . $byday;
 +					}
 +					break;
 +				case 'weekday':
 +					$rrule .= 'FREQ=WEEKLY';
 +					$rrule .= ';BYDAY=MO,TU,WE,TH,FR';
 +					break;
 +				case 'biweekly':
 +					$rrule .= 'FREQ=WEEKLY';
 +					$interval = $interval * 2;
 +					break;
 +				case 'monthly':
 +					$rrule .= 'FREQ=MONTHLY';
 +					if($request['advanced_month_select'] == 'monthday') {
 +						break;
 +					}elseif($request['advanced_month_select'] == 'weekday') {
 +						if($request['weekofmonthoptions'] == 'auto') {
 +							list($_day, $_month, $_year) = explode('-', $from);
 +							$weekofmonth = floor($_day/7);
 +						}else{
 +							$weekofmonth = $request['weekofmonthoptions'];
 +						}
 +						$days = array_flip(self::getWeeklyOptions(OC_Calendar_App::$l10n));
 +						$byday = '';
 +						foreach($request['weeklyoptions'] as $day) {
 +							if($byday == '') {
 +								$byday .= $weekofmonth . $days[$day];
 +							}else{
 +								$byday .= ',' . $weekofmonth . $days[$day];
 +							}
 +						}
 +						if($byday == '') {
 +							$byday = 'MO,TU,WE,TH,FR,SA,SU';
 +						}
 +						$rrule .= ';BYDAY=' . $byday;
 +					}
 +					break;
 +				case 'yearly':
 +					$rrule .= 'FREQ=YEARLY';
 +					if($request['advanced_year_select'] == 'bydate') {
 +
 +					}elseif($request['advanced_year_select'] == 'byyearday') {
 +						list($_day, $_month, $_year) = explode('-', $from);
 +						$byyearday = date('z', mktime(0,0,0, $_month, $_day, $_year)) + 1;
 +						if(array_key_exists('byyearday', $request)) {
 +							foreach($request['byyearday'] as $yearday) {
 +								$byyearday .= ',' . $yearday;
 +							}
 +						}
 +						$rrule .= ';BYYEARDAY=' . $byyearday;
 +					}elseif($request['advanced_year_select'] == 'byweekno') {
 +						list($_day, $_month, $_year) = explode('-', $from);
 +						$rrule .= ';BYDAY=' . strtoupper(substr(date('l', mktime(0,0,0, $_month, $_day, $_year)), 0, 2));
 +						$byweekno = '';
 +						foreach($request['byweekno'] as $weekno) {
 +							if($byweekno == '') {
 +								$byweekno = $weekno;
 +							}else{
 +								$byweekno .= ',' . $weekno;
 +							}
 +						}
 +						$rrule .= ';BYWEEKNO=' . $byweekno;
 +					}elseif($request['advanced_year_select'] == 'bydaymonth') {
 +						if(array_key_exists('weeklyoptions', $request)) {
 +							$days = array_flip(self::getWeeklyOptions(OC_Calendar_App::$l10n));
 +							$byday = '';
 +							foreach($request['weeklyoptions'] as $day) {
 +								if($byday == '') {
 +								      $byday .= $days[$day];
 +								}else{
 +								      $byday .= ',' . $days[$day];
 +								}
 +							}
 +							$rrule .= ';BYDAY=' . $byday;
 +						}
 +						if(array_key_exists('bymonth', $request)) {
 +							$monthes = array_flip(self::getByMonthOptions(OC_Calendar_App::$l10n));
 +							$bymonth = '';
 +							foreach($request['bymonth'] as $month) {
 +								if($bymonth == '') {
 +								      $bymonth .= $monthes[$month];
 +								}else{
 +								      $bymonth .= ',' . $monthes[$month];
 +								}
 +							}
 +							$rrule .= ';BYMONTH=' . $bymonth;
 +
 +						}
 +						if(array_key_exists('bymonthday', $request)) {
 +							$bymonthday = '';
 +							foreach($request['bymonthday'] as $monthday) {
 +								if($bymonthday == '') {
 +								      $bymonthday .= $monthday;
 +								}else{
 +								      $bymonthday .= ',' . $monthday;
 +								}
 +							}
 +							$rrule .= ';BYMONTHDAY=' . $bymonthday;
 +
 +						}
 +					}
 +					break;
 +				default:
 +					break;
 +			}
 +			if($interval != '') {
 +				$rrule .= ';INTERVAL=' . $interval;
 +			}
 +			if($end == 'count') {
 +				$rrule .= ';COUNT=' . $byoccurrences;
 +			}
 +			if($end == 'date') {
 +				list($bydate_day, $bydate_month, $bydate_year) = explode('-', $request['bydate']);
 +				$rrule .= ';UNTIL=' . $bydate_year . $bydate_month . $bydate_day;
 +			}
 +			$vevent->setString('RRULE', $rrule);
 +			$repeat = "true";
 +		}else{
 +			$repeat = "false";
 +		}
 +
 +
 +		$vevent->setDateTime('LAST-MODIFIED', 'now', Sabre\VObject\Property\DateTime::UTC);
 +		$vevent->setDateTime('DTSTAMP', 'now', Sabre\VObject\Property\DateTime::UTC);
 +		$vevent->setString('SUMMARY', $title);
 +
 +		if($allday) {
 +			$start = new DateTime($from);
 +			$end = new DateTime($to.' +1 day');
 +			$vevent->setDateTime('DTSTART', $start, Sabre\VObject\Property\DateTime::DATE);
 +			$vevent->setDateTime('DTEND', $end, Sabre\VObject\Property\DateTime::DATE);
 +		}else{
 +			$timezone = OC_Calendar_App::getTimezone();
 +			$timezone = new DateTimeZone($timezone);
 +			$start = new DateTime($from.' '.$fromtime, $timezone);
 +			$end = new DateTime($to.' '.$totime, $timezone);
 +			$vevent->setDateTime('DTSTART', $start, Sabre\VObject\Property\DateTime::LOCALTZ);
 +			$vevent->setDateTime('DTEND', $end, Sabre\VObject\Property\DateTime::LOCALTZ);
 +		}
 +		unset($vevent->DURATION);
 +
- 		$vevent->setString('CLASS', $accessclass);
++		if ($accessclass !== null) {
++			$vevent->setString('CLASS', $accessclass);
++		}
 +		$vevent->setString('LOCATION', $location);
 +		$vevent->setString('DESCRIPTION', $description);
 +		$vevent->setString('CATEGORIES', $categories);
 +
 +		/*if($repeat == "true") {
 +			$vevent->RRULE = $repeat;
 +		}*/
 +
 +		return $vcalendar;
 +	}
 +
 +	/**
 +	 * @brief returns the owner of an object
 +	 * @param integer $id
 +	 * @return string
 +	 */
 +	public static function getowner($id) {
 +		if ($id == 0) return null;
 +		$event = self::find($id);
 +		$cal = OC_Calendar_Calendar::find($event['calendarid']);
 +		if($cal === false || is_array($cal) === false){
 +			return null;
 +		}
 +		if(array_key_exists('userid', $cal)){
 +			return $cal['userid'];
 +		}else{
 +			return null;
 +		}
 +	}
 +
 +	/**
 +	 * @brief returns the calendarid of an object
 +	 * @param integer $id
 +	 * @return integer
 +	 */
 +	public static function getCalendarid($id) {
 +		$event = self::find($id);
 +		return $event['calendarid'];
 +	}
 +
 +	/**
 +	 * @brief checks if an object is repeating
 +	 * @param integer $id
 +	 * @return boolean
 +	 */
 +	public static function isrepeating($id) {
 +		$event = self::find($id);
 +		return ($event['repeating'] == 1)?true:false;
 +	}
 +
 +	/**
 +	 * @brief converts the start_dt and end_dt to a new timezone
 +	 * @param object $dtstart
 +	 * @param object $dtend
 +	 * @param boolean $allday
 +	 * @param string $tz
 +	 * @return array
 +	 */
 +	public static function generateStartEndDate($dtstart, $dtend, $allday, $tz) {
 +		$start_dt = $dtstart->getDateTime();
 +		$end_dt = $dtend->getDateTime();
 +		$return = array();
 +		if($allday) {
 +			$return['start'] = $start_dt->format('Y-m-d');
 +			$end_dt->modify('-1 minute');
 +			while($start_dt >= $end_dt) {
 +				$end_dt->modify('+1 day');
 +			}
 +			$return['end'] = $end_dt->format('Y-m-d');
 +		}else{
 +			if($dtstart->getDateType() !== Sabre\VObject\Property\DateTime::LOCAL) {
 +				$start_dt->setTimezone(new DateTimeZone($tz));
 +				$end_dt->setTimezone(new DateTimeZone($tz));
 +			}
 +			$return['start'] = $start_dt->format('Y-m-d H:i:s');
 +			$return['end'] = $end_dt->format('Y-m-d H:i:s');
 +		}
 +		return $return;
 +	}
 +}
diff --cc apps/calendar/templates/part.choosecalendar.rowfields.php
index 215b659,0000000..37b93c8
mode 100644,000000..100644
--- a/apps/calendar/templates/part.choosecalendar.rowfields.php
+++ b/apps/calendar/templates/part.choosecalendar.rowfields.php
@@@ -1,49 -1,0 +1,48 @@@
 +
 +	
 +<label for="active_<?php p($_['calendar']['id']) ?>" class="calendarLabel">
 +	<div class="calendarCheckbox<?php print_unescaped($_['calendar']['active'] ? '' : ' unchecked') ?>" id="checkbox_<?php p($_['calendar']['id']) ?>" style="background-color:<?php print_unescaped(($_['calendar']['calendarcolor']) ? $_['calendar']['calendarcolor'] : 'rgb(58, 135, 173)') ?>"></div>
 +	<?php p($_['calendar']['displayname']) ?>
 +	<?php if ($_['calendar']['userid'] == OCP\USER::getUser()) { ?>
 +		<input type="checkbox" id="active_<?php p($_['calendar']['id']) ?>" class="activeCalendar" data-id="<?php p($_['calendar']['id']) ?>" <?php print_unescaped($_['calendar']['active'] ? ' checked="checked"' : '') ?>>
 +	<?php } ?>
- 
++</label>
 +<span class="utils">
 +	<span class="action">
 +	<?php if ($_['calendar']['permissions'] & OCP\PERMISSION_SHARE) { ?>
 +		<a href="#" class="share icon-share permanent" data-item-type="calendar" data-item="<?php p($_['calendar']['id']); ?>"
 +		   data-possible-permissions="<?php p($_['calendar']['permissions']) ?>"
 +		   title="<?php p($l->t('Share Calendar')) ?>"></a>
 +	<?php } ?>
 +	</span>
 +
 +	<?php
 +	   if ($_['calendar']['userid'] == OCP\USER::getUser()) {
 +		   $caldav = rawurlencode(html_entity_decode($_['calendar']['uri'], ENT_QUOTES, 'UTF-8'));
 +	   } else {
 +		   $caldav = rawurlencode(html_entity_decode($_['calendar']['uri'], ENT_QUOTES, 'UTF-8')) . '_shared_by_' . $_['calendar']['userid'];
 +	   }
 +	?>
 +
 +	<span class="action">
 +	<a href="#" id="chooseCalendar-showCalDAVURL" data-user="<?php p(OCP\USER::getUser()) ?>" data-caldav="<?php p($caldav) ?>" title="<?php p($l->t('CalDav Link')) ?>" class="icon-public permanent"></a>
 +	</span>
 +
 +	<span class="action">
 +	<a href="<?php print_unescaped(OCP\Util::linkTo('calendar', 'export.php') . '?calid=' . $_['calendar']['id']) ?>" title="<?php p($l->t('Download')) ?>" class="icon-download"></a>
 +
 +	</span>
 +
 +	<span class="action">
 +	<?php if ($_['calendar']['permissions'] & OCP\PERMISSION_UPDATE) { ?>
 +		<a href="#" id="chooseCalendar-edit" data-id="<?php p($_['calendar']['id']) ?>" title="<?php p($l->t('Edit')) ?>" class="icon-rename"></a>
 +	<?php } ?>
 +
 +	</span>
 +
 +	<span class="action">
 +	<?php if ($_['calendar']['permissions'] & OCP\PERMISSION_DELETE) { ?>
 +		<a href="#"  id="chooseCalendar-delete" data-id="<?php p($_['calendar']['id']) ?>" title="<?php p($l->t('Delete')) ?>" class="icon-delete"></a>
 +	<?php } ?>
 +	</span>
 +</span>
- </label>
diff --cc apps/documents/ajax/admin.php
index 086ffee,0000000..48b6ea0
mode 100644,000000..100644
--- a/apps/documents/ajax/admin.php
+++ b/apps/documents/ajax/admin.php
@@@ -1,31 -1,0 +1,69 @@@
 +<?php
 +
 +namespace OCA\Documents;
 +
 +\OCP\JSON::callCheck();
 +\OCP\JSON::checkAdminUser();
 +
 +$converter = isset($_POST['converter']) ? $_POST['converter'] : null;
 +$url = isset($_POST['url']) ? $_POST['url'] : null;
 +try {
 +	if (!is_null($converter)){
 +		Config::setConverter($converter);
 +	}
 +	
 +	if (!is_null($url)){
 +		Config::setConverterUrl($url);
 +	}
 +	
 +	if (Config::getConverter()!='local'){
 +		if (!Converter::checkConnection()){
 +			Helper::warnLog('Bad response from Format Filter Server');
- 			\OCP\JSON::error(array('message' => Config::getL10n()->t('Format filter server is down or misconfigured') ));
++			\OCP\JSON::error(array( 'data'=>
++				array('message' => Config::getL10n()->t('Format filter server is down or misconfigured') )
++			));
 +			exit();
 +		}
++	} else {
++		$targetFilter = 'odt:writer8';
++		$targetExtension = 'odt';
++		$input = '0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAOwADAP7/CQAGAAAAAAAAAAAAAAABAAAAKQAAAAAAAAAAEAAAAgAAAAEAAAD+////AAAAAAAAAAD//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// [...]
++YAXoQAAF2EAABghMQCBiQBAAQAQ0ocAAAALgAIEAEAAgAuAAAACQBIAGUAYQBkAGkAbgBnACAAOAAAAAUACAAGJAEABABDShwASAAJEAEAAgBIAAAACQBIAGUAYQBkAGkAbgBnACAAOQAAAB0ACQASZGQAAAADJAFhJAFehAAAXYQAAGCEAAAGJAEABgA1CAFcCAEAAAAAAAAAAAAALgD+H/L/8QAuAAAACQBXAFcAOABOAHUAbQAxAHoAMAAAAAwAT0oBAFFKAQBeSgEALgD+H/L/AQEuAAAACQBXAFcAOABOAHUAbQAyAHoAMAAAAAwAT0oBAFFKAQBeSgEALgD+H/L/EQEuAAAACQBXAFcAOABOAHUAbQAyAHoAMQAAAAwAT0oGAFFKBgBeSgYALgD+H/L/IQEuAAAACQBXAFcAOABOAHUAbQAyAHoAMgAAAAwAT0oHAFFKBwBeSgcAIgD+H/L/MQEiAAAACQBXAFcAO [...]
++gQUkAAAACgBXAFcAOABOAHUAbQAxADMAegAyAAAAAAAkAP4f8v+RBSQAAAAKAFcAVwA4AE4AdQBtADEAMwB6ADMAAAAAACQA/h/y/6EFJAAAAAoAVwBXADgATgB1AG0AMQAzAHoANAAAAAAAJAD+H/L/sQUkAAAACgBXAFcAOABOAHUAbQAxADMAegA1AAAAAAAkAP4f8v/BBSQAAAAKAFcAVwA4AE4AdQBtADEAMwB6ADYAAAAAACQA/h/y/9EFJAAAAAoAVwBXADgATgB1AG0AMQAzAHoANwAAAAAAJAD+H/L/4QUkAAAACgBXAFcAOABOAHUAbQAxADMAegA4AAAAAAAwAP4f8v/xBTAAAAAKAFcAVwA4AE4AdQBtADEANAB6ADAAAAAMAE9KAQBRSgEAXkoBACQA/h/y/wEGJAAAAAoAVwBXADgATgB1AG0AMQA0AHoAMQAAAAAAJAD+H/L/EQYkAAAACgBXAFcAOAB [...]
++8BBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAA0AAAAAAAAAAAgAAAAAAAAAAAAABQAAAA4AAAAiAAAABAAADgAAAAD/////BAA2DgAAAAD/////BABvDgAAAAD/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAADAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAUAAAAFAAAABQAAAAcAAAAACAAAQggAAAUAAAAACAAARAgAAAYAAAAFAAAACwAAAAwAAAAOAAAAEw0U/xWAAAAAAAcAAAAJAAAADQAAABMhFP8VgA8AAPBAAAAAAAAG8CAAAAABCAAAAwAAAAQAAAACAAAAAQAAAAIAAAACAAAAAgAAAEAAHvEQAAAABAAACAEAAAgCAAAI9wAAEAEPAALw+gAAABAACPAIAAAAA [...]
++AlAEQAMAAlAEEAMAAlAEQAMAAlAEIAOAAlAEQAMQAlADgAMQAlAEQAMQAlADgAMwAlAEQAMAAlAEIARAAlAEQAMAAlAEIARQAlAEQAMAAbAF8AXwBSAGUAZgBIAGUAYQBkAGkAbgBnAF8AXwAxADIANABfADgAMwA0ADEAMAA2ADAAMwA4ABsAXwBfAFIAZQBmAEgAZQBhAGQAaQBuAGcAXwBfADEAMgA2AF8AOAAzADQAMQAwADYAMAAzADgAGwBfAF8AUgBlAGYASABlAGEAZABpAG4AZwBfAF8AMQAyADgAXwA4ADMANAAxADAANgAwADMAOAAbAF8AXwBSAGUAZgBIAGUAYQBkAGkAbgBnAF8AXwAxADMAMABfADgAMwA0ADEAMAA2ADAAMwA4ABsAXwBfAFIAZQBmAEgAZQBhAGQAaQBuAGcAXwBfADEAMwAyAF8AOAAzADQAMQAwADYAMAAzADgAGwBfAF8AUgBlA [...]
++w8AAAEAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAQAABehDcCYIQAABXGBQABNwIGAgAAAC4AAQAAAAAAAQMAAAAAAAAAAAAAAAAAAAAAABAAAF6EOAJghAAAFcYFAAE4AgYEAAAALgABAC4AAQAAAAAAAQMFAAAAAAAAAAAAAAAAAAAAABAAAF6ENwJghAAAFcYFAAE3AgYGAAAALgABAC4AAgAuAAEAAAAAAAEDBQcAAAAAAAAAAAAAAAAAAAAQAABehMAGYIR4/RXGBQABwAYGCAAAAC4AAQAuAAIALgADAC4AAQAAAAAAAQMFBwkAAAAAAAAAAAAAAAAAABAAAF6EuAhghOj8FcYFAAG4CAYKAAAALgABAC4AAgAuAAMALgAEAC4AAQAAAAAAAQMFBwkLAAAAAAAAAAAAAAAAABAAAF6EsApghFj8FcYFAAGwCgYMAAAALgABAC4AAgAuAAMALgAEAC4ABQAuAAEAAAAA [...]
++AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8DAAAB4AAABgMwAAGAAAAAAAAAAAAAAA6hkAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeDMAAAgAAAAAAAAAAAAAAIAzAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIA2QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA [...]
++wAAAQACAAAAAAAAAAAAAAAAAAAAAAACAAAAAtXN1ZwuGxCTlwgAKyz5rkQAAAAF1c3VnC4bEJOXCAArLPmuXAAAABgAAAABAAAAAQAAABAAAAACAAAA6f0AABgAAAABAAAAAQAAABAAAAACAAAA6f0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUgBvAG8AdAAgAEUAbgB0AHIAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYABQD//////////wEAAAAGCQIAAAAAAMAAAAAAAABGAAAAAAAAAAAAAAAAAAAAAAAA [...]
++		
++		$infile = \OCP\Files::tmpFile();
++		$outdir = \OCP\Files::tmpFolder();
++		$outfile = $outdir . '/' . basename($infile) . '.' . $targetExtension;
++		$cmd = Helper::findOpenOffice();
++ 
++		$params = ' --headless --convert-to ' . escapeshellarg($targetFilter) . ' --outdir ' 
++			. escapeshellarg($outdir) 
++			. ' --writer '. escapeshellarg($infile) . ' 2>&1';
++		;
++		file_put_contents($infile, $input);
++ 
++		$result = shell_exec($cmd . $params);
++ 		$exists = file_exists($outfile);
++		
++		if (!$exists){
++			Helper::warnLog('Conversion failed. Raw output:' . $result);
++			\OCP\JSON::error(array( 'data'=>
++					array('message' => Config::getL10n()->t('Conversion failed. Check log for details.') )
++			));
++			exit();
++		} else {
++			unlink($outfile);
++		}
 +	}
 +	
- 	\OCP\JSON::success();
++	\OCP\JSON::success(array( 'data'=>array('message'=>Config::getL10n()->t('Saved')) ));
 +} catch (\Exception $e){
- 	\OCP\JSON::error();
++	\OCP\JSON::error(array( 'data'=>
++					array('message' => $e->getMessage() )
++	));
 +}
- exit();
diff --cc apps/documents/ajax/download.php
index 1905ffe,0000000..c88180f
mode 100644,000000..100644
--- a/apps/documents/ajax/download.php
+++ b/apps/documents/ajax/download.php
@@@ -1,23 -1,0 +1,30 @@@
 +<?php
 +
 +/**
 + * ownCloud - Documents App
 + *
 + * @author Victor Dubiniuk
 + * @copyright 2013 Victor Dubiniuk victor.dubiniuk at gmail.com
 + *
 + * This file is licensed under the Affero General Public License version 3 or
 + * later.
 + */
 +
 +namespace OCA\Documents;
 +
 +\OCP\JSON::checkLoggedIn();
 +
 +$path = Helper::getArrayValueByKey($_GET, 'path');
 +if (!empty($path)){
- 	$fullPath = '/files' . $path;
++	if (\OC\Files\Filesystem::getMimeType($path)!==Filter_Office::NATIVE_MIMETYPE){
++		$fileInfo = \OC\Files\Filesystem::getFileInfo($path);
++		$file = new File($fileInfo->getId());
++		$genesis = new Genesis($file);
++		$fullPath = $genesis->getPath();
++	} else {
++		$fullPath = '/files' . $path;
++	}
 +	$download = new Download(\OCP\User::getUser(), $fullPath);
 +	$download->sendResponse();
 +}
 +exit();
diff --cc apps/documents/js/admin.js
index 1cb2540,0000000..324d509
mode 100644,000000..100644
--- a/apps/documents/js/admin.js
+++ b/apps/documents/js/admin.js
@@@ -1,37 -1,0 +1,34 @@@
 +/*global OC, $ */
 +
 +$(document).ready(function(){
 +	
 +	var documentsSettings = {
- 		converter : '',
 +		save : function() {
 +			$('#docs_apply').attr('disabled', true);
 +			var data = 	{
- 				converter : documentsSettings.converter
++				converter : $('[name="docs_converter"]:checked').val()
 +			}; 
 +			
- 			if (documentsSettings.converter !== 'local'){
++			if (data.converter !== 'local'){
 +				data.url = $('#docs_url').val();
 +			}
 +			
++			OC.msg.startAction('#documents-admin-msg', t('documents', 'Saving...'));
 +			$.post(
 +					OC.filePath('documents', 'ajax', 'admin.php'), 
 +					data,
 +					documentsSettings.afterSave
 +			);
 +		},
 +		
 +		afterSave : function(response){
 +			$('#docs_apply').attr('disabled', false);
- 			if (response && response.message) {
- 				OC.Notification.show(response.message);
- 			}
++			OC.msg.finishedAction('#documents-admin-msg', response);
 +		}
 +	};
 +	
 +	$('#docs_converter_external, #docs_converter_local').on('click', function(){
- 		documentsSettings.converter = $(this).val();
- 		$('#docs_extra').toggle(documentsSettings.converter !== 'local');
++		$('#docs_extra').toggle($('[name="docs_converter"]:checked').val() !== 'local');
 +	});
 +	$('#docs_apply').on('click', documentsSettings.save);
 +});
diff --cc apps/documents/lib/file.php
index a880aa0,0000000..0202816
mode 100644,000000..100644
--- a/apps/documents/lib/file.php
+++ b/apps/documents/lib/file.php
@@@ -1,233 -1,0 +1,235 @@@
 +<?php
 +/**
 + * ownCloud - Documents App
 + *
 + * @author Victor Dubiniuk
 + * @copyright 2013 Victor Dubiniuk victor.dubiniuk at gmail.com
 + *
 + * This library is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
 + * License as published by the Free Software Foundation; either 
 + * version 3 of the License, or any later version.
 + * 
 + * This library is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 + * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
 + *  
 + * You should have received a copy of the GNU Affero General Public
 + * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 + * 
 + */
 +
 +
 +namespace OCA\Documents;
 +
 +class File {
 +	protected $fileId;
 +	protected $owner;
 +	protected $path;
 +	protected $sharing;
 +	protected $token ='';
 +	protected $passwordProtected = false;
 +
 +
 +	public function __construct($fileId, $shareOps = null){
 +		if (!$fileId){
 +			throw new \Exception('No valid file has been passed');
 +		}
 +
 +		$this->fileId = $fileId;
 +		
 +		//if you know how to get sharing info by fileId via API, 
 +		//please send me a link to video tutorial :/
 +		if (!is_null($shareOps)){
 +			$this->sharing = $shareOps;
 +		} else {
 +			$this->sharing = $this->getSharingOps();
 +		}
 +	}
 +	
 +	
 +	public static function getByShareToken($token){
 +		$linkItem = \OCP\Share::getShareByToken($token, false);
 +		if (is_array($linkItem) && isset($linkItem['uid_owner'])) {
 +			// seems to be a valid share
 +			$rootLinkItem = \OCP\Share::resolveReShare($linkItem);
 +		} else {
 +			throw new \Exception('This file was probably unshared');
 +		}
 +		
 +		$file = new File($rootLinkItem['file_source'], array($rootLinkItem));
 +		$file->setToken($token);
 +		
 +		if (isset($linkItem['share_with']) && !empty($linkItem['share_with'])){
 +			$file->setPasswordProtected(true);
 +		}
 +		
 +		return $file;
 +	}
 +
 +	public function getToken(){
 +		return $this->token;
 +	}
 +	
 +	public function getFileId(){
 +		return $this->fileId;
 +	}
 +	
 +	public function setOwner($owner){
 +		$this->owner = $owner;
 +	}
 +	
 +	public function setPath($path){
 +		$this->path = $path;
 +	}
 +	
 +	public function setToken($token){
 +		$this->token = $token;
 +	}
 +	
 +	public function isPublicShare(){
 +		return  !empty($this->token);
 +	}
 +	
 +	public function isPasswordProtected(){
 +		return $this->passwordProtected;
 +	}
 +	
 +	public function checkPassword($password){
 +		$shareId  = $this->getShareId();
 +		if (!$this->isPasswordProtected()
 +			|| (\OC::$session->exists('public_link_authenticated')
 +				&& \OC::$session->get('public_link_authenticated') === $shareId)	
 +			){
 +				return true;
 +		}
 +		
 +		// Check Password
 +		$forcePortable = (CRYPT_BLOWFISH != 1);
 +		$hasher = new \PasswordHash(8, $forcePortable);
 +		if ($hasher->CheckPassword($password.\OC_Config::getValue('passwordsalt', ''),
 +									 $this->getPassword())) {
 +			// Save item id in session for future request
 +			\OC::$session->set('public_link_authenticated', $shareId);
 +			return true;
 +		}
 +		return false;
 +	}
 +	
 +	public function setPasswordProtected($value){
 +		$this->passwordProtected = $value;
 +	}
 +	
 +	public function getPermissions(){
 +		if (count($this->sharing)){
 +			if ($this->isPublicShare()){
 +				$permissions = \OCP\PERMISSION_READ | \OCP\PERMISSION_UPDATE;
 +			} else {
 +				$permissions = $this->sharing[0]['permissions'];
 +			}
 +		} else {
 +			list($owner, $path) = $this->getOwnerViewAndPath();
 +			$permissions = 0;
 +			if (\OC\Files\Filesystem::isReadable($path)){
 +				$permissions |= \OCP\PERMISSION_READ;
 +			}
 +			if (\OC\Files\Filesystem::isUpdatable($path)){
 +				$permissions |= \OCP\PERMISSION_UPDATE;
 +			}
 +				
 +		}
 +		return $permissions;
 +	}
 +
 +
 +	/**
 +	 * 
 +	 * @return string owner of the current file item
 +	 * @throws \Exception
 +	 */
 +	public function getOwnerViewAndPath($useDefaultRoot = false){
 +		if ($this->isPublicShare()){
 +			$rootLinkItem = \OCP\Share::resolveReShare($this->sharing[0]);
 +			if (isset($rootLinkItem['uid_owner'])){
 +				$owner = $rootLinkItem['uid_owner'];
++				\OCP\JSON::checkUserExists($rootLinkItem['uid_owner']);
++				\OC_Util::tearDownFS();
++				\OC_Util::setupFS($rootLinkItem['uid_owner']);
 +			} else {
 +				throw new \Exception($this->fileId . ' is a broken share');
 +			}
 +			$view = new View('/' . $owner . '/files');
- 			$path = $rootLinkItem['file_target'];
 +		} else {
 +			$owner = \OCP\User::getUser();
 +			$root = '/' . $owner;
 +			if ($useDefaultRoot){
 +				$root .= '/' . 'files';
 +			}
 +			$view = new View($root);
- 			$path = $view->getPath($this->fileId);
 +		}
 +			
++		$path = $view->getPath($this->fileId);
 +		if (!$path){
 +			throw new \Exception($this->fileId . ' can not be resolved');
 +		}
 +		$this->path = $path;
 +		$this->owner = $owner;
 +		
 +		if (!$view->file_exists($this->path)){
 +			throw new \Exception($this->path . ' doesn\'t exist');
 +		}
 +
 +		return array($view, $this->path);
 +	}
 +	
 +
 +	public function getOwner(){
 +		if (!$this->owner){
 +			$this->getOwnerViewAndPath();
 +		}
 +		return $this->owner;
 +	}
 +	
 +	
 +	protected function getPassword(){
 +		return $this->sharing[0]['share_with'];
 +	}
 +	
 +	protected function getShareId(){
 +		return $this->sharing[0]['id'];
 +	}
 +
 +	protected function getSharingOps(){
 +		
 +		$where  = 'AND `file_source`=?';
 +		$values = array($this->fileId);
 +		
 +		if (\OCP\User::isLoggedIn()){
 +			$where .= ' AND ((`share_type`=' . \OCP\Share::SHARE_TYPE_USER . ' AND `share_with`=?) OR  `share_type`=' . \OCP\Share::SHARE_TYPE_LINK . ')';
 +			$values[] = \OCP\User::getUser();
 +		} else {
 +			$where .= ' AND (`share_type`=' . \OCP\Share::SHARE_TYPE_LINK . ')';
 +		}
 +		
 +		$query = \OC_DB::prepare('SELECT `*PREFIX*share`.`id`, `item_type`, `*PREFIX*share`.`parent`, `uid_owner`, '
 +							.'`share_type`, `share_with`, `file_source`, `path`, `file_target`, '
 +							.'`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, '
 +							.'`name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`' 
 +							.'FROM `*PREFIX*share` INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` WHERE `item_type` = \'file\' ' . $where);
 +		$result = $query->execute($values);
 +		$shares = $result->fetchAll();
 +		
 +		$origins = array();
 +		if (is_array($shares)){
 +			foreach ($shares as $share){
 +				$origin = \OCP\Share::resolveReShare($share);
 +				if (!isset($origin['path']) && isset($origin['file_target'])){
 +					$origin['path'] = 'files/' . $origin['file_target'];
 +				}
 +				$origins[] = $origin;
 +			}
 +		}
 +		return $origins;
 +	}
 +}
diff --cc apps/documents/templates/admin.php
index cbed8ed,0000000..6fd9852
mode 100644,000000..100644
--- a/apps/documents/templates/admin.php
+++ b/apps/documents/templates/admin.php
@@@ -1,28 -1,0 +1,29 @@@
 +<?php $isLocal = $_['converter']=='local' ?>
 +<div class="section" id="documents">
 +	<h2><?php p($l->t('Documents')) ?></h2>
 +	<p><?php p($l->t('MS Word support (requires openOffice/libreOffice)')) ?></p>
 +	<p>
 +		<input type="radio" name="docs_converter" value="local" id="docs_converter_local" 
 +			   <?php print_unescaped($isLocal ? 'checked="checked"' : '') ?> 
 +			   />
 +		<label for="docs_converter_local"><?php p($l->t('Local')) ?></label><br>
 +		<em><?php p($l->t('openOffice/libreOffice is installed on this server. Path to binary is provided via preview_libreoffice_path in config.php')) ?></em>
 +	</p>
 +	<p>
 +		<input type="radio" name="docs_converter" value="external" id="docs_converter_external"
 +			   <?php print_unescaped(!$isLocal ? 'checked="checked"' : '') ?> 
 +			   />
 +		<label for="docs_converter_external"><?php p($l->t('External')) ?></label><br>
 +		<em><?php p($l->t('openOffice/libreOffice is installed on external server running a format filter server')) ?></em>
 +	</p>
 +	<div id="docs_extra" <?php print_unescaped($isLocal ? 'style="display:none"' : '') ?>>
 +	<input type="text" name="docs_url" id="docs_url" 
 +		   value="<?php p($_['converter_url'])?>" 
 +		   original-title="<?php p($l->t('scheme://domain.tld[:port]')) ?>" 
 +		   style="width:250px;"
 +		   />
 +	<br /><em><?php p($l->t('Server URL')) ?></em>
 +	</div>
- 	<br /><button type="button" id="docs_apply"><?php p($l->t('Apply')) ?></button>
++	<br /><button type="button" id="docs_apply"><?php p($l->t('Apply and test')) ?></button>
++	<span id="documents-admin-msg" class="msg"></span>
 +</div>
diff --cc apps/files/js/fileactions.js
index e06d291,fd03876..4aefd61
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@@ -264,14 -291,14 +291,20 @@@
  			if (actions['Delete']) {
  				var img = self.icons['Delete'];
  				var html;
++				var mountType = $tr.attr('data-mounttype');
++				var deleteTitle = t('files', 'Delete');
++				if (mountType === 'external-root') {
++					deleteTitle = t('files', 'Disconnect storage');
++				} else if (mountType === 'shared-root') {
++					deleteTitle = t('files', 'Unshare');
++				} else if (fileList.id === 'trashbin') {
++					deleteTitle = t('files', 'Delete permanently');
++				}
++
  				if (img.call) {
  					img = img(file);
  				}
--				if (typeof trashBinApp !== 'undefined' && trashBinApp) {
--					html = '<a href="#" original-title="' + t('files', 'Delete permanently') + '" class="action delete delete-icon" />';
--				} else {
--					html = '<a href="#" original-title="' + t('files', 'Delete') + '" class="action delete delete-icon" />';
--				}
++				html = '<a href="#" original-title="' + escapeHTML(deleteTitle) + '" class="action delete delete-icon" />';
  				var element = $(html);
  				element.data('action', actions['Delete']);
  				element.on('click', {a: null, elem: parent, actionFunc: actions['Delete'].action}, actionHandler);
diff --cc apps/files/js/filelist.js
index 400e3e2,96436d3..61e73b7
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@@ -598,6 -600,6 +600,10 @@@
  				"data-permissions": fileData.permissions || this.getDirectoryPermissions()
  			});
  
++			if (fileData.mountType) {
++				tr.attr('data-mounttype', fileData.mountType);
++			}
++
  			if (!_.isUndefined(path)) {
  				tr.attr('data-path', path);
  			}
diff --cc apps/files/lib/helper.php
index b84b6c0,b84b6c0..be0992b
--- a/apps/files/lib/helper.php
+++ b/apps/files/lib/helper.php
@@@ -37,6 -37,6 +37,7 @@@ class Helpe
  	public static function determineIcon($file) {
  		if($file['type'] === 'dir') {
  			$icon = \OC_Helper::mimetypeIcon('dir');
++			// TODO: move this part to the client side, using mountType
  			if ($file->isShared()) {
  				$icon = \OC_Helper::mimetypeIcon('dir-shared');
  			} elseif ($file->isMounted()) {
@@@ -125,6 -125,6 +126,18 @@@
  		if (isset($i['is_share_mount_point'])) {
  			$entry['isShareMountPoint'] = $i['is_share_mount_point'];
  		}
++		$mountType = null;
++		if ($i->isShared()) {
++			$mountType = 'shared';
++		} else if ($i->isMounted()) {
++			$mountType = 'external';
++		}
++		if ($mountType !== null) {
++			if ($i->getInternalPath() === '') {
++				$mountType .= '-root';
++			}
++			$entry['mountType'] = $mountType;
++		}
  		return $entry;
  	}
  
diff --cc apps/files_encryption/lib/keymanager.php
index 70820a6,da84e97..da84e97
mode 100644,100755..100644
--- a/apps/files_encryption/lib/keymanager.php
+++ b/apps/files_encryption/lib/keymanager.php
diff --cc apps/files_external/js/mountsfilelist.js
index 70b5b81,0f61f78..20bf0f7
--- a/apps/files_external/js/mountsfilelist.js
+++ b/apps/files_external/js/mountsfilelist.js
@@@ -109,6 -104,6 +104,7 @@@
  		_makeFiles: function(data) {
  			var files = _.map(data, function(fileData) {
  				fileData.icon = OC.imagePath('core', 'filetypes/folder-external');
++				fileData.mountType = 'external';
  				return fileData;
  			});
  
diff --cc apps/files_external/lib/sftp.php
index 52cac9b,52cac9b..fc74d5b
--- a/apps/files_external/lib/sftp.php
+++ b/apps/files_external/lib/sftp.php
@@@ -7,6 -7,6 +7,10 @@@
   */
  namespace OC\Files\Storage;
  
++/**
++* Uses phpseclib's Net_SFTP class and the Net_SFTP_Stream stream wrapper to
++* provide access to SFTP servers.
++*/
  class SFTP extends \OC\Files\Storage\Common {
  	private $host;
  	private $user;
@@@ -18,6 -18,6 +22,17 @@@
  	private static $tempFiles = array();
  
  	public function __construct($params) {
++		// The sftp:// scheme has to be manually registered via inclusion of
++		// the 'Net/SFTP/Stream.php' file which registers the Net_SFTP_Stream
++		// stream wrapper as a side effect.
++		// A slightly better way to register the stream wrapper is available
++		// since phpseclib 0.3.7 in the form of a static call to
++		// Net_SFTP_Stream::register() which will trigger autoloading if
++		// necessary.
++		// TODO: Call Net_SFTP_Stream::register() instead when phpseclib is
++		//       updated to 0.3.7 or higher.
++		require_once 'Net/SFTP/Stream.php';
++
  		$this->host = $params['host'];
  		$proto = strpos($this->host, '://');
  		if ($proto != false) {
diff --cc apps/files_pdfviewer/3rdparty/pdfjs/viewer.js
index 43afeda,0000000..b08ff56
mode 100644,000000..100644
--- a/apps/files_pdfviewer/3rdparty/pdfjs/viewer.js
+++ b/apps/files_pdfviewer/3rdparty/pdfjs/viewer.js
@@@ -1,5655 -1,0 +1,5655 @@@
 +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 +/* Copyright 2012 Mozilla Foundation
 + *
 + * Licensed under the Apache License, Version 2.0 (the "License");
 + * you may not use this file except in compliance with the License.
 + * You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +/* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, PDFFindBar, CustomStyle,
 +           PDFFindController, ProgressBar, TextLayerBuilder, DownloadManager,
 +           getFileName, scrollIntoView, getPDFFileNameFromURL, PDFHistory,
 +           Preferences, ViewHistory, PageView, ThumbnailView, URL,
 +           noContextMenuHandler, SecondaryToolbar, PasswordPrompt,
 +           PresentationMode, HandTool, Promise, DocumentProperties */
 +
 +'use strict';
 +
 +var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
 +var DEFAULT_SCALE = 'auto';
 +var DEFAULT_SCALE_DELTA = 1.1;
 +var UNKNOWN_SCALE = 0;
 +var CACHE_SIZE = 20;
 +var CSS_UNITS = 96.0 / 72.0;
 +var SCROLLBAR_PADDING = 40;
 +var VERTICAL_PADDING = 5;
 +var MAX_AUTO_SCALE = 1.25;
 +var MIN_SCALE = 0.25;
 +var MAX_SCALE = 4.0;
 +var VIEW_HISTORY_MEMORY = 20;
 +var SCALE_SELECT_CONTAINER_PADDING = 8;
 +var SCALE_SELECT_PADDING = 22;
 +var THUMBNAIL_SCROLL_MARGIN = -19;
 +var USE_ONLY_CSS_ZOOM = false;
 +var CLEANUP_TIMEOUT = 30000;
 +var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
 +var RenderingStates = {
 +  INITIAL: 0,
 +  RUNNING: 1,
 +  PAUSED: 2,
 +  FINISHED: 3
 +};
 +var FindStates = {
 +  FIND_FOUND: 0,
 +  FIND_NOTFOUND: 1,
 +  FIND_WRAPPED: 2,
 +  FIND_PENDING: 3
 +};
 +
 +PDFJS.imageResourcesPath = './images/';
 +  PDFJS.workerSrc = '../build/pdf.worker.js';
 +
 +var mozL10n = document.mozL10n || document.webL10n;
 +
 +
 +// optimised CSS custom property getter/setter
 +var CustomStyle = (function CustomStyleClosure() {
 +
 +  // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
 +  //              animate-css-transforms-firefox-webkit.html
 +  // in some versions of IE9 it is critical that ms appear in this list
 +  // before Moz
 +  var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
 +  var _cache = { };
 +
 +  function CustomStyle() {
 +  }
 +
 +  CustomStyle.getProp = function get(propName, element) {
 +    // check cache only when no element is given
 +    if (arguments.length == 1 && typeof _cache[propName] == 'string') {
 +      return _cache[propName];
 +    }
 +
 +    element = element || document.documentElement;
 +    var style = element.style, prefixed, uPropName;
 +
 +    // test standard property first
 +    if (typeof style[propName] == 'string') {
 +      return (_cache[propName] = propName);
 +    }
 +
 +    // capitalize
 +    uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
 +
 +    // test vendor specific properties
 +    for (var i = 0, l = prefixes.length; i < l; i++) {
 +      prefixed = prefixes[i] + uPropName;
 +      if (typeof style[prefixed] == 'string') {
 +        return (_cache[propName] = prefixed);
 +      }
 +    }
 +
 +    //if all fails then set to undefined
 +    return (_cache[propName] = 'undefined');
 +  };
 +
 +  CustomStyle.setProp = function set(propName, element, str) {
 +    var prop = this.getProp(propName);
 +    if (prop != 'undefined')
 +      element.style[prop] = str;
 +  };
 +
 +  return CustomStyle;
 +})();
 +
 +function getFileName(url) {
 +  var anchor = url.indexOf('#');
 +  var query = url.indexOf('?');
 +  var end = Math.min(
 +    anchor > 0 ? anchor : url.length,
 +    query > 0 ? query : url.length);
 +  return url.substring(url.lastIndexOf('/', end) + 1, end);
 +}
 +
 +/**
 + * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
 + * @return {Object} The object with horizontal (sx) and vertical (sy)
 +                    scales. The scaled property is set to false if scaling is
 +                    not required, true otherwise.
 + */
 +function getOutputScale(ctx) {
 +  var devicePixelRatio = window.devicePixelRatio || 1;
 +  var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
 +                          ctx.mozBackingStorePixelRatio ||
 +                          ctx.msBackingStorePixelRatio ||
 +                          ctx.oBackingStorePixelRatio ||
 +                          ctx.backingStorePixelRatio || 1;
 +  var pixelRatio = devicePixelRatio / backingStoreRatio;
 +  return {
 +    sx: pixelRatio,
 +    sy: pixelRatio,
 +    scaled: pixelRatio != 1
 +  };
 +}
 +
 +/**
 + * Scrolls specified element into view of its parent.
 + * element {Object} The element to be visible.
 + * spot {Object} An object with optional top and left properties,
 + *               specifying the offset from the top left edge.
 + */
 +function scrollIntoView(element, spot) {
 +  // Assuming offsetParent is available (it's not available when viewer is in
 +  // hidden iframe or object). We have to scroll: if the offsetParent is not set
 +  // producing the error. See also animationStartedClosure.
 +  var parent = element.offsetParent;
 +  var offsetY = element.offsetTop + element.clientTop;
 +  var offsetX = element.offsetLeft + element.clientLeft;
 +  if (!parent) {
 +    console.error('offsetParent is not set -- cannot scroll');
 +    return;
 +  }
 +  while (parent.clientHeight === parent.scrollHeight) {
 +    if (parent.dataset._scaleY) {
 +      offsetY /= parent.dataset._scaleY;
 +      offsetX /= parent.dataset._scaleX;
 +    }
 +    offsetY += parent.offsetTop;
 +    offsetX += parent.offsetLeft;
 +    parent = parent.offsetParent;
 +    if (!parent) {
 +      return; // no need to scroll
 +    }
 +  }
 +  if (spot) {
 +    if (spot.top !== undefined) {
 +      offsetY += spot.top;
 +    }
 +    if (spot.left !== undefined) {
 +      offsetX += spot.left;
 +      parent.scrollLeft = offsetX;
 +    }
 +  }
 +  parent.scrollTop = offsetY;
 +}
 +
 +/**
 + * Event handler to suppress context menu.
 + */
 +function noContextMenuHandler(e) {
 +  e.preventDefault();
 +}
 +
 +/**
 + * Returns the filename or guessed filename from the url (see issue 3455).
 + * url {String} The original PDF location.
 + * @return {String} Guessed PDF file name.
 + */
 +function getPDFFileNameFromURL(url) {
 +  var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
 +  //            SCHEME      HOST         1.PATH  2.QUERY   3.REF
 +  // Pattern to get last matching NAME.pdf
 +  var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
 +  var splitURI = reURI.exec(url);
 +  var suggestedFilename = reFilename.exec(splitURI[1]) ||
 +                           reFilename.exec(splitURI[2]) ||
 +                           reFilename.exec(splitURI[3]);
 +  if (suggestedFilename) {
 +    suggestedFilename = suggestedFilename[0];
 +    if (suggestedFilename.indexOf('%') != -1) {
 +      // URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
 +      try {
 +        suggestedFilename =
 +          reFilename.exec(decodeURIComponent(suggestedFilename))[0];
 +      } catch(e) { // Possible (extremely rare) errors:
 +        // URIError "Malformed URI", e.g. for "%AA.pdf"
 +        // TypeError "null has no properties", e.g. for "%2F.pdf"
 +      }
 +    }
 +  }
 +  return suggestedFilename || 'document.pdf';
 +}
 +
 +var ProgressBar = (function ProgressBarClosure() {
 +
 +  function clamp(v, min, max) {
 +    return Math.min(Math.max(v, min), max);
 +  }
 +
 +  function ProgressBar(id, opts) {
 +
 +    // Fetch the sub-elements for later.
 +    this.div = document.querySelector(id + ' .progress');
 +
 +    // Get the loading bar element, so it can be resized to fit the viewer.
 +    this.bar = this.div.parentNode;
 +
 +    // Get options, with sensible defaults.
 +    this.height = opts.height || 100;
 +    this.width = opts.width || 100;
 +    this.units = opts.units || '%';
 +
 +    // Initialize heights.
 +    this.div.style.height = this.height + this.units;
 +    this.percent = 0;
 +  }
 +
 +  ProgressBar.prototype = {
 +
 +    updateBar: function ProgressBar_updateBar() {
 +      if (this._indeterminate) {
 +        this.div.classList.add('indeterminate');
 +        this.div.style.width = this.width + this.units;
 +        return;
 +      }
 +
 +      this.div.classList.remove('indeterminate');
 +      var progressSize = this.width * this._percent / 100;
 +      this.div.style.width = progressSize + this.units;
 +    },
 +
 +    get percent() {
 +      return this._percent;
 +    },
 +
 +    set percent(val) {
 +      this._indeterminate = isNaN(val);
 +      this._percent = clamp(val, 0, 100);
 +      this.updateBar();
 +    },
 +
 +    setWidth: function ProgressBar_setWidth(viewer) {
 +      if (viewer) {
 +        var container = viewer.parentNode;
 +        var scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
 +        if (scrollbarWidth > 0) {
 +          this.bar.setAttribute('style', 'width: calc(100% - ' +
 +                                         scrollbarWidth + 'px);');
 +        }
 +      }
 +    },
 +
 +    hide: function ProgressBar_hide() {
 +      this.bar.classList.add('hidden');
 +      this.bar.removeAttribute('style');
 +    }
 +  };
 +
 +  return ProgressBar;
 +})();
 +
 +var Cache = function cacheCache(size) {
 +  var data = [];
 +  this.push = function cachePush(view) {
 +    var i = data.indexOf(view);
 +    if (i >= 0)
 +      data.splice(i);
 +    data.push(view);
 +    if (data.length > size)
 +      data.shift().destroy();
 +  };
 +};
 +
 +var isLocalStorageEnabled = (function isLocalStorageEnabledClosure() {
 +  // Feature test as per http://diveintohtml5.info/storage.html
 +  // The additional localStorage call is to get around a FF quirk, see
 +  // bug #495747 in bugzilla
 +  try {
 +    return ('localStorage' in window && window['localStorage'] !== null &&
 +            localStorage);
 +  } catch (e) {
 +    return false;
 +  }
 +})();
 +
 +
 +
 +var EXPORTED_SYMBOLS = ['DEFAULT_PREFERENCES'];
 +
 +var DEFAULT_PREFERENCES = {
 +  showPreviousViewOnLoad: true,
 +  defaultZoomValue: '',
 +  ifAvailableShowOutlineOnLoad: false
 +};
 +
 +
 +var Preferences = (function PreferencesClosure() {
 +  function Preferences() {
 +    this.prefs = {};
 +    this.isInitializedPromiseResolved = false;
 +    this.initializedPromise = this.readFromStorage().then(function(prefObj) {
 +      this.isInitializedPromiseResolved = true;
 +      if (prefObj) {
 +        this.prefs = prefObj;
 +      }
 +    }.bind(this));
 +  }
 +
 +  Preferences.prototype = {
 +    writeToStorage: function Preferences_writeToStorage(prefObj) {
 +      return;
 +    },
 +
 +    readFromStorage: function Preferences_readFromStorage() {
 +      var readFromStoragePromise = Promise.resolve();
 +      return readFromStoragePromise;
 +    },
 +
 +    reset: function Preferences_reset() {
 +      if (this.isInitializedPromiseResolved) {
 +        this.prefs = {};
 +        this.writeToStorage(this.prefs);
 +      }
 +    },
 +
 +    set: function Preferences_set(name, value) {
 +      if (!this.isInitializedPromiseResolved) {
 +        return;
 +      } else if (DEFAULT_PREFERENCES[name] === undefined) {
 +        console.error('Preferences_set: \'' + name + '\' is undefined.');
 +        return;
 +      } else if (value === undefined) {
 +        console.error('Preferences_set: no value is specified.');
 +        return;
 +      }
 +      var valueType = typeof value;
 +      var defaultType = typeof DEFAULT_PREFERENCES[name];
 +
 +      if (valueType !== defaultType) {
 +        if (valueType === 'number' && defaultType === 'string') {
 +          value = value.toString();
 +        } else {
 +          console.error('Preferences_set: \'' + value + '\' is a \"' +
 +                        valueType + '\", expected a \"' + defaultType + '\".');
 +          return;
 +        }
 +      }
 +      this.prefs[name] = value;
 +      this.writeToStorage(this.prefs);
 +    },
 +
 +    get: function Preferences_get(name) {
 +      var defaultPref = DEFAULT_PREFERENCES[name];
 +
 +      if (defaultPref === undefined) {
 +        console.error('Preferences_get: \'' + name + '\' is undefined.');
 +        return;
 +      } else if (this.isInitializedPromiseResolved) {
 +        var pref = this.prefs[name];
 +
 +        if (pref !== undefined) {
 +          return pref;
 +        }
 +      }
 +      return defaultPref;
 +    }
 +  };
 +
 +  return Preferences;
 +})();
 +
 +
 +Preferences.prototype.writeToStorage = function(prefObj) {
 +  if (isLocalStorageEnabled) {
 +    localStorage.setItem('preferences', JSON.stringify(prefObj));
 +  }
 +};
 +
 +Preferences.prototype.readFromStorage = function() {
 +  var readFromStoragePromise = new Promise(function (resolve) {
 +    if (isLocalStorageEnabled) {
 +      var readPrefs = JSON.parse(localStorage.getItem('preferences'));
 +      resolve(readPrefs);
 +    }
 +  });
 +  return readFromStoragePromise;
 +};
 +
 +
 +(function mozPrintCallbackPolyfillClosure() {
 +  if ('mozPrintCallback' in document.createElement('canvas')) {
 +    return;
 +  }
 +  // Cause positive result on feature-detection:
 +  HTMLCanvasElement.prototype.mozPrintCallback = undefined;
 +
 +  var canvases;   // During print task: non-live NodeList of <canvas> elements
 +  var index;      // Index of <canvas> element that is being processed
 +
 +  var print = window.print;
 +  window.print = function print() {
 +    if (canvases) {
 +      console.warn('Ignored window.print() because of a pending print job.');
 +      return;
 +    }
 +    try {
 +      dispatchEvent('beforeprint');
 +    } finally {
 +      canvases = document.querySelectorAll('canvas');
 +      index = -1;
 +      next();
 +    }
 +  };
 +
 +  function dispatchEvent(eventType) {
 +    var event = document.createEvent('CustomEvent');
 +    event.initCustomEvent(eventType, false, false, 'custom');
 +    window.dispatchEvent(event);
 +  }
 +
 +  function next() {
 +    if (!canvases) {
 +      return; // Print task cancelled by user (state reset in abort())
 +    }
 +
 +    renderProgress();
 +    if (++index < canvases.length) {
 +      var canvas = canvases[index];
 +      if (typeof canvas.mozPrintCallback === 'function') {
 +        canvas.mozPrintCallback({
 +          context: canvas.getContext('2d'),
 +          abort: abort,
 +          done: next
 +        });
 +      } else {
 +        next();
 +      }
 +    } else {
 +      renderProgress();
 +      print.call(window);
 +      setTimeout(abort, 20); // Tidy-up
 +    }
 +  }
 +
 +  function abort() {
 +    if (canvases) {
 +      canvases = null;
 +      renderProgress();
 +      dispatchEvent('afterprint');
 +    }
 +  }
 +
 +  function renderProgress() {
 +    var progressContainer = document.getElementById('mozPrintCallback-shim');
 +    if (canvases) {
 +      var progress = Math.round(100 * index / canvases.length);
 +      var progressBar = progressContainer.querySelector('progress');
 +      var progressPerc = progressContainer.querySelector('.relative-progress');
 +      progressBar.value = progress;
 +      progressPerc.textContent = progress + '%';
 +      progressContainer.removeAttribute('hidden');
 +      progressContainer.onclick = abort;
 +    } else {
 +      progressContainer.setAttribute('hidden', '');
 +    }
 +  }
 +
 +  var hasAttachEvent = !!document.attachEvent;
 +
 +  window.addEventListener('keydown', function(event) {
 +    // Intercept Cmd/Ctrl + P in all browsers.
 +    // Also intercept Cmd/Ctrl + Shift + P in Chrome and Opera
 +    if (event.keyCode === 80/*P*/ && (event.ctrlKey || event.metaKey) &&
 +        !event.altKey && (!event.shiftKey || window.chrome || window.opera)) {
 +      window.print();
 +      if (hasAttachEvent) {
 +        // Only attachEvent can cancel Ctrl + P dialog in IE <=10
 +        // attachEvent is gone in IE11, so the dialog will re-appear in IE11.
 +        return;
 +      }
 +      event.preventDefault();
 +      if (event.stopImmediatePropagation) {
 +        event.stopImmediatePropagation();
 +      } else {
 +        event.stopPropagation();
 +      }
 +      return;
 +    }
 +    if (event.keyCode === 27 && canvases) { // Esc
 +      abort();
 +    }
 +  }, true);
 +  if (hasAttachEvent) {
 +    document.attachEvent('onkeydown', function(event) {
 +      event = event || window.event;
 +      if (event.keyCode === 80/*P*/ && event.ctrlKey) {
 +        event.keyCode = 0;
 +        return false;
 +      }
 +    });
 +  }
 +
 +  if ('onbeforeprint' in window) {
 +    // Do not propagate before/afterprint events when they are not triggered
 +    // from within this polyfill. (FF/IE).
 +    var stopPropagationIfNeeded = function(event) {
 +      if (event.detail !== 'custom' && event.stopImmediatePropagation) {
 +        event.stopImmediatePropagation();
 +      }
 +    };
 +    window.addEventListener('beforeprint', stopPropagationIfNeeded, false);
 +    window.addEventListener('afterprint', stopPropagationIfNeeded, false);
 +  }
 +})();
 +
 +
 +
 +var DownloadManager = (function DownloadManagerClosure() {
 +
 +  function download(blobUrl, filename) {
 +    var a = document.createElement('a');
 +    if (a.click) {
 +      // Use a.click() if available. Otherwise, Chrome might show
 +      // "Unsafe JavaScript attempt to initiate a navigation change
 +      //  for frame with URL" and not open the PDF at all.
 +      // Supported by (not mentioned = untested):
 +      // - Firefox 6 - 19 (4- does not support a.click, 5 ignores a.click)
 +      // - Chrome 19 - 26 (18- does not support a.click)
 +      // - Opera 9 - 12.15
 +      // - Internet Explorer 6 - 10
 +      // - Safari 6 (5.1- does not support a.click)
 +      a.href = blobUrl;
 +      a.target = '_parent';
 +      // Use a.download if available. This increases the likelihood that
 +      // the file is downloaded instead of opened by another PDF plugin.
 +      if ('download' in a) {
 +        a.download = filename;
 +      }
 +      // <a> must be in the document for IE and recent Firefox versions.
 +      // (otherwise .click() is ignored)
 +      (document.body || document.documentElement).appendChild(a);
 +      a.click();
 +      a.parentNode.removeChild(a);
 +    } else {
 +      if (window.top === window &&
 +          blobUrl.split('#')[0] === window.location.href.split('#')[0]) {
 +        // If _parent == self, then opening an identical URL with different
 +        // location hash will only cause a navigation, not a download.
 +        var padCharacter = blobUrl.indexOf('?') === -1 ? '?' : '&';
 +        blobUrl = blobUrl.replace(/#|$/, padCharacter + '$&');
 +      }
 +      window.open(blobUrl, '_parent');
 +    }
 +  }
 +
 +  function DownloadManager() {}
 +
 +  DownloadManager.prototype = {
 +    downloadUrl: function DownloadManager_downloadUrl(url, filename) {
 +      if (!PDFJS.isValidUrl(url, true)) {
 +        return; // restricted/invalid URL
 +      }
 +
 +      download(url + '#pdfjs.action=download', filename);
 +    },
 +
 +    download: function DownloadManager_download(blob, url, filename) {
 +      if (!URL) {
 +        // URL.createObjectURL is not supported
 +        this.downloadUrl(url, filename);
 +        return;
 +      }
 +
 +      if (navigator.msSaveBlob) {
 +        // IE10 / IE11
 +        if (!navigator.msSaveBlob(blob, filename)) {
 +          this.downloadUrl(url, filename);
 +        }
 +        return;
 +      }
 +
 +      var blobUrl = URL.createObjectURL(blob);
 +      download(blobUrl, filename);
 +    }
 +  };
 +
 +  return DownloadManager;
 +})();
 +
 +
 +
 +
 +var cache = new Cache(CACHE_SIZE);
 +var currentPageNumber = 1;
 +
 +
 +/**
 + * View History - This is a utility for saving various view parameters for
 + *                recently opened files.
 + *
 + * The way that the view parameters are stored depends on how PDF.js is built,
 + * for 'node make <flag>' the following cases exist:
 + *  - FIREFOX or MOZCENTRAL - uses about:config.
 + *  - B2G                   - uses asyncStorage.
 + *  - GENERIC or CHROME     - uses localStorage, if it is available.
 + */
 +var ViewHistory = (function ViewHistoryClosure() {
 +  function ViewHistory(fingerprint) {
 +    this.fingerprint = fingerprint;
 +    var initializedPromiseResolve;
 +    this.isInitializedPromiseResolved = false;
 +    this.initializedPromise = new Promise(function (resolve) {
 +      initializedPromiseResolve = resolve;
 +    });
 +
 +    var resolvePromise = (function ViewHistoryResolvePromise(db) {
 +      this.isInitializedPromiseResolved = true;
 +      this.initialize(db || '{}');
 +      initializedPromiseResolve();
 +    }).bind(this);
 +
 +
 +
 +    if (isLocalStorageEnabled) {
 +      resolvePromise(localStorage.getItem('database'));
 +    }
 +  }
 +
 +  ViewHistory.prototype = {
 +    initialize: function ViewHistory_initialize(database) {
 +      database = JSON.parse(database);
 +      if (!('files' in database)) {
 +        database.files = [];
 +      }
 +      if (database.files.length >= VIEW_HISTORY_MEMORY) {
 +        database.files.shift();
 +      }
 +      var index;
 +      for (var i = 0, length = database.files.length; i < length; i++) {
 +        var branch = database.files[i];
 +        if (branch.fingerprint === this.fingerprint) {
 +          index = i;
 +          break;
 +        }
 +      }
 +      if (typeof index !== 'number') {
 +        index = database.files.push({fingerprint: this.fingerprint}) - 1;
 +      }
 +      this.file = database.files[index];
 +      this.database = database;
 +    },
 +
 +    set: function ViewHistory_set(name, val) {
 +      if (!this.isInitializedPromiseResolved) {
 +        return;
 +      }
 +      var file = this.file;
 +      file[name] = val;
 +      var database = JSON.stringify(this.database);
 +
 +
 +
 +      if (isLocalStorageEnabled) {
 +        localStorage.setItem('database', database);
 +      }
 +    },
 +
 +    get: function ViewHistory_get(name, defaultValue) {
 +      if (!this.isInitializedPromiseResolved) {
 +        return defaultValue;
 +      }
 +      return this.file[name] || defaultValue;
 +    }
 +  };
 +
 +  return ViewHistory;
 +})();
 +
 +
 +/* globals PDFFindController, FindStates, mozL10n */
 +
 +/**
 + * Creates a "search bar" given set of DOM elements
 + * that act as controls for searching, or for setting
 + * search preferences in the UI. This object also sets
 + * up the appropriate events for the controls. Actual
 + * searching is done by PDFFindController
 + */
 +var PDFFindBar = {
 +
 +  opened: false,
 +  bar: null,
 +  toggleButton: null,
 +  findField: null,
 +  highlightAll: null,
 +  caseSensitive: null,
 +  findMsg: null,
 +  findStatusIcon: null,
 +  findPreviousButton: null,
 +  findNextButton: null,
 +
 +  initialize: function(options) {
 +    if(typeof PDFFindController === 'undefined' || PDFFindController === null) {
 +        throw 'PDFFindBar cannot be initialized ' +
 +            'without a PDFFindController instance.';
 +    }
 +
 +    this.bar = options.bar;
 +    this.toggleButton = options.toggleButton;
 +    this.findField = options.findField;
 +    this.highlightAll = options.highlightAllCheckbox;
 +    this.caseSensitive = options.caseSensitiveCheckbox;
 +    this.findMsg = options.findMsg;
 +    this.findStatusIcon = options.findStatusIcon;
 +    this.findPreviousButton = options.findPreviousButton;
 +    this.findNextButton = options.findNextButton;
 +
 +    var self = this;
 +    this.toggleButton.addEventListener('click', function() {
 +      self.toggle();
 +    });
 +
 +    this.findField.addEventListener('input', function() {
 +      self.dispatchEvent('');
 +    });
 +
 +    this.bar.addEventListener('keydown', function(evt) {
 +      switch (evt.keyCode) {
 +        case 13: // Enter
 +          if (evt.target === self.findField) {
 +            self.dispatchEvent('again', evt.shiftKey);
 +          }
 +          break;
 +        case 27: // Escape
 +          self.close();
 +          break;
 +      }
 +    });
 +
 +    this.findPreviousButton.addEventListener('click',
 +      function() { self.dispatchEvent('again', true); }
 +    );
 +
 +    this.findNextButton.addEventListener('click', function() {
 +      self.dispatchEvent('again', false);
 +    });
 +
 +    this.highlightAll.addEventListener('click', function() {
 +      self.dispatchEvent('highlightallchange');
 +    });
 +
 +    this.caseSensitive.addEventListener('click', function() {
 +      self.dispatchEvent('casesensitivitychange');
 +    });
 +  },
 +
 +  dispatchEvent: function(aType, aFindPrevious) {
 +    var event = document.createEvent('CustomEvent');
 +    event.initCustomEvent('find' + aType, true, true, {
 +      query: this.findField.value,
 +      caseSensitive: this.caseSensitive.checked,
 +      highlightAll: this.highlightAll.checked,
 +      findPrevious: aFindPrevious
 +    });
 +    return window.dispatchEvent(event);
 +  },
 +
 +  updateUIState: function(state, previous) {
 +    var notFound = false;
 +    var findMsg = '';
 +    var status = '';
 +
 +    switch (state) {
 +      case FindStates.FIND_FOUND:
 +        break;
 +
 +      case FindStates.FIND_PENDING:
 +        status = 'pending';
 +        break;
 +
 +      case FindStates.FIND_NOTFOUND:
 +        findMsg = mozL10n.get('find_not_found', null, 'Phrase not found');
 +        notFound = true;
 +        break;
 +
 +      case FindStates.FIND_WRAPPED:
 +        if (previous) {
 +          findMsg = mozL10n.get('find_reached_top', null,
 +                      'Reached top of document, continued from bottom');
 +        } else {
 +          findMsg = mozL10n.get('find_reached_bottom', null,
 +                                'Reached end of document, continued from top');
 +        }
 +        break;
 +    }
 +
 +    if (notFound) {
 +      this.findField.classList.add('notFound');
 +    } else {
 +      this.findField.classList.remove('notFound');
 +    }
 +
 +    this.findField.setAttribute('data-status', status);
 +    this.findMsg.textContent = findMsg;
 +  },
 +
 +  open: function() {
 +    if (!this.opened) {
 +      this.opened = true;
 +      this.toggleButton.classList.add('toggled');
 +      this.bar.classList.remove('hidden');
 +    }
 +
 +    this.findField.select();
 +    this.findField.focus();
 +  },
 +
 +  close: function() {
 +    if (!this.opened) return;
 +
 +    this.opened = false;
 +    this.toggleButton.classList.remove('toggled');
 +    this.bar.classList.add('hidden');
 +
 +    PDFFindController.active = false;
 +  },
 +
 +  toggle: function() {
 +    if (this.opened) {
 +      this.close();
 +    } else {
 +      this.open();
 +    }
 +  }
 +};
 +
 +
 +
 +/* globals PDFFindBar, PDFJS, FindStates, FirefoxCom, Promise */
 +
 +/**
 + * Provides a "search" or "find" functionality for the PDF.
 + * This object actually performs the search for a given string.
 + */
 +
 +var PDFFindController = {
 +  startedTextExtraction: false,
 +
 +  extractTextPromises: [],
 +
 +  pendingFindMatches: {},
 +
 +  // If active, find results will be highlighted.
 +  active: false,
 +
 +  // Stores the text for each page.
 +  pageContents: [],
 +
 +  pageMatches: [],
 +
 +  // Currently selected match.
 +  selected: {
 +    pageIdx: -1,
 +    matchIdx: -1
 +  },
 +
 +  // Where find algorithm currently is in the document.
 +  offset: {
 +    pageIdx: null,
 +    matchIdx: null
 +  },
 +
 +  resumePageIdx: null,
 +
 +  state: null,
 +
 +  dirtyMatch: false,
 +
 +  findTimeout: null,
 +
 +  pdfPageSource: null,
 +
 +  integratedFind: false,
 +
 +  initialize: function(options) {
 +    if(typeof PDFFindBar === 'undefined' || PDFFindBar === null) {
 +      throw 'PDFFindController cannot be initialized ' +
 +            'without a PDFFindController instance';
 +    }
 +
 +    this.pdfPageSource = options.pdfPageSource;
 +    this.integratedFind = options.integratedFind;
 +
 +    var events = [
 +      'find',
 +      'findagain',
 +      'findhighlightallchange',
 +      'findcasesensitivitychange'
 +    ];
 +
 +    this.firstPagePromise = new Promise(function (resolve) {
 +      this.resolveFirstPage = resolve;
 +    }.bind(this));
 +    this.handleEvent = this.handleEvent.bind(this);
 +
 +    for (var i = 0; i < events.length; i++) {
 +      window.addEventListener(events[i], this.handleEvent);
 +    }
 +  },
 +
 +  reset: function pdfFindControllerReset() {
 +    this.startedTextExtraction = false;
 +    this.extractTextPromises = [];
 +    this.active = false;
 +  },
 +
 +  calcFindMatch: function(pageIndex) {
 +    var pageContent = this.pageContents[pageIndex];
 +    var query = this.state.query;
 +    var caseSensitive = this.state.caseSensitive;
 +    var queryLen = query.length;
 +
 +    if (queryLen === 0) {
 +      // Do nothing the matches should be wiped out already.
 +      return;
 +    }
 +
 +    if (!caseSensitive) {
 +      pageContent = pageContent.toLowerCase();
 +      query = query.toLowerCase();
 +    }
 +
 +    var matches = [];
 +
 +    var matchIdx = -queryLen;
 +    while (true) {
 +      matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
 +      if (matchIdx === -1) {
 +        break;
 +      }
 +
 +      matches.push(matchIdx);
 +    }
 +    this.pageMatches[pageIndex] = matches;
 +    this.updatePage(pageIndex);
 +    if (this.resumePageIdx === pageIndex) {
 +      this.resumePageIdx = null;
 +      this.nextPageMatch();
 +    }
 +  },
 +
 +  extractText: function() {
 +    if (this.startedTextExtraction) {
 +      return;
 +    }
 +    this.startedTextExtraction = true;
 +
 +    this.pageContents = [];
 +    var extractTextPromisesResolves = [];
 +    for (var i = 0, ii = this.pdfPageSource.pdfDocument.numPages; i < ii; i++) {
 +      this.extractTextPromises.push(new Promise(function (resolve) {
 +        extractTextPromisesResolves.push(resolve);
 +      }));
 +    }
 +
 +    var self = this;
 +    function extractPageText(pageIndex) {
 +      self.pdfPageSource.pages[pageIndex].getTextContent().then(
 +        function textContentResolved(bidiTexts) {
 +          var str = '';
 +
 +          for (var i = 0; i < bidiTexts.length; i++) {
 +            str += bidiTexts[i].str;
 +          }
 +
 +          // Store the pageContent as a string.
 +          self.pageContents.push(str);
 +
 +          extractTextPromisesResolves[pageIndex](pageIndex);
 +          if ((pageIndex + 1) < self.pdfPageSource.pages.length)
 +            extractPageText(pageIndex + 1);
 +        }
 +      );
 +    }
 +    extractPageText(0);
 +  },
 +
 +  handleEvent: function(e) {
 +    if (this.state === null || e.type !== 'findagain') {
 +      this.dirtyMatch = true;
 +    }
 +    this.state = e.detail;
 +    this.updateUIState(FindStates.FIND_PENDING);
 +
 +    this.firstPagePromise.then(function() {
 +      this.extractText();
 +
 +      clearTimeout(this.findTimeout);
 +      if (e.type === 'find') {
 +        // Only trigger the find action after 250ms of silence.
 +        this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
 +      } else {
 +        this.nextMatch();
 +      }
 +    }.bind(this));
 +  },
 +
 +  updatePage: function(idx) {
 +    var page = this.pdfPageSource.pages[idx];
 +
 +    if (this.selected.pageIdx === idx) {
 +      // If the page is selected, scroll the page into view, which triggers
 +      // rendering the page, which adds the textLayer. Once the textLayer is
 +      // build, it will scroll onto the selected match.
 +      page.scrollIntoView();
 +    }
 +
 +    if (page.textLayer) {
 +      page.textLayer.updateMatches();
 +    }
 +  },
 +
 +  nextMatch: function() {
 +    var previous = this.state.findPrevious;
 +    var currentPageIndex = this.pdfPageSource.page - 1;
 +    var numPages = this.pdfPageSource.pages.length;
 +
 +    this.active = true;
 +
 +    if (this.dirtyMatch) {
 +      // Need to recalculate the matches, reset everything.
 +      this.dirtyMatch = false;
 +      this.selected.pageIdx = this.selected.matchIdx = -1;
 +      this.offset.pageIdx = currentPageIndex;
 +      this.offset.matchIdx = null;
 +      this.hadMatch = false;
 +      this.resumePageIdx = null;
 +      this.pageMatches = [];
 +      var self = this;
 +
 +      for (var i = 0; i < numPages; i++) {
 +        // Wipe out any previous highlighted matches.
 +        this.updatePage(i);
 +
 +        // As soon as the text is extracted start finding the matches.
 +        if (!(i in this.pendingFindMatches)) {
 +          this.pendingFindMatches[i] = true;
 +          this.extractTextPromises[i].then(function(pageIdx) {
 +            delete self.pendingFindMatches[pageIdx];
 +            self.calcFindMatch(pageIdx);
 +          });
 +        }
 +      }
 +    }
 +
 +    // If there's no query there's no point in searching.
 +    if (this.state.query === '') {
 +      this.updateUIState(FindStates.FIND_FOUND);
 +      return;
 +    }
 +
 +    // If we're waiting on a page, we return since we can't do anything else.
 +    if (this.resumePageIdx) {
 +      return;
 +    }
 +
 +    var offset = this.offset;
 +    // If there's already a matchIdx that means we are iterating through a
 +    // page's matches.
 +    if (offset.matchIdx !== null) {
 +      var numPageMatches = this.pageMatches[offset.pageIdx].length;
 +      if ((!previous && offset.matchIdx + 1 < numPageMatches) ||
 +          (previous && offset.matchIdx > 0)) {
 +        // The simple case, we just have advance the matchIdx to select the next
 +        // match on the page.
 +        this.hadMatch = true;
 +        offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1;
 +        this.updateMatch(true);
 +        return;
 +      }
 +      // We went beyond the current page's matches, so we advance to the next
 +      // page.
 +      this.advanceOffsetPage(previous);
 +    }
 +    // Start searching through the page.
 +    this.nextPageMatch();
 +  },
 +
 +  matchesReady: function(matches) {
 +    var offset = this.offset;
 +    var numMatches = matches.length;
 +    var previous = this.state.findPrevious;
 +    if (numMatches) {
 +      // There were matches for the page, so initialize the matchIdx.
 +      this.hadMatch = true;
 +      offset.matchIdx = previous ? numMatches - 1 : 0;
 +      this.updateMatch(true);
 +      // matches were found
 +      return true;
 +    } else {
 +      // No matches attempt to search the next page.
 +      this.advanceOffsetPage(previous);
 +      if (offset.wrapped) {
 +        offset.matchIdx = null;
 +        if (!this.hadMatch) {
 +          // No point in wrapping there were no matches.
 +          this.updateMatch(false);
 +          // while matches were not found, searching for a page 
 +          // with matches should nevertheless halt.
 +          return true;
 +        }
 +      }
 +      // matches were not found (and searching is not done)
 +      return false;
 +    }
 +  },
 +
 +  nextPageMatch: function() {
 +    if (this.resumePageIdx !== null) {
 +      console.error('There can only be one pending page.');
 +    }
 +    do {
 +      var pageIdx = this.offset.pageIdx;
 +      var matches = this.pageMatches[pageIdx];
 +      if (!matches) {
 +        // The matches don't exist yet for processing by "matchesReady",
 +        // so set a resume point for when they do exist.
 +        this.resumePageIdx = pageIdx;
 +        break;
 +      }
 +    } while (!this.matchesReady(matches));
 +  },
 +
 +  advanceOffsetPage: function(previous) {
 +    var offset = this.offset;
 +    var numPages = this.extractTextPromises.length;
 +    offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
 +    offset.matchIdx = null;
 +    if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
 +      offset.pageIdx = previous ? numPages - 1 : 0;
 +      offset.wrapped = true;
 +      return;
 +    }
 +  },
 +
 +  updateMatch: function(found) {
 +    var state = FindStates.FIND_NOTFOUND;
 +    var wrapped = this.offset.wrapped;
 +    this.offset.wrapped = false;
 +    if (found) {
 +      var previousPage = this.selected.pageIdx;
 +      this.selected.pageIdx = this.offset.pageIdx;
 +      this.selected.matchIdx = this.offset.matchIdx;
 +      state = wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND;
 +      // Update the currently selected page to wipe out any selected matches.
 +      if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
 +        this.updatePage(previousPage);
 +      }
 +    }
 +    this.updateUIState(state, this.state.findPrevious);
 +    if (this.selected.pageIdx !== -1) {
 +      this.updatePage(this.selected.pageIdx, true);
 +    }
 +  },
 +
 +  updateUIState: function(state, previous) {
 +    if (this.integratedFind) {
 +      FirefoxCom.request('updateFindControlState',
 +                         {result: state, findPrevious: previous});
 +      return;
 +    }
 +    PDFFindBar.updateUIState(state, previous);
 +  }
 +};
 +
 +
 +
 +var PDFHistory = {
 +  initialized: false,
 +  initialDestination: null,
 +
 +  initialize: function pdfHistoryInitialize(fingerprint) {
 +    if (PDFJS.disableHistory || PDFView.isViewerEmbedded) {
 +      // The browsing history is only enabled when the viewer is standalone,
 +      // i.e. not when it is embedded in a web page.
 +      return;
 +    }
 +    this.initialized = true;
 +    this.reInitialized = false;
 +    this.allowHashChange = true;
 +    this.historyUnlocked = true;
 +
 +    this.previousHash = window.location.hash.substring(1);
 +    this.currentBookmark = '';
 +    this.currentPage = 0;
 +    this.updatePreviousBookmark = false;
 +    this.previousBookmark = '';
 +    this.previousPage = 0;
 +    this.nextHashParam = '';
 +
 +    this.fingerprint = fingerprint;
 +    this.currentUid = this.uid = 0;
 +    this.current = {};
 +
 +    var state = window.history.state;
 +    if (this._isStateObjectDefined(state)) {
 +      // This corresponds to navigating back to the document
 +      // from another page in the browser history.
 +      if (state.target.dest) {
 +        this.initialDestination = state.target.dest;
 +      } else {
 +        PDFView.initialBookmark = state.target.hash;
 +      }
 +      this.currentUid = state.uid;
 +      this.uid = state.uid + 1;
 +      this.current = state.target;
 +    } else {
 +      // This corresponds to the loading of a new document.
 +      if (state && state.fingerprint &&
 +          this.fingerprint !== state.fingerprint) {
 +        // Reinitialize the browsing history when a new document
 +        // is opened in the web viewer.
 +        this.reInitialized = true;
 +      }
 +      this._pushOrReplaceState({ fingerprint: this.fingerprint }, true);
 +    }
 +
 +    var self = this;
 +    window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
 +      evt.preventDefault();
 +      evt.stopPropagation();
 +
 +      if (!self.historyUnlocked) {
 +        return;
 +      }
 +      if (evt.state) {
 +        // Move back/forward in the history.
 +        self._goTo(evt.state);
 +      } else {
 +        // Handle the user modifying the hash of a loaded document.
 +        self.previousHash = window.location.hash.substring(1);
 +
 +        // If the history is empty when the hash changes,
 +        // update the previous entry in the browser history.
 +        if (self.uid === 0) {
 +          var previousParams = (self.previousHash && self.currentBookmark &&
 +                                self.previousHash !== self.currentBookmark) ?
 +            { hash: self.currentBookmark, page: self.currentPage } :
 +            { page: 1 };
 +          self.historyUnlocked = false;
 +          self.allowHashChange = false;
 +          window.history.back();
 +          self._pushToHistory(previousParams, false, true);
 +          window.history.forward();
 +          self.historyUnlocked = true;
 +        }
 +        self._pushToHistory({ hash: self.previousHash }, false, true);
 +        self._updatePreviousBookmark();
 +      }
 +    }, false);
 +
 +    function pdfHistoryBeforeUnload() {
 +      var previousParams = self._getPreviousParams(null, true);
 +      if (previousParams) {
 +        var replacePrevious = (!self.current.dest &&
 +                               self.current.hash !== self.previousHash);
 +        self._pushToHistory(previousParams, false, replacePrevious);
 +        self._updatePreviousBookmark();
 +      }
 +      // Remove the event listener when navigating away from the document,
 +      // since 'beforeunload' prevents Firefox from caching the document.
 +      window.removeEventListener('beforeunload', pdfHistoryBeforeUnload, false);
 +    }
 +    window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
 +
 +    window.addEventListener('pageshow', function pdfHistoryPageShow(evt) {
 +      // If the entire viewer (including the PDF file) is cached in the browser,
 +      // we need to reattach the 'beforeunload' event listener since
 +      // the 'DOMContentLoaded' event is not fired on 'pageshow'.
 +      window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
 +    }, false);
 +  },
 +
 +  _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
 +    return (state && state.uid >= 0 &&
 +            state.fingerprint && this.fingerprint === state.fingerprint &&
 +            state.target && state.target.hash) ? true : false;
 +  },
 +
 +  _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj,
 +                                                              replace) {
 +    if (replace) {
 +      window.history.replaceState(stateObj, '', document.URL);
 +    } else {
 +      window.history.pushState(stateObj, '', document.URL);
 +    }
 +  },
 +
 +  get isHashChangeUnlocked() {
 +    if (!this.initialized) {
 +      return true;
 +    }
 +    // If the current hash changes when moving back/forward in the history,
 +    // this will trigger a 'popstate' event *as well* as a 'hashchange' event.
 +    // Since the hash generally won't correspond to the exact the position
 +    // stored in the history's state object, triggering the 'hashchange' event
 +    // can thus corrupt the browser history.
 +    //
 +    // When the hash changes during a 'popstate' event, we *only* prevent the
 +    // first 'hashchange' event and immediately reset allowHashChange.
 +    // If it is not reset, the user would not be able to change the hash.
 +
 +    var temp = this.allowHashChange;
 +    this.allowHashChange = true;
 +    return temp;
 +  },
 +
 +  _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
 +    if (this.updatePreviousBookmark &&
 +        this.currentBookmark && this.currentPage) {
 +      this.previousBookmark = this.currentBookmark;
 +      this.previousPage = this.currentPage;
 +      this.updatePreviousBookmark = false;
 +    }
 +  },
 +
 +  updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark,
 +                                                                  pageNum) {
 +    if (this.initialized) {
 +      this.currentBookmark = bookmark.substring(1);
 +      this.currentPage = pageNum | 0;
 +      this._updatePreviousBookmark();
 +    }
 +  },
 +
 +  updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
 +    if (this.initialized) {
 +      this.nextHashParam = param;
 +    }
 +  },
 +
 +  push: function pdfHistoryPush(params, isInitialBookmark) {
 +    if (!(this.initialized && this.historyUnlocked)) {
 +      return;
 +    }
 +    if (params.dest && !params.hash) {
 +      params.hash = (this.current.hash && this.current.dest &&
 +                     this.current.dest === params.dest) ?
 +        this.current.hash :
 +        PDFView.getDestinationHash(params.dest).split('#')[1];
 +    }
 +    if (params.page) {
 +      params.page |= 0;
 +    }
 +    if (isInitialBookmark) {
 +      var target = window.history.state.target;
 +      if (!target) {
 +        // Invoked when the user specifies an initial bookmark,
 +        // thus setting PDFView.initialBookmark, when the document is loaded.
 +        this._pushToHistory(params, false);
 +        this.previousHash = window.location.hash.substring(1);
 +      }
 +      this.updatePreviousBookmark = this.nextHashParam ? false : true;
 +      if (target) {
 +        // If the current document is reloaded,
 +        // avoid creating duplicate entries in the history.
 +        this._updatePreviousBookmark();
 +      }
 +      return;
 +    }
 +    if (this.nextHashParam) {
 +      if (this.nextHashParam === params.hash) {
 +        this.nextHashParam = null;
 +        this.updatePreviousBookmark = true;
 +        return;
 +      } else {
 +        this.nextHashParam = null;
 +      }
 +    }
 +
 +    if (params.hash) {
 +      if (this.current.hash) {
 +        if (this.current.hash !== params.hash) {
 +          this._pushToHistory(params, true);
 +        } else {
 +          if (!this.current.page && params.page) {
 +            this._pushToHistory(params, false, true);
 +          }
 +          this.updatePreviousBookmark = true;
 +        }
 +      } else {
 +        this._pushToHistory(params, true);
 +      }
 +    } else if (this.current.page && params.page &&
 +               this.current.page !== params.page) {
 +      this._pushToHistory(params, true);
 +    }
 +  },
 +
 +  _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage,
 +                                                            beforeUnload) {
 +    if (!(this.currentBookmark && this.currentPage)) {
 +      return null;
 +    } else if (this.updatePreviousBookmark) {
 +      this.updatePreviousBookmark = false;
 +    }
 +    if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) {
 +      // Prevent the history from getting stuck in the current state,
 +      // effectively preventing the user from going back/forward in the history.
 +      //
 +      // This happens if the current position in the document didn't change when
 +      // the history was previously updated. The reasons for this are either:
 +      // 1. The current zoom value is such that the document does not need to,
 +      //    or cannot, be scrolled to display the destination.
 +      // 2. The previous destination is broken, and doesn't actally point to a
 +      //    position within the document.
 +      //    (This is either due to a bad PDF generator, or the user making a
 +      //     mistake when entering a destination in the hash parameters.)
 +      return null;
 +    }
 +    if ((!this.current.dest && !onlyCheckPage) || beforeUnload) {
 +      if (this.previousBookmark === this.currentBookmark) {
 +        return null;
 +      }
 +    } else if (this.current.page || onlyCheckPage) {
 +      if (this.previousPage === this.currentPage) {
 +        return null;
 +      }
 +    } else {
 +      return null;
 +    }
 +    var params = { hash: this.currentBookmark, page: this.currentPage };
 +    if (PresentationMode.active) {
 +      params.hash = null;
 +    }
 +    return params;
 +  },
 +
 +  _stateObj: function pdfHistory_stateObj(params) {
 +    return { fingerprint: this.fingerprint, uid: this.uid, target: params };
 +  },
 +
 +  _pushToHistory: function pdfHistory_pushToHistory(params,
 +                                                    addPrevious, overwrite) {
 +    if (!this.initialized) {
 +      return;
 +    }
 +    if (!params.hash && params.page) {
 +      params.hash = ('page=' + params.page);
 +    }
 +    if (addPrevious && !overwrite) {
 +      var previousParams = this._getPreviousParams();
 +      if (previousParams) {
 +        var replacePrevious = (!this.current.dest &&
 +                               this.current.hash !== this.previousHash);
 +        this._pushToHistory(previousParams, false, replacePrevious);
 +      }
 +    }
 +    this._pushOrReplaceState(this._stateObj(params),
 +                             (overwrite || this.uid === 0));
 +    this.currentUid = this.uid++;
 +    this.current = params;
 +    this.updatePreviousBookmark = true;
 +  },
 +
 +  _goTo: function pdfHistory_goTo(state) {
 +    if (!(this.initialized && this.historyUnlocked &&
 +          this._isStateObjectDefined(state))) {
 +      return;
 +    }
 +    if (!this.reInitialized && state.uid < this.currentUid) {
 +      var previousParams = this._getPreviousParams(true);
 +      if (previousParams) {
 +        this._pushToHistory(this.current, false);
 +        this._pushToHistory(previousParams, false);
 +        this.currentUid = state.uid;
 +        window.history.back();
 +        return;
 +      }
 +    }
 +    this.historyUnlocked = false;
 +
 +    if (state.target.dest) {
 +      PDFView.navigateTo(state.target.dest);
 +    } else {
 +      PDFView.setHash(state.target.hash);
 +    }
 +    this.currentUid = state.uid;
 +    if (state.uid > this.uid) {
 +      this.uid = state.uid;
 +    }
 +    this.current = state.target;
 +    this.updatePreviousBookmark = true;
 +
 +    var currentHash = window.location.hash.substring(1);
 +    if (this.previousHash !== currentHash) {
 +      this.allowHashChange = false;
 +    }
 +    this.previousHash = currentHash;
 +
 +    this.historyUnlocked = true;
 +  },
 +
 +  back: function pdfHistoryBack() {
 +    this.go(-1);
 +  },
 +
 +  forward: function pdfHistoryForward() {
 +    this.go(1);
 +  },
 +
 +  go: function pdfHistoryGo(direction) {
 +    if (this.initialized && this.historyUnlocked) {
 +      var state = window.history.state;
 +      if (direction === -1 && state && state.uid > 0) {
 +        window.history.back();
 +      } else if (direction === 1 && state && state.uid < (this.uid - 1)) {
 +        window.history.forward();
 +      }
 +    }
 +  }
 +};
 +
 +
 +var SecondaryToolbar = {
 +  opened: false,
 +  previousContainerHeight: null,
 +  newContainerHeight: null,
 +
 +  initialize: function secondaryToolbarInitialize(options) {
 +    this.toolbar = options.toolbar;
 +    this.presentationMode = options.presentationMode;
 +    this.documentProperties = options.documentProperties;
 +    this.buttonContainer = this.toolbar.firstElementChild;
 +
 +    // Define the toolbar buttons.
 +    this.toggleButton = options.toggleButton;
 +    this.presentationModeButton = options.presentationModeButton;
 +    this.openFile = options.openFile;
 +    this.print = options.print;
 +    this.download = options.download;
 +    this.viewBookmark = options.viewBookmark;
 +    this.firstPage = options.firstPage;
 +    this.lastPage = options.lastPage;
 +    this.pageRotateCw = options.pageRotateCw;
 +    this.pageRotateCcw = options.pageRotateCcw;
 +    this.documentPropertiesButton = options.documentPropertiesButton;
 +
 +    // Attach the event listeners.
 +    var elements = [
 +      // Button to toggle the visibility of the secondary toolbar:
 +      { element: this.toggleButton, handler: this.toggle },
 +      // All items within the secondary toolbar
 +      // (except for toggleHandTool, hand_tool.js is responsible for it):
 +      { element: this.presentationModeButton,
 +        handler: this.presentationModeClick },
 +      { element: this.openFile, handler: this.openFileClick },
 +      { element: this.print, handler: this.printClick },
 +      { element: this.download, handler: this.downloadClick },
 +      { element: this.viewBookmark, handler: this.viewBookmarkClick },
 +      { element: this.firstPage, handler: this.firstPageClick },
 +      { element: this.lastPage, handler: this.lastPageClick },
 +      { element: this.pageRotateCw, handler: this.pageRotateCwClick },
 +      { element: this.pageRotateCcw, handler: this.pageRotateCcwClick },
 +      { element: this.documentPropertiesButton,
 +        handler: this.documentPropertiesClick }
 +    ];
 +
 +    for (var item in elements) {
 +      var element = elements[item].element;
 +      if (element) {
 +        element.addEventListener('click', elements[item].handler.bind(this));
 +      }
 +    }
 +  },
 +
 +  // Event handling functions.
 +  presentationModeClick: function secondaryToolbarPresentationModeClick(evt) {
 +    this.presentationMode.request();
 +    this.close();
 +  },
 +
 +  openFileClick: function secondaryToolbarOpenFileClick(evt) {
 +    document.getElementById('fileInput').click();
 +    this.close();
 +  },
 +
 +  printClick: function secondaryToolbarPrintClick(evt) {
 +    window.print();
 +    this.close();
 +  },
 +
 +  downloadClick: function secondaryToolbarDownloadClick(evt) {
 +    PDFView.download();
 +    this.close();
 +  },
 +
 +  viewBookmarkClick: function secondaryToolbarViewBookmarkClick(evt) {
 +    this.close();
 +  },
 +
 +  firstPageClick: function secondaryToolbarFirstPageClick(evt) {
 +    PDFView.page = 1;
 +    this.close();
 +  },
 +
 +  lastPageClick: function secondaryToolbarLastPageClick(evt) {
 +    PDFView.page = PDFView.pdfDocument.numPages;
 +    this.close();
 +  },
 +
 +  pageRotateCwClick: function secondaryToolbarPageRotateCwClick(evt) {
 +    PDFView.rotatePages(90);
 +  },
 +
 +  pageRotateCcwClick: function secondaryToolbarPageRotateCcwClick(evt) {
 +    PDFView.rotatePages(-90);
 +  },
 +
 +  documentPropertiesClick: function secondaryToolbarDocumentPropsClick(evt) {
 +    this.documentProperties.show();
 +    this.close();
 +  },
 +
 +  // Misc. functions for interacting with the toolbar.
 +  setMaxHeight: function secondaryToolbarSetMaxHeight(container) {
 +    if (!container || !this.buttonContainer) {
 +      return;
 +    }
 +    this.newContainerHeight = container.clientHeight;
 +    if (this.previousContainerHeight === this.newContainerHeight) {
 +      return;
 +    }
 +    this.buttonContainer.setAttribute('style',
 +      'max-height: ' + (this.newContainerHeight - SCROLLBAR_PADDING) + 'px;');
 +    this.previousContainerHeight = this.newContainerHeight;
 +  },
 +
 +  open: function secondaryToolbarOpen() {
 +    if (this.opened) {
 +      return;
 +    }
 +    this.opened = true;
 +    this.toggleButton.classList.add('toggled');
 +    this.toolbar.classList.remove('hidden');
 +  },
 +
 +  close: function secondaryToolbarClose(target) {
 +    if (!this.opened) {
 +      return;
 +    } else if (target && !this.toolbar.contains(target)) {
 +      return;
 +    }
 +    this.opened = false;
 +    this.toolbar.classList.add('hidden');
 +    this.toggleButton.classList.remove('toggled');
 +  },
 +
 +  toggle: function secondaryToolbarToggle() {
 +    if (this.opened) {
 +      this.close();
 +    } else {
 +      this.open();
 +    }
 +  }
 +};
 +
 +
 +var PasswordPrompt = {
 +  visible: false,
 +  updatePassword: null,
 +  reason: null,
 +  overlayContainer: null,
 +  passwordField: null,
 +  passwordText: null,
 +  passwordSubmit: null,
 +  passwordCancel: null,
 +
 +  initialize: function secondaryToolbarInitialize(options) {
 +    this.overlayContainer = options.overlayContainer;
 +    this.passwordField = options.passwordField;
 +    this.passwordText = options.passwordText;
 +    this.passwordSubmit = options.passwordSubmit;
 +    this.passwordCancel = options.passwordCancel;
 +
 +    // Attach the event listeners.
 +    this.passwordSubmit.addEventListener('click',
 +      this.verifyPassword.bind(this));
 +
 +    this.passwordCancel.addEventListener('click', this.hide.bind(this));
 +
 +    this.passwordField.addEventListener('keydown',
 +      function (e) {
 +        if (e.keyCode === 13) { // Enter key
 +          this.verifyPassword();
 +        }
 +      }.bind(this));
 +
 +    window.addEventListener('keydown',
 +      function (e) {
 +        if (e.keyCode === 27) { // Esc key
 +          this.hide();
 +        }
 +      }.bind(this));
 +  },
 +
 +  show: function passwordPromptShow() {
 +    if (this.visible) {
 +      return;
 +    }
 +    this.visible = true;
 +    this.overlayContainer.classList.remove('hidden');
 +    this.overlayContainer.firstElementChild.classList.remove('hidden');
 +    this.passwordField.focus();
 +
 +    var promptString = mozL10n.get('password_label', null,
 +      'Enter the password to open this PDF file.');
 +
 +    if (this.reason === PDFJS.PasswordResponses.INCORRECT_PASSWORD) {
 +      promptString = mozL10n.get('password_invalid', null,
 +        'Invalid password. Please try again.');
 +    }
 +
 +    this.passwordText.textContent = promptString;
 +  },
 +
 +  hide: function passwordPromptClose() {
 +    if (!this.visible) {
 +      return;
 +    }
 +    this.visible = false;
 +    this.passwordField.value = '';
 +    this.overlayContainer.classList.add('hidden');
 +    this.overlayContainer.firstElementChild.classList.add('hidden');
 +  },
 +
 +  verifyPassword: function passwordPromptVerifyPassword() {
 +    var password = this.passwordField.value;
 +    if (password && password.length > 0) {
 +      this.hide();
 +      return this.updatePassword(password);
 +    }
 +  }
 +};
 +
 +
 +var DELAY_BEFORE_HIDING_CONTROLS = 3000; // in ms
 +var SELECTOR = 'presentationControls';
 +var DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS = 1000; // in ms
 +
 +var PresentationMode = {
 +  active: false,
 +  args: null,
 +  contextMenuOpen: false,
 +  prevCoords: { x: null, y: null },
 +
 +  initialize: function presentationModeInitialize(options) {
 +    this.container = options.container;
 +    this.secondaryToolbar = options.secondaryToolbar;
 +
 +    this.viewer = this.container.firstElementChild;
 +
 +    this.firstPage = options.firstPage;
 +    this.lastPage = options.lastPage;
 +    this.pageRotateCw = options.pageRotateCw;
 +    this.pageRotateCcw = options.pageRotateCcw;
 +
 +    this.firstPage.addEventListener('click', function() {
 +      this.contextMenuOpen = false;
 +      this.secondaryToolbar.firstPageClick();
 +    }.bind(this));
 +    this.lastPage.addEventListener('click', function() {
 +      this.contextMenuOpen = false;
 +      this.secondaryToolbar.lastPageClick();
 +    }.bind(this));
 +
 +    this.pageRotateCw.addEventListener('click', function() {
 +      this.contextMenuOpen = false;
 +      this.secondaryToolbar.pageRotateCwClick();
 +    }.bind(this));
 +    this.pageRotateCcw.addEventListener('click', function() {
 +      this.contextMenuOpen = false;
 +      this.secondaryToolbar.pageRotateCcwClick();
 +    }.bind(this));
 +  },
 +
 +  get isFullscreen() {
 +    return (document.fullscreenElement ||
 +            document.mozFullScreen ||
 +            document.webkitIsFullScreen ||
 +            document.msFullscreenElement);
 +  },
 +
 +  /**
 +   * Initialize a timeout that is used to reset PDFView.currentPosition when the
 +   * browser transitions to fullscreen mode. Since resize events are triggered
 +   * multiple times during the switch to fullscreen mode, this is necessary in
 +   * order to prevent the page from being scrolled partially, or completely,
 +   * out of view when Presentation Mode is enabled.
 +   * Note: This is only an issue at certain zoom levels, e.g. 'page-width'.
 +   */
 +  _setSwitchInProgress: function presentationMode_setSwitchInProgress() {
 +    if (this.switchInProgress) {
 +      clearTimeout(this.switchInProgress);
 +    }
 +    this.switchInProgress = setTimeout(function switchInProgressTimeout() {
 +      delete this.switchInProgress;
 +    }.bind(this), DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS);
 +
 +    PDFView.currentPosition = null;
 +  },
 +
 +  _resetSwitchInProgress: function presentationMode_resetSwitchInProgress() {
 +    if (this.switchInProgress) {
 +      clearTimeout(this.switchInProgress);
 +      delete this.switchInProgress;
 +    }
 +  },
 +
 +  request: function presentationModeRequest() {
 +    if (!PDFView.supportsFullscreen || this.isFullscreen ||
 +        !this.viewer.hasChildNodes()) {
 +      return false;
 +    }
 +    this._setSwitchInProgress();
 +
 +    if (this.container.requestFullscreen) {
 +      this.container.requestFullscreen();
 +    } else if (this.container.mozRequestFullScreen) {
 +      this.container.mozRequestFullScreen();
 +    } else if (this.container.webkitRequestFullScreen) {
 +      this.container.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
 +    } else if (this.container.msRequestFullscreen) {
 +      this.container.msRequestFullscreen();
 +    } else {
 +      return false;
 +    }
 +
 +    this.args = {
 +      page: PDFView.page,
 +      previousScale: PDFView.currentScaleValue
 +    };
 +
 +    return true;
 +  },
 +
 +  enter: function presentationModeEnter() {
 +    this.active = true;
 +    this._resetSwitchInProgress();
 +
 +    // Ensure that the correct page is scrolled into view when entering
 +    // Presentation Mode, by waiting until fullscreen mode in enabled.
 +    // Note: This is only necessary in non-Mozilla browsers.
 +    setTimeout(function enterPresentationModeTimeout() {
 +      PDFView.page = this.args.page;
 +      PDFView.setScale('page-fit', true);
 +    }.bind(this), 0);
 +
 +    window.addEventListener('mousemove', this.mouseMove, false);
 +    window.addEventListener('mousedown', this.mouseDown, false);
 +    window.addEventListener('contextmenu', this.contextMenu, false);
 +
 +    this.showControls();
 +    HandTool.enterPresentationMode();
 +    this.contextMenuOpen = false;
 +    this.container.setAttribute('contextmenu', 'viewerContextMenu');
 +  },
 +
 +  exit: function presentationModeExit() {
 +    var page = PDFView.page;
 +
 +    // Ensure that the correct page is scrolled into view when exiting
 +    // Presentation Mode, by waiting until fullscreen mode is disabled.
 +    // Note: This is only necessary in non-Mozilla browsers.
 +    setTimeout(function exitPresentationModeTimeout() {
 +      PDFView.setScale(this.args.previousScale);
 +      PDFView.page = page;
 +      // Keep Presentation Mode active until the page is scrolled into view,
 +      // to prevent issues in non-Mozilla browsers.
 +      this.active = false;
 +      this.args = null;
 +    }.bind(this), 0);
 +
 +    window.removeEventListener('mousemove', this.mouseMove, false);
 +    window.removeEventListener('mousedown', this.mouseDown, false);
 +    window.removeEventListener('contextmenu', this.contextMenu, false);
 +
 +    this.hideControls();
 +    PDFView.clearMouseScrollState();
 +    HandTool.exitPresentationMode();
 +    this.container.removeAttribute('contextmenu');
 +    this.contextMenuOpen = false;
 +
 +    // Ensure that the thumbnail of the current page is visible
 +    // when exiting presentation mode.
 +    scrollIntoView(document.getElementById('thumbnailContainer' + page));
 +  },
 +
 +  showControls: function presentationModeShowControls() {
 +    if (this.controlsTimeout) {
 +      clearTimeout(this.controlsTimeout);
 +    } else {
 +      this.container.classList.add(SELECTOR);
 +    }
 +    this.controlsTimeout = setTimeout(function hideControlsTimeout() {
 +      this.container.classList.remove(SELECTOR);
 +      delete this.controlsTimeout;
 +    }.bind(this), DELAY_BEFORE_HIDING_CONTROLS);
 +  },
 +
 +  hideControls: function presentationModeHideControls() {
 +    if (!this.controlsTimeout) {
 +      return;
 +    }
 +    this.container.classList.remove(SELECTOR);
 +    clearTimeout(this.controlsTimeout);
 +    delete this.controlsTimeout;
 +  },
 +
 +  mouseMove: function presentationModeMouseMove(evt) {
 +    // Workaround for a bug in WebKit browsers that causes the 'mousemove' event
 +    // to be fired when the cursor is changed. For details, see:
 +    // http://code.google.com/p/chromium/issues/detail?id=103041.
 +
 +    var currCoords = { x: evt.clientX, y: evt.clientY };
 +    var prevCoords = PresentationMode.prevCoords;
 +    PresentationMode.prevCoords = currCoords;
 +
 +    if (currCoords.x === prevCoords.x && currCoords.y === prevCoords.y) {
 +      return;
 +    }
 +    PresentationMode.showControls();
 +  },
 +
 +  mouseDown: function presentationModeMouseDown(evt) {
 +    var self = PresentationMode;
 +    if (self.contextMenuOpen) {
 +      self.contextMenuOpen = false;
 +      evt.preventDefault();
 +      return;
 +    }
 +
 +    if (evt.button === 0) {
 +      // Enable clicking of links in presentation mode. Please note:
 +      // Only links pointing to destinations in the current PDF document work.
 +      var isInternalLink = (evt.target.href &&
 +                            evt.target.classList.contains('internalLink'));
 +      if (!isInternalLink) {
 +        // Unless an internal link was clicked, advance one page.
 +        evt.preventDefault();
 +        PDFView.page += (evt.shiftKey ? -1 : 1);
 +      }
 +    }
 +  },
 +
 +  contextMenu: function presentationModeContextMenu(evt) {
 +    PresentationMode.contextMenuOpen = true;
 +  }
 +};
 +
 +(function presentationModeClosure() {
 +  function presentationModeChange(e) {
 +    if (PresentationMode.isFullscreen) {
 +      PresentationMode.enter();
 +    } else {
 +      PresentationMode.exit();
 +    }
 +  }
 +
 +  window.addEventListener('fullscreenchange', presentationModeChange, false);
 +  window.addEventListener('mozfullscreenchange', presentationModeChange, false);
 +  window.addEventListener('webkitfullscreenchange', presentationModeChange,
 +                          false);
 +  window.addEventListener('MSFullscreenChange', presentationModeChange, false);
 +})();
 +
 +
 +/* Copyright 2013 Rob Wu <gwnRob at gmail.com>
 + * https://github.com/Rob--W/grab-to-pan.js
 + *
 + * Licensed under the Apache License, Version 2.0 (the "License");
 + * you may not use this file except in compliance with the License.
 + * You may obtain a copy of the License at
 + *
 + *     http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing, software
 + * distributed under the License is distributed on an "AS IS" BASIS,
 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 + * See the License for the specific language governing permissions and
 + * limitations under the License.
 + */
 +
 +'use strict';
 +
 +var GrabToPan = (function GrabToPanClosure() {
 +  /**
 +   * Construct a GrabToPan instance for a given HTML element.
 +   * @param options.element {Element}
 +   * @param options.ignoreTarget {function} optional. See `ignoreTarget(node)`
 +   * @param options.onActiveChanged {function(boolean)} optional. Called
 +   *  when grab-to-pan is (de)activated. The first argument is a boolean that
 +   *  shows whether grab-to-pan is activated.
 +   */
 +  function GrabToPan(options) {
 +    this.element = options.element;
 +    this.document = options.element.ownerDocument;
 +    if (typeof options.ignoreTarget === 'function') {
 +      this.ignoreTarget = options.ignoreTarget;
 +    }
 +    this.onActiveChanged = options.onActiveChanged;
 +
 +    // Bind the contexts to ensure that `this` always points to
 +    // the GrabToPan instance.
 +    this.activate = this.activate.bind(this);
 +    this.deactivate = this.deactivate.bind(this);
 +    this.toggle = this.toggle.bind(this);
 +    this._onmousedown = this._onmousedown.bind(this);
 +    this._onmousemove = this._onmousemove.bind(this);
 +    this._endPan = this._endPan.bind(this);
 +
 +    // This overlay will be inserted in the document when the mouse moves during
 +    // a grab operation, to ensure that the cursor has the desired appearance.
 +    var overlay = this.overlay = document.createElement('div');
 +    overlay.className = 'grab-to-pan-grabbing';
 +  }
 +  GrabToPan.prototype = {
 +    /**
 +     * Class name of element which can be grabbed
 +     */
 +    CSS_CLASS_GRAB: 'grab-to-pan-grab',
 +
 +    /**
 +     * Bind a mousedown event to the element to enable grab-detection.
 +     */
 +    activate: function GrabToPan_activate() {
 +      if (!this.active) {
 +        this.active = true;
 +        this.element.addEventListener('mousedown', this._onmousedown, true);
 +        this.element.classList.add(this.CSS_CLASS_GRAB);
 +        if (this.onActiveChanged) {
 +          this.onActiveChanged(true);
 +        }
 +      }
 +    },
 +
 +    /**
 +     * Removes all events. Any pending pan session is immediately stopped.
 +     */
 +    deactivate: function GrabToPan_deactivate() {
 +      if (this.active) {
 +        this.active = false;
 +        this.element.removeEventListener('mousedown', this._onmousedown, true);
 +        this._endPan();
 +        this.element.classList.remove(this.CSS_CLASS_GRAB);
 +        if (this.onActiveChanged) {
 +          this.onActiveChanged(false);
 +        }
 +      }
 +    },
 +
 +    toggle: function GrabToPan_toggle() {
 +      if (this.active) {
 +        this.deactivate();
 +      } else {
 +        this.activate();
 +      }
 +    },
 +
 +    /**
 +     * Whether to not pan if the target element is clicked.
 +     * Override this method to change the default behaviour.
 +     *
 +     * @param node {Element} The target of the event
 +     * @return {boolean} Whether to not react to the click event.
 +     */
 +    ignoreTarget: function GrabToPan_ignoreTarget(node) {
 +      // Use matchesSelector to check whether the clicked element
 +      // is (a child of) an input element / link
 +      return node[matchesSelector](
 +        'a[href], a[href] *, input, textarea, button, button *, select, option'
 +      );
 +    },
 +
 +    /**
 +     * @private
 +     */
 +    _onmousedown: function GrabToPan__onmousedown(event) {
 +      if (event.button !== 0 || this.ignoreTarget(event.target)) {
 +        return;
 +      }
 +      if (event.originalTarget) {
 +        try {
 +          /* jshint expr:true */
 +          event.originalTarget.tagName;
 +        } catch (e) {
 +          // Mozilla-specific: element is a scrollbar (XUL element)
 +          return;
 +        }
 +      }
 +
 +      this.scrollLeftStart = this.element.scrollLeft;
 +      this.scrollTopStart = this.element.scrollTop;
 +      this.clientXStart = event.clientX;
 +      this.clientYStart = event.clientY;
 +      this.document.addEventListener('mousemove', this._onmousemove, true);
 +      this.document.addEventListener('mouseup', this._endPan, true);
 +      // When a scroll event occurs before a mousemove, assume that the user
 +      // dragged a scrollbar (necessary for Opera Presto, Safari and IE)
 +      // (not needed for Chrome/Firefox)
 +      this.element.addEventListener('scroll', this._endPan, true);
 +      event.preventDefault();
 +      event.stopPropagation();
 +      this.document.documentElement.classList.add(this.CSS_CLASS_GRABBING);
 +    },
 +
 +    /**
 +     * @private
 +     */
 +    _onmousemove: function GrabToPan__onmousemove(event) {
 +      this.element.removeEventListener('scroll', this._endPan, true);
 +      if (isLeftMouseReleased(event)) {
 +        this._endPan();
 +        return;
 +      }
 +      var xDiff = event.clientX - this.clientXStart;
 +      var yDiff = event.clientY - this.clientYStart;
 +      this.element.scrollTop = this.scrollTopStart - yDiff;
 +      this.element.scrollLeft = this.scrollLeftStart - xDiff;
 +      if (!this.overlay.parentNode) {
 +        document.body.appendChild(this.overlay);
 +      }
 +    },
 +
 +    /**
 +     * @private
 +     */
 +    _endPan: function GrabToPan__endPan() {
 +      this.element.removeEventListener('scroll', this._endPan, true);
 +      this.document.removeEventListener('mousemove', this._onmousemove, true);
 +      this.document.removeEventListener('mouseup', this._endPan, true);
 +      if (this.overlay.parentNode) {
 +        this.overlay.parentNode.removeChild(this.overlay);
 +      }
 +    }
 +  };
 +
 +  // Get the correct (vendor-prefixed) name of the matches method.
 +  var matchesSelector;
 +  ['webkitM', 'mozM', 'msM', 'oM', 'm'].some(function(prefix) {
 +    var name = prefix + 'atches';
 +    if (name in document.documentElement) {
 +      matchesSelector = name;
 +    }
 +    name += 'Selector';
 +    if (name in document.documentElement) {
 +      matchesSelector = name;
 +    }
 +    return matchesSelector; // If found, then truthy, and [].some() ends.
 +  });
 +
 +  // Browser sniffing because it's impossible to feature-detect
 +  // whether event.which for onmousemove is reliable
 +  var isNotIEorIsIE10plus = !document.documentMode || document.documentMode > 9;
 +  var chrome = window.chrome;
 +  var isChrome15OrOpera15plus = chrome && (chrome.webstore || chrome.app);
 +  //                                       ^ Chrome 15+       ^ Opera 15+
 +  var isSafari6plus = /Apple/.test(navigator.vendor) &&
 +                      /Version\/([6-9]\d*|[1-5]\d+)/.test(navigator.userAgent);
 +
 +  /**
 +   * Whether the left mouse is not pressed.
 +   * @param event {MouseEvent}
 +   * @return {boolean} True if the left mouse button is not pressed.
 +   *                   False if unsure or if the left mouse button is pressed.
 +   */
 +  function isLeftMouseReleased(event) {
 +    if ('buttons' in event && isNotIEorIsIE10plus) {
 +      // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-buttons
 +      // Firefox 15+
 +      // Internet Explorer 10+
 +      return !(event.buttons | 1);
 +    }
 +    if (isChrome15OrOpera15plus || isSafari6plus) {
 +      // Chrome 14+
 +      // Opera 15+
 +      // Safari 6.0+
 +      return event.which === 0;
 +    }
 +  }
 +
 +  return GrabToPan;
 +})();
 +
 +var HandTool = {
 +  initialize: function handToolInitialize(options) {
 +    var toggleHandTool = options.toggleHandTool;
 +    this.handTool = new GrabToPan({
 +      element: options.container,
 +      onActiveChanged: function(isActive) {
 +        if (!toggleHandTool) {
 +          return;
 +        }
 +        if (isActive) {
 +          toggleHandTool.title =
 +            mozL10n.get('hand_tool_disable.title', null, 'Disable hand tool');
 +          toggleHandTool.firstElementChild.textContent =
 +            mozL10n.get('hand_tool_disable_label', null, 'Disable hand tool');
 +        } else {
 +          toggleHandTool.title =
 +            mozL10n.get('hand_tool_enable.title', null, 'Enable hand tool');
 +          toggleHandTool.firstElementChild.textContent =
 +            mozL10n.get('hand_tool_enable_label', null, 'Enable hand tool');
 +        }
 +      }
 +    });
 +    if (toggleHandTool) {
 +      toggleHandTool.addEventListener('click', this.toggle.bind(this), false);
 +    }
 +    // TODO: Read global prefs and call this.handTool.activate() if needed.
 +  },
 +
 +  toggle: function handToolToggle() {
 +    this.handTool.toggle();
 +    SecondaryToolbar.close();
 +  },
 +
 +  enterPresentationMode: function handToolEnterPresentationMode() {
 +    if (this.handTool.active) {
 +      this.wasActive = true;
 +      this.handTool.deactivate();
 +    }
 +  },
 +
 +  exitPresentationMode: function handToolExitPresentationMode() {
 +    if (this.wasActive) {
 +      this.wasActive = null;
 +      this.handTool.activate();
 +    }
 +  }
 +};
 +
 +
 +var DocumentProperties = {
 +  overlayContainer: null,
 +  fileName: '',
 +  fileSize: '',
 +  visible: false,
 +
 +  // Document property fields (in the viewer).
 +  fileNameField: null,
 +  fileSizeField: null,
 +  titleField: null,
 +  authorField: null,
 +  subjectField: null,
 +  keywordsField: null,
 +  creationDateField: null,
 +  modificationDateField: null,
 +  creatorField: null,
 +  producerField: null,
 +  versionField: null,
 +  pageCountField: null,
 +
 +  initialize: function documentPropertiesInitialize(options) {
 +    this.overlayContainer = options.overlayContainer;
 +
 +    // Set the document property fields.
 +    this.fileNameField = options.fileNameField;
 +    this.fileSizeField = options.fileSizeField;
 +    this.titleField = options.titleField;
 +    this.authorField = options.authorField;
 +    this.subjectField = options.subjectField;
 +    this.keywordsField = options.keywordsField;
 +    this.creationDateField = options.creationDateField;
 +    this.modificationDateField = options.modificationDateField;
 +    this.creatorField = options.creatorField;
 +    this.producerField = options.producerField;
 +    this.versionField = options.versionField;
 +    this.pageCountField = options.pageCountField;
 +
 +    // Bind the event listener for the Close button.
 +    if (options.closeButton) {
 +      options.closeButton.addEventListener('click', this.hide.bind(this));
 +    }
 +
 +    // Bind the event listener for the Esc key (to close the dialog).
 +    window.addEventListener('keydown',
 +      function (e) {
 +        if (e.keyCode === 27) { // Esc key
 +          this.hide();
 +        }
 +      }.bind(this));
 +  },
 +
 +  getProperties: function documentPropertiesGetProperties() {
 +    var self = this;
 +
 +    // Get the file name.
 +    this.fileName = getPDFFileNameFromURL(PDFView.url);
 +
 +    // Get the file size.
 +    PDFView.pdfDocument.getDownloadInfo().then(function(data) {
 +      self.setFileSize(data.length);
 +    });
 +
 +    // Get the other document properties.
 +    PDFView.pdfDocument.getMetadata().then(function(data) {
 +      var fields = [
 +        { field: self.fileNameField, content: self.fileName },
 +        { field: self.fileSizeField, content: self.fileSize },
 +        { field: self.titleField, content: data.info.Title },
 +        { field: self.authorField, content: data.info.Author },
 +        { field: self.subjectField, content: data.info.Subject },
 +        { field: self.keywordsField, content: data.info.Keywords },
 +        { field: self.creationDateField,
 +          content: self.parseDate(data.info.CreationDate) },
 +        { field: self.modificationDateField,
 +          content: self.parseDate(data.info.ModDate) },
 +        { field: self.creatorField, content: data.info.Creator },
 +        { field: self.producerField, content: data.info.Producer },
 +        { field: self.versionField, content: data.info.PDFFormatVersion },
 +        { field: self.pageCountField, content: PDFView.pdfDocument.numPages }
 +      ];
 +
 +      // Show the properties in the dialog.
 +      for (var item in fields) {
 +        var element = fields[item];
 +        if (element.field && element.content !== undefined &&
 +            element.content !== '') {
 +          element.field.textContent = element.content;
 +        }
 +      }
 +    });
 +  },
 +
 +  setFileSize: function documentPropertiesSetFileSize(fileSize) {
 +    var kb = fileSize / 1024;
 +    if (kb < 1024) {
 +      this.fileSize = mozL10n.get('document_properties_kb', {
 +        size_kb: (+kb.toPrecision(3)).toLocaleString(),
 +        size_b: fileSize.toLocaleString()
 +      }, '{{size_kb}} KB ({{size_b}} bytes)');
 +    } else {
 +      this.fileSize = mozL10n.get('document_properties_mb', {
 +        size_mb: (+(kb / 1024).toPrecision(3)).toLocaleString(),
 +        size_b: fileSize.toLocaleString()
 +      }, '{{size_mb}} MB ({{size_b}} bytes)');
 +    }
 +  },
 +
 +  show: function documentPropertiesShow() {
 +    if (this.visible) {
 +      return;
 +    }
 +    this.visible = true;
 +    this.overlayContainer.classList.remove('hidden');
 +    this.overlayContainer.lastElementChild.classList.remove('hidden');
 +    this.getProperties();
 +  },
 +
 +  hide: function documentPropertiesClose() {
 +    if (!this.visible) {
 +      return;
 +    }
 +    this.visible = false;
 +    this.overlayContainer.classList.add('hidden');
 +    this.overlayContainer.lastElementChild.classList.add('hidden');
 +  },
 +
 +  parseDate: function documentPropertiesParseDate(inputDate) {
 +    // This is implemented according to the PDF specification (see
 +    // http://www.gnupdf.org/Date for an overview), but note that 
 +    // Adobe Reader doesn't handle changing the date to universal time
 +    // and doesn't use the user's time zone (they're effectively ignoring
 +    // the HH' and mm' parts of the date string).
 +    var dateToParse = inputDate;
 +    if (dateToParse === undefined) {
 +      return '';
 +    }
 +
 +    // Remove the D: prefix if it is available.
 +    if (dateToParse.substring(0,2) === 'D:') {
 +      dateToParse = dateToParse.substring(2);
 +    }
 +
 +    // Get all elements from the PDF date string.
 +    // JavaScript's Date object expects the month to be between
 +    // 0 and 11 instead of 1 and 12, so we're correcting for this.
 +    var year = parseInt(dateToParse.substring(0,4), 10);
 +    var month = parseInt(dateToParse.substring(4,6), 10) - 1;
 +    var day = parseInt(dateToParse.substring(6,8), 10);
 +    var hours = parseInt(dateToParse.substring(8,10), 10);
 +    var minutes = parseInt(dateToParse.substring(10,12), 10);
 +    var seconds = parseInt(dateToParse.substring(12,14), 10);
 +    var utRel = dateToParse.substring(14,15);
 +    var offsetHours = parseInt(dateToParse.substring(15,17), 10);
 +    var offsetMinutes = parseInt(dateToParse.substring(18,20), 10);
 +
 +    // As per spec, utRel = 'Z' means equal to universal time.
 +    // The other cases ('-' and '+') have to be handled here.
 +    if (utRel == '-') {
 +      hours += offsetHours;
 +      minutes += offsetMinutes;
 +    } else if (utRel == '+') {
 +      hours -= offsetHours;
 +      minutes += offsetMinutes;
 +    }
 +
 +    // Return the new date format from the user's locale.
 +    var date = new Date(Date.UTC(year, month, day, hours, minutes, seconds));
 +    var dateString = date.toLocaleDateString();
 +    var timeString = date.toLocaleTimeString();
 +    return mozL10n.get('document_properties_date_string',
 +                       {date: dateString, time: timeString},
 +                       '{{date}}, {{time}}');
 +  }
 +};
 +
 +
 +var PDFView = {
 +  pages: [],
 +  thumbnails: [],
 +  currentScale: UNKNOWN_SCALE,
 +  currentScaleValue: null,
 +  initialBookmark: document.location.hash.substring(1),
 +  container: null,
 +  thumbnailContainer: null,
 +  initialized: false,
 +  fellback: false,
 +  pdfDocument: null,
 +  sidebarOpen: false,
 +  pageViewScroll: null,
 +  thumbnailViewScroll: null,
 +  pageRotation: 0,
 +  mouseScrollTimeStamp: 0,
 +  mouseScrollDelta: 0,
 +  lastScroll: 0,
 +  previousPageNumber: 1,
 +  isViewerEmbedded: (window.parent !== window),
 +  idleTimeout: null,
 +  currentPosition: null,
 +
 +  // called once when the document is loaded
 +  initialize: function pdfViewInitialize() {
 +    var self = this;
 +    var container = this.container = document.getElementById('viewerContainer');
 +    this.pageViewScroll = {};
 +    this.watchScroll(container, this.pageViewScroll, updateViewarea);
 +
 +    var thumbnailContainer = this.thumbnailContainer =
 +                             document.getElementById('thumbnailView');
 +    this.thumbnailViewScroll = {};
 +    this.watchScroll(thumbnailContainer, this.thumbnailViewScroll,
 +                     this.renderHighestPriority.bind(this));
 +
 +    PDFFindBar.initialize({
 +      bar: document.getElementById('findbar'),
 +      toggleButton: document.getElementById('viewFind'),
 +      findField: document.getElementById('findInput'),
 +      highlightAllCheckbox: document.getElementById('findHighlightAll'),
 +      caseSensitiveCheckbox: document.getElementById('findMatchCase'),
 +      findMsg: document.getElementById('findMsg'),
 +      findStatusIcon: document.getElementById('findStatusIcon'),
 +      findPreviousButton: document.getElementById('findPrevious'),
 +      findNextButton: document.getElementById('findNext')
 +    });
 +
 +    PDFFindController.initialize({
 +      pdfPageSource: this,
 +      integratedFind: this.supportsIntegratedFind
 +    });
 +
 +    HandTool.initialize({
 +      container: container,
 +      toggleHandTool: document.getElementById('toggleHandTool')
 +    });
 +
 +    SecondaryToolbar.initialize({
 +      toolbar: document.getElementById('secondaryToolbar'),
 +      presentationMode: PresentationMode,
 +      toggleButton: document.getElementById('secondaryToolbarToggle'),
 +      presentationModeButton:
 +        document.getElementById('secondaryPresentationMode'),
 +      openFile: document.getElementById('secondaryOpenFile'),
 +      print: document.getElementById('secondaryPrint'),
 +      download: document.getElementById('secondaryDownload'),
 +      viewBookmark: document.getElementById('secondaryViewBookmark'),
 +      firstPage: document.getElementById('firstPage'),
 +      lastPage: document.getElementById('lastPage'),
 +      pageRotateCw: document.getElementById('pageRotateCw'),
 +      pageRotateCcw: document.getElementById('pageRotateCcw'),
 +      documentProperties: DocumentProperties,
 +      documentPropertiesButton: document.getElementById('documentProperties')
 +    });
 +
 +    PasswordPrompt.initialize({
 +      overlayContainer: document.getElementById('overlayContainer'),
 +      passwordField: document.getElementById('password'),
 +      passwordText: document.getElementById('passwordText'),
 +      passwordSubmit: document.getElementById('passwordSubmit'),
 +      passwordCancel: document.getElementById('passwordCancel')
 +    });
 +
 +    PresentationMode.initialize({
 +      container: container,
 +      secondaryToolbar: SecondaryToolbar,
 +      firstPage: document.getElementById('contextFirstPage'),
 +      lastPage: document.getElementById('contextLastPage'),
 +      pageRotateCw: document.getElementById('contextPageRotateCw'),
 +      pageRotateCcw: document.getElementById('contextPageRotateCcw')
 +    });
 +
 +    DocumentProperties.initialize({
 +      overlayContainer: document.getElementById('overlayContainer'),
 +      closeButton: document.getElementById('documentPropertiesClose'),
 +      fileNameField: document.getElementById('fileNameField'),
 +      fileSizeField: document.getElementById('fileSizeField'),
 +      titleField: document.getElementById('titleField'),
 +      authorField: document.getElementById('authorField'),
 +      subjectField: document.getElementById('subjectField'),
 +      keywordsField: document.getElementById('keywordsField'),
 +      creationDateField: document.getElementById('creationDateField'),
 +      modificationDateField: document.getElementById('modificationDateField'),
 +      creatorField: document.getElementById('creatorField'),
 +      producerField: document.getElementById('producerField'),
 +      versionField: document.getElementById('versionField'),
 +      pageCountField: document.getElementById('pageCountField')
 +    });
 +
 +    this.initialized = true;
 +    container.addEventListener('scroll', function() {
 +      self.lastScroll = Date.now();
 +    }, false);
 +  },
 +
 +  getPage: function pdfViewGetPage(n) {
 +    return this.pdfDocument.getPage(n);
 +  },
 +
 +  // Helper function to keep track whether a div was scrolled up or down and
 +  // then call a callback.
 +  watchScroll: function pdfViewWatchScroll(viewAreaElement, state, callback) {
 +    state.down = true;
 +    state.lastY = viewAreaElement.scrollTop;
 +    viewAreaElement.addEventListener('scroll', function webViewerScroll(evt) {
 +      var currentY = viewAreaElement.scrollTop;
 +      var lastY = state.lastY;
 +      if (currentY > lastY)
 +        state.down = true;
 +      else if (currentY < lastY)
 +        state.down = false;
 +      // else do nothing and use previous value
 +      state.lastY = currentY;
 +      callback();
 +    }, true);
 +  },
 +
 +  _setScaleUpdatePages: function pdfView_setScaleUpdatePages(
 +      newScale, newValue, resetAutoSettings, noScroll) {
 +    this.currentScaleValue = newValue;
 +    if (newScale === this.currentScale) {
 +      return;
 +    }
 +    for (var i = 0, ii = this.pages.length; i < ii; i++) {
 +      this.pages[i].update(newScale);
 +    }
 +    this.currentScale = newScale;
 +
 +    if (!noScroll) {
 +      var page = this.page, dest;
 +      if (this.currentPosition && !IGNORE_CURRENT_POSITION_ON_ZOOM) {
 +        page = this.currentPosition.page;
 +        dest = [null, { name: 'XYZ' }, this.currentPosition.left,
 +                this.currentPosition.top, null];
 +      }
 +      this.pages[page - 1].scrollIntoView(dest);
 +    }
 +    var event = document.createEvent('UIEvents');
 +    event.initUIEvent('scalechange', false, false, window, 0);
 +    event.scale = newScale;
 +    event.resetAutoSettings = resetAutoSettings;
 +    window.dispatchEvent(event);
 +  },
 +
 +  setScale: function pdfViewSetScale(value, resetAutoSettings, noScroll) {
 +    if (value === 'custom') {
 +      return;
 +    }
 +    var scale = parseFloat(value);
 +
 +    if (scale > 0) {
 +      this._setScaleUpdatePages(scale, value, true, noScroll);
 +    } else {
 +      var currentPage = this.pages[this.page - 1];
 +      if (!currentPage) {
 +        return;
 +      }
 +      var pageWidthScale = (this.container.clientWidth - SCROLLBAR_PADDING) /
 +                            currentPage.width * currentPage.scale;
 +      var pageHeightScale = (this.container.clientHeight - VERTICAL_PADDING) /
 +                             currentPage.height * currentPage.scale;
 +      switch (value) {
 +        case 'page-actual':
 +          scale = 1;
 +          break;
 +        case 'page-width':
 +          scale = pageWidthScale;
 +          break;
 +        case 'page-height':
 +          scale = pageHeightScale;
 +          break;
 +        case 'page-fit':
 +          scale = Math.min(pageWidthScale, pageHeightScale);
 +          break;
 +        case 'auto':
 +          scale = Math.min(MAX_AUTO_SCALE, pageWidthScale);
 +          break;
 +        default:
 +          console.error('pdfViewSetScale: \'' + value +
 +                        '\' is an unknown zoom value.');
 +          return;
 +      }
 +      this._setScaleUpdatePages(scale, value, resetAutoSettings, noScroll);
 +
 +      selectScaleOption(value);
 +    }
 +  },
 +
 +  zoomIn: function pdfViewZoomIn(ticks) {
 +    var newScale = this.currentScale;
 +    do {
 +      newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
 +      newScale = Math.ceil(newScale * 10) / 10;
 +      newScale = Math.min(MAX_SCALE, newScale);
 +    } while (--ticks && newScale < MAX_SCALE);
 +    this.setScale(newScale, true);
 +  },
 +
 +  zoomOut: function pdfViewZoomOut(ticks) {
 +    var newScale = this.currentScale;
 +    do {
 +      newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
 +      newScale = Math.floor(newScale * 10) / 10;
 +      newScale = Math.max(MIN_SCALE, newScale);
 +    } while (--ticks && newScale > MIN_SCALE);
 +    this.setScale(newScale, true);
 +  },
 +
 +  set page(val) {
 +    var pages = this.pages;
 +    var event = document.createEvent('UIEvents');
 +    event.initUIEvent('pagechange', false, false, window, 0);
 +
 +    if (!(0 < val && val <= pages.length)) {
 +      this.previousPageNumber = val;
 +      event.pageNumber = this.page;
 +      window.dispatchEvent(event);
 +      return;
 +    }
 +
 +    pages[val - 1].updateStats();
 +    this.previousPageNumber = currentPageNumber;
 +    currentPageNumber = val;
 +    event.pageNumber = val;
 +    window.dispatchEvent(event);
 +
 +    // checking if the this.page was called from the updateViewarea function:
 +    // avoiding the creation of two "set page" method (internal and public)
 +    if (updateViewarea.inProgress) {
 +      return;
 +    }
 +    // Avoid scrolling the first page during loading
 +    if (this.loading && val === 1) {
 +      return;
 +    }
 +    pages[val - 1].scrollIntoView();
 +  },
 +
 +  get page() {
 +    return currentPageNumber;
 +  },
 +
 +  get supportsPrinting() {
 +    var canvas = document.createElement('canvas');
 +    var value = 'mozPrintCallback' in canvas;
 +    // shadow
 +    Object.defineProperty(this, 'supportsPrinting', { value: value,
 +                                                      enumerable: true,
 +                                                      configurable: true,
 +                                                      writable: false });
 +    return value;
 +  },
 +
 +  get supportsFullscreen() {
 +    var doc = document.documentElement;
 +    var support = doc.requestFullscreen || doc.mozRequestFullScreen ||
 +                  doc.webkitRequestFullScreen || doc.msRequestFullscreen;
 +
 +    if (document.fullscreenEnabled === false ||
 +        document.mozFullScreenEnabled === false ||
 +        document.webkitFullscreenEnabled === false ||
 +        document.msFullscreenEnabled === false) {
 +      support = false;
 +    } else if (this.isViewerEmbedded) {
 +      // Need to check if the viewer is embedded as well, to prevent issues with
 +      // presentation mode when the viewer is embedded in '<object>' tags.
 +      support = false;
 +    }
 +
 +    Object.defineProperty(this, 'supportsFullscreen', { value: support,
 +                                                        enumerable: true,
 +                                                        configurable: true,
 +                                                        writable: false });
 +    return support;
 +  },
 +
 +  get supportsIntegratedFind() {
 +    var support = false;
 +    Object.defineProperty(this, 'supportsIntegratedFind', { value: support,
 +                                                            enumerable: true,
 +                                                            configurable: true,
 +                                                            writable: false });
 +    return support;
 +  },
 +
 +  get supportsDocumentFonts() {
 +    var support = true;
 +    Object.defineProperty(this, 'supportsDocumentFonts', { value: support,
 +                                                           enumerable: true,
 +                                                           configurable: true,
 +                                                           writable: false });
 +    return support;
 +  },
 +
 +  get supportsDocumentColors() {
 +    var support = true;
 +    Object.defineProperty(this, 'supportsDocumentColors', { value: support,
 +                                                            enumerable: true,
 +                                                            configurable: true,
 +                                                            writable: false });
 +    return support;
 +  },
 +
 +  get loadingBar() {
 +    var bar = new ProgressBar('#loadingBar', {});
 +    Object.defineProperty(this, 'loadingBar', { value: bar,
 +                                                enumerable: true,
 +                                                configurable: true,
 +                                                writable: false });
 +    return bar;
 +  },
 +
 +  get isHorizontalScrollbarEnabled() {
 +    return (PresentationMode.active ? false :
 +            (this.container.scrollWidth > this.container.clientWidth));
 +  },
 +
 +
 +  setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
 +    this.url = url;
 +    try {
 +      this.setTitle(decodeURIComponent(getFileName(url)) || url);
 +    } catch (e) {
 +      // decodeURIComponent may throw URIError,
 +      // fall back to using the unprocessed url in that case
 +      this.setTitle(url);
 +    }
 +  },
 +
 +  setTitle: function pdfViewSetTitle(title) {
 +    document.title = title;
 +  },
 +
 +  // TODO(mack): This function signature should really be pdfViewOpen(url, args)
 +  open: function pdfViewOpen(url, scale, password,
 +                             pdfDataRangeTransport, args) {
 +    var parameters = {password: password};
 +    if (typeof url === 'string') { // URL
 +      this.setTitleUsingUrl(url);
 +      parameters.url = url;
 +    } else if (url && 'byteLength' in url) { // ArrayBuffer
 +      parameters.data = url;
 +    }
 +    if (args) {
 +      for (var prop in args) {
 +        parameters[prop] = args[prop];
 +      }
 +    }
 +
 +    // Terminate worker of the previous document if any.
 +    if (this.pdfDocument) {
 +      this.pdfDocument.destroy();
 +    }
 +    this.pdfDocument = null;
 +    var self = this;
 +    self.loading = true;
 +    var passwordNeeded = function passwordNeeded(updatePassword, reason) {
 +      PasswordPrompt.updatePassword = updatePassword;
 +      PasswordPrompt.reason = reason;
 +      PasswordPrompt.show();
 +    };
 +
 +    function getDocumentProgress(progressData) {
 +      self.progress(progressData.loaded / progressData.total);
 +    }
 +
 +    PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded,
 +                      getDocumentProgress).then(
 +      function getDocumentCallback(pdfDocument) {
 +        self.load(pdfDocument, scale);
 +        self.loading = false;
 +      },
 +      function getDocumentError(message, exception) {
 +        var loadingErrorMessage = mozL10n.get('loading_error', null,
 +          'An error occurred while loading the PDF.');
 +
 +        if (exception && exception.name === 'InvalidPDFException') {
 +          // change error message also for other builds
 +          var loadingErrorMessage = mozL10n.get('invalid_file_error', null,
 +                                        'Invalid or corrupted PDF file.');
 +        }
 +
 +        if (exception && exception.name === 'MissingPDFException') {
 +          // special message for missing PDF's
 +          var loadingErrorMessage = mozL10n.get('missing_file_error', null,
 +                                        'Missing PDF file.');
 +
 +        }
 +
 +        var moreInfo = {
 +          message: message
 +        };
 +        self.error(loadingErrorMessage, moreInfo);
 +        self.loading = false;
 +      }
 +    );
 +  },
 +
 +  download: function pdfViewDownload() {
 +    function noData() {
 +      downloadManager.downloadUrl(url, filename);
 +    }
 +
 +    var url = this.url.split('#')[0];
 +    var filename = getPDFFileNameFromURL(url);
 +    var downloadManager = new DownloadManager();
 +    downloadManager.onerror = function (err) {
 +      // This error won't really be helpful because it's likely the
 +      // fallback won't work either (or is already open).
 +      PDFView.error('PDF failed to download.');
 +    };
 +
 +    if (!this.pdfDocument) { // the PDF is not ready yet
 +      noData();
 +      return;
 +    }
 +
 +    this.pdfDocument.getData().then(
 +      function getDataSuccess(data) {
 +        var blob = PDFJS.createBlob(data, 'application/pdf');
 +        downloadManager.download(blob, url, filename);
 +      },
 +      noData // Error occurred try downloading with just the url.
 +    ).then(null, noData);
 +  },
 +
 +  fallback: function pdfViewFallback(featureId) {
 +    return;
 +  },
 +
 +  navigateTo: function pdfViewNavigateTo(dest) {
 +    var destString = '';
 +    var self = this;
 +
 +    var goToDestination = function(destRef) {
 +      self.pendingRefStr = null;
 +      // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
 +      var pageNumber = destRef instanceof Object ?
 +        self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
 +        (destRef + 1);
 +      if (pageNumber) {
 +        if (pageNumber > self.pages.length) {
 +          pageNumber = self.pages.length;
 +        }
 +        var currentPage = self.pages[pageNumber - 1];
 +        currentPage.scrollIntoView(dest);
 +
 +        // Update the browsing history.
 +        PDFHistory.push({ dest: dest, hash: destString, page: pageNumber });
 +      } else {
 +        self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
 +          var pageNum = pageIndex + 1;
 +          self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] = pageNum;
 +          goToDestination(destRef);
 +        });
 +      }
 +    };
 +
 +    this.destinationsPromise.then(function() {
 +      if (typeof dest === 'string') {
 +        destString = dest;
 +        dest = self.destinations[dest];
 +      }
 +      if (!(dest instanceof Array)) {
 +        return; // invalid destination
 +      }
 +      goToDestination(dest[0]);
 +    });
 +  },
 +
 +  getDestinationHash: function pdfViewGetDestinationHash(dest) {
 +    if (typeof dest === 'string')
 +      return PDFView.getAnchorUrl('#' + escape(dest));
 +    if (dest instanceof Array) {
 +      var destRef = dest[0]; // see navigateTo method for dest format
 +      var pageNumber = destRef instanceof Object ?
 +        this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
 +        (destRef + 1);
 +      if (pageNumber) {
 +        var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber);
 +        var destKind = dest[1];
 +        if (typeof destKind === 'object' && 'name' in destKind &&
 +            destKind.name == 'XYZ') {
 +          var scale = (dest[4] || this.currentScaleValue);
 +          var scaleNumber = parseFloat(scale);
 +          if (scaleNumber) {
 +            scale = scaleNumber * 100;
 +          }
 +          pdfOpenParams += '&zoom=' + scale;
 +          if (dest[2] || dest[3]) {
 +            pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
 +          }
 +        }
 +        return pdfOpenParams;
 +      }
 +    }
 +    return '';
 +  },
 +
 +  /**
 +   * Prefix the full url on anchor links to make sure that links are resolved
 +   * relative to the current URL instead of the one defined in <base href>.
 +   * @param {String} anchor The anchor hash, including the #.
 +   */
 +  getAnchorUrl: function getAnchorUrl(anchor) {
 +    return anchor;
 +  },
 +
 +  /**
 +   * Show the error box.
 +   * @param {String} message A message that is human readable.
 +   * @param {Object} moreInfo (optional) Further information about the error
 +   *                            that is more technical.  Should have a 'message'
 +   *                            and optionally a 'stack' property.
 +   */
 +  error: function pdfViewError(message, moreInfo) {
 +    var moreInfoText = mozL10n.get('error_version_info',
 +      {version: PDFJS.version || '?', build: PDFJS.build || '?'},
 +      'PDF.js v{{version}} (build: {{build}})') + '\n';
 +    if (moreInfo) {
 +      moreInfoText +=
 +        mozL10n.get('error_message', {message: moreInfo.message},
 +        'Message: {{message}}');
 +      if (moreInfo.stack) {
 +        moreInfoText += '\n' +
 +          mozL10n.get('error_stack', {stack: moreInfo.stack},
 +          'Stack: {{stack}}');
 +      } else {
 +        if (moreInfo.filename) {
 +          moreInfoText += '\n' +
 +            mozL10n.get('error_file', {file: moreInfo.filename},
 +            'File: {{file}}');
 +        }
 +        if (moreInfo.lineNumber) {
 +          moreInfoText += '\n' +
 +            mozL10n.get('error_line', {line: moreInfo.lineNumber},
 +            'Line: {{line}}');
 +        }
 +      }
 +    }
 +
 +    var errorWrapper = document.getElementById('errorWrapper');
 +    errorWrapper.removeAttribute('hidden');
 +
 +    var errorMessage = document.getElementById('errorMessage');
 +    errorMessage.textContent = message;
 +
 +    var closeButton = document.getElementById('errorClose');
 +    closeButton.onclick = function() {
 +      errorWrapper.setAttribute('hidden', 'true');
 +    };
 +
 +    var errorMoreInfo = document.getElementById('errorMoreInfo');
 +    var moreInfoButton = document.getElementById('errorShowMore');
 +    var lessInfoButton = document.getElementById('errorShowLess');
 +    moreInfoButton.onclick = function() {
 +      errorMoreInfo.removeAttribute('hidden');
 +      moreInfoButton.setAttribute('hidden', 'true');
 +      lessInfoButton.removeAttribute('hidden');
 +      errorMoreInfo.style.height = errorMoreInfo.scrollHeight + 'px';
 +    };
 +    lessInfoButton.onclick = function() {
 +      errorMoreInfo.setAttribute('hidden', 'true');
 +      moreInfoButton.removeAttribute('hidden');
 +      lessInfoButton.setAttribute('hidden', 'true');
 +    };
 +    moreInfoButton.oncontextmenu = noContextMenuHandler;
 +    lessInfoButton.oncontextmenu = noContextMenuHandler;
 +    closeButton.oncontextmenu = noContextMenuHandler;
 +    moreInfoButton.removeAttribute('hidden');
 +    lessInfoButton.setAttribute('hidden', 'true');
 +    errorMoreInfo.value = moreInfoText;
 +  },
 +
 +  progress: function pdfViewProgress(level) {
 +    var percent = Math.round(level * 100);
 +    // When we transition from full request to range requests, it's possible
 +    // that we discard some of the loaded data. This can cause the loading
 +    // bar to move backwards. So prevent this by only updating the bar if it
 +    // increases.
 +    if (percent > PDFView.loadingBar.percent) {
 +      PDFView.loadingBar.percent = percent;
 +    }
 +  },
 +
 +  load: function pdfViewLoad(pdfDocument, scale) {
 +    var self = this;
 +    var isOnePageRenderedResolved = false;
 +    var resolveOnePageRendered = null;
 +    var onePageRendered = new Promise(function (resolve) {
 +      resolveOnePageRendered = resolve;
 +    });
 +    function bindOnAfterDraw(pageView, thumbnailView) {
 +      // when page is painted, using the image as thumbnail base
 +      pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
 +        if (!isOnePageRenderedResolved) {
 +          isOnePageRenderedResolved = true;
 +          resolveOnePageRendered();
 +        }
 +        thumbnailView.setImage(pageView.canvas);
 +      };
 +    }
 +
 +    PDFFindController.reset();
 +
 +    this.pdfDocument = pdfDocument;
 +
 +    var errorWrapper = document.getElementById('errorWrapper');
 +    errorWrapper.setAttribute('hidden', 'true');
 +
 +    pdfDocument.getDownloadInfo().then(function() {
 +      PDFView.loadingBar.hide();
 +      var outerContainer = document.getElementById('outerContainer');
 +      outerContainer.classList.remove('loadingInProgress');
 +    });
 +
 +    var thumbsView = document.getElementById('thumbnailView');
 +    thumbsView.parentNode.scrollTop = 0;
 +
 +    while (thumbsView.hasChildNodes())
 +      thumbsView.removeChild(thumbsView.lastChild);
 +
 +    if ('_loadingInterval' in thumbsView)
 +      clearInterval(thumbsView._loadingInterval);
 +
 +    var container = document.getElementById('viewer');
 +    while (container.hasChildNodes())
 +      container.removeChild(container.lastChild);
 +
 +    var pagesCount = pdfDocument.numPages;
 +
 +    var id = pdfDocument.fingerprint;
 +    document.getElementById('numPages').textContent =
 +      mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
 +    document.getElementById('pageNumber').max = pagesCount;
 +
 +    var prefs = PDFView.prefs = new Preferences();
 +    PDFView.documentFingerprint = id;
 +    var store = PDFView.store = new ViewHistory(id);
 +
 +    this.pageRotation = 0;
 +
 +    var pages = this.pages = [];
 +    var pagesRefMap = this.pagesRefMap = {};
 +    var thumbnails = this.thumbnails = [];
 +
 +    var resolvePagesPromise;
 +    var pagesPromise = new Promise(function (resolve) {
 +      resolvePagesPromise = resolve;
 +    });
 +    this.pagesPromise = pagesPromise;
 +
 +    var firstPagePromise = pdfDocument.getPage(1);
 +
 +    // Fetch a single page so we can get a viewport that will be the default
 +    // viewport for all pages
 +    firstPagePromise.then(function(pdfPage) {
 +      var viewport = pdfPage.getViewport((scale || 1.0) * CSS_UNITS);
 +      for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
 +        var viewportClone = viewport.clone();
 +        var pageView = new PageView(container, pageNum, scale,
 +                                    self.navigateTo.bind(self),
 +                                    viewportClone);
 +        var thumbnailView = new ThumbnailView(thumbsView, pageNum,
 +                                              viewportClone);
 +        bindOnAfterDraw(pageView, thumbnailView);
 +        pages.push(pageView);
 +        thumbnails.push(thumbnailView);
 +      }
 +
 +      // Fetch all the pages since the viewport is needed before printing
 +      // starts to create the correct size canvas. Wait until one page is
 +      // rendered so we don't tie up too many resources early on.
 +      onePageRendered.then(function () {
 +        if (!PDFJS.disableAutoFetch) {
 +          var getPagesLeft = pagesCount;
 +          for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
 +            pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
 +              var pageView = pages[pageNum - 1];
 +              if (!pageView.pdfPage) {
 +                pageView.setPdfPage(pdfPage);
 +              }
 +              var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R';
 +              pagesRefMap[refStr] = pageNum;
 +              getPagesLeft--;
 +              if (!getPagesLeft) {
 +                resolvePagesPromise();
 +              }
 +            }.bind(null, pageNum));
 +          }
 +        } else {
 +          // XXX: Printing is semi-broken with auto fetch disabled.
 +          resolvePagesPromise();
 +        }
 +      });
 +
 +      var event = document.createEvent('CustomEvent');
 +      event.initCustomEvent('documentload', true, true, {});
 +      window.dispatchEvent(event);
 +
 +      PDFView.loadingBar.setWidth(container);
 +
 +      PDFFindController.resolveFirstPage();
 +    });
 +
 +    var prefsPromise = prefs.initializedPromise;
 +    var storePromise = store.initializedPromise;
 +    Promise.all([firstPagePromise, prefsPromise, storePromise]).
 +        then(function() {
 +      var showPreviousViewOnLoad = prefs.get('showPreviousViewOnLoad');
 +      var defaultZoomValue = prefs.get('defaultZoomValue');
 +
 +      var storedHash = null;
 +      if (showPreviousViewOnLoad && store.get('exists', false)) {
 +        var pageNum = store.get('page', '1');
 +        var zoom = defaultZoomValue || store.get('zoom', PDFView.currentScale);
 +        var left = store.get('scrollLeft', '0');
 +        var top = store.get('scrollTop', '0');
 +
 +        storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' +
 +                     left + ',' + top;
 +      } else if (defaultZoomValue) {
 +        storedHash = 'page=1&zoom=' + defaultZoomValue;
 +      }
 +      // Initialize the browsing history.
 +      PDFHistory.initialize(self.documentFingerprint);
 +
 +      self.setInitialView(storedHash, scale);
 +
 +      // Make all navigation keys work on document load,
 +      // unless the viewer is embedded in a web page.
 +      if (!self.isViewerEmbedded) {
 +        self.container.focus();
 +      }
 +    });
 +
 +    pagesPromise.then(function() {
 +      if (PDFView.supportsPrinting) {
 +        pdfDocument.getJavaScript().then(function(javaScript) {
 +          if (javaScript.length) {
 +            console.warn('Warning: JavaScript is not supported');
 +            PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.javaScript);
 +          }
 +          // Hack to support auto printing.
 +          var regex = /\bprint\s*\(/g;
 +          for (var i = 0, ii = javaScript.length; i < ii; i++) {
 +            var js = javaScript[i];
 +            if (js && regex.test(js)) {
 +              setTimeout(function() {
 +                window.print();
 +              });
 +              return;
 +            }
 +          }
 +        });
 +      }
 +    });
 +
 +    var destinationsPromise =
 +      this.destinationsPromise = pdfDocument.getDestinations();
 +    destinationsPromise.then(function(destinations) {
 +      self.destinations = destinations;
 +    });
 +
 +    // outline depends on destinations and pagesRefMap
 +    var promises = [pagesPromise, destinationsPromise,
 +                    PDFView.animationStartedPromise];
 +    Promise.all(promises).then(function() {
 +      pdfDocument.getOutline().then(function(outline) {
 +        self.outline = new DocumentOutlineView(outline);
 +        document.getElementById('viewOutline').disabled = !outline;
 +
 +        if (outline && prefs.get('ifAvailableShowOutlineOnLoad')) {
 +          if (!self.sidebarOpen) {
 +            document.getElementById('sidebarToggle').click();
 +          }
 +          self.switchSidebarView('outline');
 +        }
 +      });
 +    });
 +
 +    pdfDocument.getMetadata().then(function(data) {
 +      var info = data.info, metadata = data.metadata;
 +      self.documentInfo = info;
 +      self.metadata = metadata;
 +
 +      // Provides some basic debug information
 +      console.log('PDF ' + pdfDocument.fingerprint + ' [' +
 +                  info.PDFFormatVersion + ' ' + (info.Producer || '-') +
 +                  ' / ' + (info.Creator || '-') + ']' +
 +                  (PDFJS.version ? ' (PDF.js: ' + PDFJS.version + ')' : ''));
 +
 +      var pdfTitle;
 +      if (metadata) {
 +        if (metadata.has('dc:title'))
 +          pdfTitle = metadata.get('dc:title');
 +      }
 +
 +      if (!pdfTitle && info && info['Title'])
 +        pdfTitle = info['Title'];
 +
 +      if (pdfTitle)
 +        self.setTitle(pdfTitle + ' - ' + document.title);
 +
 +      if (info.IsAcroFormPresent) {
 +        console.warn('Warning: AcroForm/XFA is not supported');
 +        PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.forms);
 +      }
 +
 +    });
 +  },
 +
 +  setInitialView: function pdfViewSetInitialView(storedHash, scale) {
 +    // Reset the current scale, as otherwise the page's scale might not get
 +    // updated if the zoom level stayed the same.
 +    this.currentScale = 0;
 +    this.currentScaleValue = null;
 +    // When opening a new file (when one is already loaded in the viewer):
 +    // Reset 'currentPageNumber', since otherwise the page's scale will be wrong
 +    // if 'currentPageNumber' is larger than the number of pages in the file.
 +    document.getElementById('pageNumber').value = currentPageNumber = 1;
 +    // Reset the current position when loading a new file,
 +    // to prevent displaying the wrong position in the document.
 +    this.currentPosition = null;
 +
 +    if (PDFHistory.initialDestination) {
 +      this.navigateTo(PDFHistory.initialDestination);
 +      PDFHistory.initialDestination = null;
 +    } else if (this.initialBookmark) {
 +      this.setHash(this.initialBookmark);
 +      PDFHistory.push({ hash: this.initialBookmark }, !!this.initialBookmark);
 +      this.initialBookmark = null;
 +    } else if (storedHash) {
 +      this.setHash(storedHash);
 +    } else if (scale) {
 +      this.setScale(scale, true);
 +      this.page = 1;
 +    }
 +
 +    if (PDFView.currentScale === UNKNOWN_SCALE) {
 +      // Scale was not initialized: invalid bookmark or scale was not specified.
 +      // Setting the default one.
 +      this.setScale(DEFAULT_SCALE, true);
 +    }
 +  },
 +
 +  renderHighestPriority: function pdfViewRenderHighestPriority() {
 +    if (PDFView.idleTimeout) {
 +      clearTimeout(PDFView.idleTimeout);
 +      PDFView.idleTimeout = null;
 +    }
 +
 +    // Pages have a higher priority than thumbnails, so check them first.
 +    var visiblePages = this.getVisiblePages();
 +    var pageView = this.getHighestPriority(visiblePages, this.pages,
 +                                           this.pageViewScroll.down);
 +    if (pageView) {
 +      this.renderView(pageView, 'page');
 +      return;
 +    }
 +    // No pages needed rendering so check thumbnails.
 +    if (this.sidebarOpen) {
 +      var visibleThumbs = this.getVisibleThumbs();
 +      var thumbView = this.getHighestPriority(visibleThumbs,
 +                                              this.thumbnails,
 +                                              this.thumbnailViewScroll.down);
 +      if (thumbView) {
 +        this.renderView(thumbView, 'thumbnail');
 +        return;
 +      }
 +    }
 +
 +    PDFView.idleTimeout = setTimeout(function () {
 +      PDFView.cleanup();
 +    }, CLEANUP_TIMEOUT);
 +  },
 +
 +  cleanup: function pdfViewCleanup() {
 +    for (var i = 0, ii = this.pages.length; i < ii; i++) {
 +      if (this.pages[i] &&
 +          this.pages[i].renderingState !== RenderingStates.FINISHED) {
 +        this.pages[i].reset();
 +      }
 +    }
 +    this.pdfDocument.cleanup();
 +  },
 +
 +  getHighestPriority: function pdfViewGetHighestPriority(visible, views,
 +                                                         scrolledDown) {
 +    // The state has changed figure out which page has the highest priority to
 +    // render next (if any).
 +    // Priority:
 +    // 1 visible pages
 +    // 2 if last scrolled down page after the visible pages
 +    // 2 if last scrolled up page before the visible pages
 +    var visibleViews = visible.views;
 +
 +    var numVisible = visibleViews.length;
 +    if (numVisible === 0) {
 +      return false;
 +    }
 +    for (var i = 0; i < numVisible; ++i) {
 +      var view = visibleViews[i].view;
 +      if (!this.isViewFinished(view))
 +        return view;
 +    }
 +
 +    // All the visible views have rendered, try to render next/previous pages.
 +    if (scrolledDown) {
 +      var nextPageIndex = visible.last.id;
 +      // ID's start at 1 so no need to add 1.
 +      if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex]))
 +        return views[nextPageIndex];
 +    } else {
 +      var previousPageIndex = visible.first.id - 2;
 +      if (views[previousPageIndex] &&
 +          !this.isViewFinished(views[previousPageIndex]))
 +        return views[previousPageIndex];
 +    }
 +    // Everything that needs to be rendered has been.
 +    return false;
 +  },
 +
 +  isViewFinished: function pdfViewIsViewFinished(view) {
 +    return view.renderingState === RenderingStates.FINISHED;
 +  },
 +
 +  // Render a page or thumbnail view. This calls the appropriate function based
 +  // on the views state. If the view is already rendered it will return false.
 +  renderView: function pdfViewRender(view, type) {
 +    var state = view.renderingState;
 +    switch (state) {
 +      case RenderingStates.FINISHED:
 +        return false;
 +      case RenderingStates.PAUSED:
 +        PDFView.highestPriorityPage = type + view.id;
 +        view.resume();
 +        break;
 +      case RenderingStates.RUNNING:
 +        PDFView.highestPriorityPage = type + view.id;
 +        break;
 +      case RenderingStates.INITIAL:
 +        PDFView.highestPriorityPage = type + view.id;
 +        view.draw(this.renderHighestPriority.bind(this));
 +        break;
 +    }
 +    return true;
 +  },
 +
 +  setHash: function pdfViewSetHash(hash) {
 +    if (!hash)
 +      return;
 +
 +    if (hash.indexOf('=') >= 0) {
 +      var params = PDFView.parseQueryString(hash);
 +      // borrowing syntax from "Parameters for Opening PDF Files"
 +      if ('nameddest' in params) {
 +        PDFHistory.updateNextHashParam(params.nameddest);
 +        PDFView.navigateTo(params.nameddest);
 +        return;
 +      }
 +      var pageNumber, dest;
 +      if ('page' in params) {
 +        pageNumber = (params.page | 0) || 1;
 +      }
 +      if ('zoom' in params) {
 +        var zoomArgs = params.zoom.split(','); // scale,left,top
 +        // building destination array
 +
 +        // If the zoom value, it has to get divided by 100. If it is a string,
 +        // it should stay as it is.
 +        var zoomArg = zoomArgs[0];
 +        var zoomArgNumber = parseFloat(zoomArg);
 +        if (zoomArgNumber) {
 +          zoomArg = zoomArgNumber / 100;
 +        }
 +        dest = [null, {name: 'XYZ'},
 +                zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
 +                zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
 +                zoomArg];
 +      }
 +      if (dest) {
 +        var currentPage = this.pages[(pageNumber || this.page) - 1];
 +        currentPage.scrollIntoView(dest);
 +      } else if (pageNumber) {
 +        this.page = pageNumber; // simple page
 +      }
 +      if ('pagemode' in params) {
 +        var toggle = document.getElementById('sidebarToggle');
 +        if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks') {
 +          if (!this.sidebarOpen) {
 +            toggle.click();
 +          }
 +          this.switchSidebarView(params.pagemode === 'thumbs' ?
 +                                 'thumbs' : 'outline');
 +        } else if (params.pagemode === 'none' && this.sidebarOpen) {
 +          toggle.click();
 +        }
 +      }
 +    } else if (/^\d+$/.test(hash)) { // page number
 +      this.page = hash;
 +    } else { // named destination
 +      PDFHistory.updateNextHashParam(unescape(hash));
 +      PDFView.navigateTo(unescape(hash));
 +    }
 +  },
 +
 +  switchSidebarView: function pdfViewSwitchSidebarView(view) {
 +    var thumbsView = document.getElementById('thumbnailView');
 +    var outlineView = document.getElementById('outlineView');
 +
 +    var thumbsButton = document.getElementById('viewThumbnail');
 +    var outlineButton = document.getElementById('viewOutline');
 +
 +    switch (view) {
 +      case 'thumbs':
 +        var wasOutlineViewVisible = thumbsView.classList.contains('hidden');
 +
 +        thumbsButton.classList.add('toggled');
 +        outlineButton.classList.remove('toggled');
 +        thumbsView.classList.remove('hidden');
 +        outlineView.classList.add('hidden');
 +
 +        PDFView.renderHighestPriority();
 +
 +        if (wasOutlineViewVisible) {
 +          // Ensure that the thumbnail of the current page is visible
 +          // when switching from the outline view.
 +          scrollIntoView(document.getElementById('thumbnailContainer' +
 +                                                 this.page));
 +        }
 +        break;
 +
 +      case 'outline':
 +        thumbsButton.classList.remove('toggled');
 +        outlineButton.classList.add('toggled');
 +        thumbsView.classList.add('hidden');
 +        outlineView.classList.remove('hidden');
 +
 +        if (outlineButton.getAttribute('disabled'))
 +          return;
 +        break;
 +    }
 +  },
 +
 +  getVisiblePages: function pdfViewGetVisiblePages() {
 +    if (!PresentationMode.active) {
 +      return this.getVisibleElements(this.container, this.pages, true);
 +    } else {
 +      // The algorithm in getVisibleElements doesn't work in all browsers and
 +      // configurations when presentation mode is active.
 +      var visible = [];
 +      var currentPage = this.pages[this.page - 1];
 +      visible.push({ id: currentPage.id, view: currentPage });
 +      return { first: currentPage, last: currentPage, views: visible };
 +    }
 +  },
 +
 +  getVisibleThumbs: function pdfViewGetVisibleThumbs() {
 +    return this.getVisibleElements(this.thumbnailContainer, this.thumbnails);
 +  },
 +
 +  // Generic helper to find out what elements are visible within a scroll pane.
 +  getVisibleElements: function pdfViewGetVisibleElements(
 +      scrollEl, views, sortByVisibility) {
 +    var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
 +    var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
 +
 +    var visible = [], view;
 +    var currentHeight, viewHeight, hiddenHeight, percentHeight;
 +    var currentWidth, viewWidth;
 +    for (var i = 0, ii = views.length; i < ii; ++i) {
 +      view = views[i];
 +      currentHeight = view.el.offsetTop + view.el.clientTop;
 +      viewHeight = view.el.clientHeight;
 +      if ((currentHeight + viewHeight) < top) {
 +        continue;
 +      }
 +      if (currentHeight > bottom) {
 +        break;
 +      }
 +      currentWidth = view.el.offsetLeft + view.el.clientLeft;
 +      viewWidth = view.el.clientWidth;
 +      if ((currentWidth + viewWidth) < left || currentWidth > right) {
 +        continue;
 +      }
 +      hiddenHeight = Math.max(0, top - currentHeight) +
 +                     Math.max(0, currentHeight + viewHeight - bottom);
 +      percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
 +
 +      visible.push({ id: view.id, x: currentWidth, y: currentHeight,
 +                     view: view, percent: percentHeight });
 +    }
 +
 +    var first = visible[0];
 +    var last = visible[visible.length - 1];
 +
 +    if (sortByVisibility) {
 +      visible.sort(function(a, b) {
 +        var pc = a.percent - b.percent;
 +        if (Math.abs(pc) > 0.001) {
 +          return -pc;
 +        }
 +        return a.id - b.id; // ensure stability
 +      });
 +    }
 +    return {first: first, last: last, views: visible};
 +  },
 +
 +  // Helper function to parse query string (e.g. ?param1=value&parm2=...).
 +  parseQueryString: function pdfViewParseQueryString(query) {
 +    var parts = query.split('&');
 +    var params = {};
 +    for (var i = 0, ii = parts.length; i < parts.length; ++i) {
 +      var param = parts[i].split('=');
 +      var key = param[0];
 +      var value = param.length > 1 ? param[1] : null;
 +      params[decodeURIComponent(key)] = decodeURIComponent(value);
 +    }
 +    return params;
 +  },
 +
 +  beforePrint: function pdfViewSetupBeforePrint() {
 +    if (!this.supportsPrinting) {
 +      var printMessage = mozL10n.get('printing_not_supported', null,
 +          'Warning: Printing is not fully supported by this browser.');
 +      this.error(printMessage);
 +      return;
 +    }
 +
 +    var alertNotReady = false;
 +    if (!this.pages.length) {
 +      alertNotReady = true;
 +    } else {
 +      for (var i = 0, ii = this.pages.length; i < ii; ++i) {
 +        if (!this.pages[i].pdfPage) {
 +          alertNotReady = true;
 +          break;
 +        }
 +      }
 +    }
 +    if (alertNotReady) {
 +      var notReadyMessage = mozL10n.get('printing_not_ready', null,
 +          'Warning: The PDF is not fully loaded for printing.');
 +      window.alert(notReadyMessage);
 +      return;
 +    }
 +
 +    var body = document.querySelector('body');
 +    body.setAttribute('data-mozPrintCallback', true);
 +    for (var i = 0, ii = this.pages.length; i < ii; ++i) {
 +      this.pages[i].beforePrint();
 +    }
 +  },
 +
 +  afterPrint: function pdfViewSetupAfterPrint() {
 +    var div = document.getElementById('printContainer');
 +    while (div.hasChildNodes())
 +      div.removeChild(div.lastChild);
 +  },
 +
 +  rotatePages: function pdfViewRotatePages(delta) {
 +    var currentPage = this.pages[this.page - 1];
 +    this.pageRotation = (this.pageRotation + 360 + delta) % 360;
 +
 +    for (var i = 0, l = this.pages.length; i < l; i++) {
 +      var page = this.pages[i];
 +      page.update(page.scale, this.pageRotation);
 +    }
 +
 +    for (var i = 0, l = this.thumbnails.length; i < l; i++) {
 +      var thumb = this.thumbnails[i];
 +      thumb.update(this.pageRotation);
 +    }
 +
 +    this.setScale(this.currentScaleValue, true, true);
 +
 +    this.renderHighestPriority();
 +
 +    if (currentPage) {
 +      currentPage.scrollIntoView();
 +    }
 +  },
 +
 +  /**
 +   * This function flips the page in presentation mode if the user scrolls up
 +   * or down with large enough motion and prevents page flipping too often.
 +   *
 +   * @this {PDFView}
 +   * @param {number} mouseScrollDelta The delta value from the mouse event.
 +   */
 +  mouseScroll: function pdfViewMouseScroll(mouseScrollDelta) {
 +    var MOUSE_SCROLL_COOLDOWN_TIME = 50;
 +
 +    var currentTime = (new Date()).getTime();
 +    var storedTime = this.mouseScrollTimeStamp;
 +
 +    // In case one page has already been flipped there is a cooldown time
 +    // which has to expire before next page can be scrolled on to.
 +    if (currentTime > storedTime &&
 +        currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME)
 +      return;
 +
 +    // In case the user decides to scroll to the opposite direction than before
 +    // clear the accumulated delta.
 +    if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) ||
 +        (this.mouseScrollDelta < 0 && mouseScrollDelta > 0))
 +      this.clearMouseScrollState();
 +
 +    this.mouseScrollDelta += mouseScrollDelta;
 +
 +    var PAGE_FLIP_THRESHOLD = 120;
 +    if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) {
 +
 +      var PageFlipDirection = {
 +        UP: -1,
 +        DOWN: 1
 +      };
 +
 +      // In presentation mode scroll one page at a time.
 +      var pageFlipDirection = (this.mouseScrollDelta > 0) ?
 +                                PageFlipDirection.UP :
 +                                PageFlipDirection.DOWN;
 +      this.clearMouseScrollState();
 +      var currentPage = this.page;
 +
 +      // In case we are already on the first or the last page there is no need
 +      // to do anything.
 +      if ((currentPage == 1 && pageFlipDirection == PageFlipDirection.UP) ||
 +          (currentPage == this.pages.length &&
 +           pageFlipDirection == PageFlipDirection.DOWN))
 +        return;
 +
 +      this.page += pageFlipDirection;
 +      this.mouseScrollTimeStamp = currentTime;
 +    }
 +  },
 +
 +  /**
 +   * This function clears the member attributes used with mouse scrolling in
 +   * presentation mode.
 +   *
 +   * @this {PDFView}
 +   */
 +  clearMouseScrollState: function pdfViewClearMouseScrollState() {
 +    this.mouseScrollTimeStamp = 0;
 +    this.mouseScrollDelta = 0;
 +  }
 +};
 +
 +
 +var PageView = function pageView(container, id, scale,
 +                                 navigateTo, defaultViewport) {
 +  this.id = id;
 +
 +  this.rotation = 0;
 +  this.scale = scale || 1.0;
 +  this.viewport = defaultViewport;
 +  this.pdfPageRotate = defaultViewport.rotation;
 +
 +  this.renderingState = RenderingStates.INITIAL;
 +  this.resume = null;
 +
 +  this.textLayer = null;
 +
 +  this.zoomLayer = null;
 +
 +  this.annotationLayer = null;
 +
 +  var anchor = document.createElement('a');
 +  anchor.name = '' + this.id;
 +
 +  var div = this.el = document.createElement('div');
 +  div.id = 'pageContainer' + this.id;
 +  div.className = 'page';
 +  div.style.width = Math.floor(this.viewport.width) + 'px';
 +  div.style.height = Math.floor(this.viewport.height) + 'px';
 +
 +  container.appendChild(anchor);
 +  container.appendChild(div);
 +
 +  this.setPdfPage = function pageViewSetPdfPage(pdfPage) {
 +    this.pdfPage = pdfPage;
 +    this.pdfPageRotate = pdfPage.rotate;
 +    var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
 +    this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, totalRotation);
 +    this.stats = pdfPage.stats;
 +    this.reset();
 +  };
 +
 +  this.destroy = function pageViewDestroy() {
 +    this.zoomLayer = null;
 +    this.reset();
 +    if (this.pdfPage) {
 +      this.pdfPage.destroy();
 +    }
 +  };
 +
 +  this.reset = function pageViewReset() {
 +    if (this.renderTask) {
 +      this.renderTask.cancel();
 +    }
 +    this.resume = null;
 +    this.renderingState = RenderingStates.INITIAL;
 +
 +    div.style.width = Math.floor(this.viewport.width) + 'px';
 +    div.style.height = Math.floor(this.viewport.height) + 'px';
 +
 +    var childNodes = div.childNodes;
 +    for (var i = div.childNodes.length - 1; i >= 0; i--) {
 +      var node = childNodes[i];
 +      if (this.zoomLayer && this.zoomLayer === node) {
 +        continue;
 +      }
 +      div.removeChild(node);
 +    }
 +    div.removeAttribute('data-loaded');
 +
 +    this.annotationLayer = null;
 +
 +    delete this.canvas;
 +
 +    this.loadingIconDiv = document.createElement('div');
 +    this.loadingIconDiv.className = 'loadingIcon';
 +    div.appendChild(this.loadingIconDiv);
 +  };
 +
 +  this.update = function pageViewUpdate(scale, rotation) {
 +    this.scale = scale || this.scale;
 +
 +    if (typeof rotation !== 'undefined') {
 +      this.rotation = rotation;
 +    }
 +
 +    var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
 +    this.viewport = this.viewport.clone({
 +      scale: this.scale * CSS_UNITS,
 +      rotation: totalRotation
 +    });
 +
 +    if (USE_ONLY_CSS_ZOOM && this.canvas) {
 +      this.cssTransform(this.canvas);
 +      return;
 +    } else if (this.canvas && !this.zoomLayer) {
 +      this.zoomLayer = this.canvas.parentNode;
 +      this.zoomLayer.style.position = 'absolute';
 +    }
 +    if (this.zoomLayer) {
 +      this.cssTransform(this.zoomLayer.firstChild);
 +    }
 +    this.reset();
 +  };
 +
 +  this.cssTransform = function pageCssTransform(canvas) {
 +    // Scale canvas, canvas wrapper, and page container.
 +    var width = this.viewport.width;
 +    var height = this.viewport.height;
 +    canvas.style.width = canvas.parentNode.style.width = div.style.width =
 +        Math.floor(width) + 'px';
 +    canvas.style.height = canvas.parentNode.style.height = div.style.height =
 +        Math.floor(height) + 'px';
 +    // The canvas may have been originally rotated, so rotate relative to that.
 +    var relativeRotation = this.viewport.rotation - canvas._viewport.rotation;
 +    var absRotation = Math.abs(relativeRotation);
 +    var scaleX = 1, scaleY = 1;
 +    if (absRotation === 90 || absRotation === 270) {
 +      // Scale x and y because of the rotation.
 +      scaleX = height / width;
 +      scaleY = width / height;
 +    }
 +    var cssTransform = 'rotate(' + relativeRotation + 'deg) ' +
 +                       'scale(' + scaleX + ',' + scaleY + ')';
 +    CustomStyle.setProp('transform', canvas, cssTransform);
 +
 +    if (this.textLayer) {
 +      // Rotating the text layer is more complicated since the divs inside the
 +      // the text layer are rotated.
 +      // TODO: This could probably be simplified by drawing the text layer in
 +      // one orientation then rotating overall.
 +      var textRelativeRotation = this.viewport.rotation -
 +                                 this.textLayer.viewport.rotation;
 +      var textAbsRotation = Math.abs(textRelativeRotation);
 +      var scale = (width / canvas.width);
 +      if (textAbsRotation === 90 || textAbsRotation === 270) {
 +        scale = width / canvas.height;
 +      }
 +      var textLayerDiv = this.textLayer.textLayerDiv;
 +      var transX, transY;
 +      switch (textAbsRotation) {
 +        case 0:
 +          transX = transY = 0;
 +          break;
 +        case 90:
 +          transX = 0;
 +          transY = '-' + textLayerDiv.style.height;
 +          break;
 +        case 180:
 +          transX = '-' + textLayerDiv.style.width;
 +          transY = '-' + textLayerDiv.style.height;
 +          break;
 +        case 270:
 +          transX = '-' + textLayerDiv.style.width;
 +          transY = 0;
 +          break;
 +        default:
 +          console.error('Bad rotation value.');
 +          break;
 +      }
 +      CustomStyle.setProp('transform', textLayerDiv,
 +                          'rotate(' + textAbsRotation + 'deg) ' +
 +                            'scale(' + scale + ', ' + scale + ') ' +
 +                            'translate(' + transX + ', ' + transY + ')');
 +      CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%');
 +    }
 +
 +    if (USE_ONLY_CSS_ZOOM && this.annotationLayer) {
 +      setupAnnotations(div, this.pdfPage, this.viewport);
 +    }
 +  };
 +
 +  Object.defineProperty(this, 'width', {
 +    get: function PageView_getWidth() {
 +      return this.viewport.width;
 +    },
 +    enumerable: true
 +  });
 +
 +  Object.defineProperty(this, 'height', {
 +    get: function PageView_getHeight() {
 +      return this.viewport.height;
 +    },
 +    enumerable: true
 +  });
 +
 +  var self = this;
 +
 +  function setupAnnotations(pageDiv, pdfPage, viewport) {
 +
 +    function bindLink(link, dest) {
 +      link.href = PDFView.getDestinationHash(dest);
 +      link.onclick = function pageViewSetupLinksOnclick() {
 +        if (dest) {
 +          PDFView.navigateTo(dest);
 +        }
 +        return false;
 +      };
 +      if (dest) {
 +        link.className = 'internalLink';
 +      }
 +    }
 +
 +    function bindNamedAction(link, action) {
 +      link.href = PDFView.getAnchorUrl('');
 +      link.onclick = function pageViewSetupNamedActionOnClick() {
 +        // See PDF reference, table 8.45 - Named action
 +        switch (action) {
 +          case 'GoToPage':
 +            document.getElementById('pageNumber').focus();
 +            break;
 +
 +          case 'GoBack':
 +            PDFHistory.back();
 +            break;
 +
 +          case 'GoForward':
 +            PDFHistory.forward();
 +            break;
 +
 +          case 'Find':
 +            if (!PDFView.supportsIntegratedFind) {
 +              PDFFindBar.toggle();
 +            }
 +            break;
 +
 +          case 'NextPage':
 +            PDFView.page++;
 +            break;
 +
 +          case 'PrevPage':
 +            PDFView.page--;
 +            break;
 +
 +          case 'LastPage':
 +            PDFView.page = PDFView.pages.length;
 +            break;
 +
 +          case 'FirstPage':
 +            PDFView.page = 1;
 +            break;
 +
 +          default:
 +            break; // No action according to spec
 +        }
 +        return false;
 +      };
 +      link.className = 'internalLink';
 +    }
 +
 +    pdfPage.getAnnotations().then(function(annotationsData) {
 +      if (self.annotationLayer) {
 +        // If an annotationLayer already exists, delete it to avoid creating
 +        // duplicate annotations when rapidly re-zooming the document.
 +        pageDiv.removeChild(self.annotationLayer);
 +        self.annotationLayer = null;
 +      }
 +      viewport = viewport.clone({ dontFlip: true });
 +      for (var i = 0; i < annotationsData.length; i++) {
 +        var data = annotationsData[i];
 +        var annotation = PDFJS.Annotation.fromData(data);
 +        if (!annotation || !annotation.hasHtml()) {
 +          continue;
 +        }
 +
 +        var element = annotation.getHtmlElement(pdfPage.commonObjs);
 +        mozL10n.translate(element);
 +
 +        data = annotation.getData();
 +        var rect = data.rect;
 +        var view = pdfPage.view;
 +        rect = PDFJS.Util.normalizeRect([
 +          rect[0],
 +          view[3] - rect[1] + view[1],
 +          rect[2],
 +          view[3] - rect[3] + view[1]
 +        ]);
 +        element.style.left = rect[0] + 'px';
 +        element.style.top = rect[1] + 'px';
 +        element.style.position = 'absolute';
 +
 +        var transform = viewport.transform;
 +        var transformStr = 'matrix(' + transform.join(',') + ')';
 +        CustomStyle.setProp('transform', element, transformStr);
 +        var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px';
 +        CustomStyle.setProp('transformOrigin', element, transformOriginStr);
 +
 +        if (data.subtype === 'Link' && !data.url) {
 +          var link = element.getElementsByTagName('a')[0];
 +          if (link) {
 +            if (data.action) {
 +              bindNamedAction(link, data.action);
 +            } else {
 +              bindLink(link, ('dest' in data) ? data.dest : null);
 +            }
 +          }
 +        }
 +
 +        if (!self.annotationLayer) {
 +          var annotationLayerDiv = document.createElement('div');
 +          annotationLayerDiv.className = 'annotationLayer';
 +          pageDiv.appendChild(annotationLayerDiv);
 +          self.annotationLayer = annotationLayerDiv;
 +        }
 +        self.annotationLayer.appendChild(element);
 +      }
 +    });
 +  }
 +
 +  this.getPagePoint = function pageViewGetPagePoint(x, y) {
 +    return this.viewport.convertToPdfPoint(x, y);
 +  };
 +
 +  this.scrollIntoView = function pageViewScrollIntoView(dest) {
 +    if (PresentationMode.active) {
 +      if (PDFView.page !== this.id) {
 +        // Avoid breaking PDFView.getVisiblePages in presentation mode.
 +        PDFView.page = this.id;
 +        return;
 +      }
 +      dest = null;
 +      PDFView.setScale(PDFView.currentScaleValue, true, true);
 +    }
 +    if (!dest) {
 +      scrollIntoView(div);
 +      return;
 +    }
 +
 +    var x = 0, y = 0;
 +    var width = 0, height = 0, widthScale, heightScale;
 +    var changeOrientation = !!(this.rotation % 180);
 +    var pageWidth = (changeOrientation ? this.height : this.width) /
 +      this.scale / CSS_UNITS;
 +    var pageHeight = (changeOrientation ? this.width : this.height) /
 +      this.scale / CSS_UNITS;
 +    var scale = 0;
 +    switch (dest[1].name) {
 +      case 'XYZ':
 +        x = dest[2];
 +        y = dest[3];
 +        scale = dest[4];
 +        // If x and/or y coordinates are not supplied, default to
 +        // _top_ left of the page (not the obvious bottom left,
 +        // since aligning the bottom of the intended page with the
 +        // top of the window is rarely helpful).
 +        x = x !== null ? x : 0;
 +        y = y !== null ? y : pageHeight;
 +        break;
 +      case 'Fit':
 +      case 'FitB':
 +        scale = 'page-fit';
 +        break;
 +      case 'FitH':
 +      case 'FitBH':
 +        y = dest[2];
 +        scale = 'page-width';
 +        break;
 +      case 'FitV':
 +      case 'FitBV':
 +        x = dest[2];
 +        width = pageWidth;
 +        height = pageHeight;
 +        scale = 'page-height';
 +        break;
 +      case 'FitR':
 +        x = dest[2];
 +        y = dest[3];
 +        width = dest[4] - x;
 +        height = dest[5] - y;
 +        widthScale = (PDFView.container.clientWidth - SCROLLBAR_PADDING) /
 +          width / CSS_UNITS;
 +        heightScale = (PDFView.container.clientHeight - SCROLLBAR_PADDING) /
 +          height / CSS_UNITS;
 +        scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
 +        break;
 +      default:
 +        return;
 +    }
 +
 +    if (scale && scale !== PDFView.currentScale) {
 +      PDFView.setScale(scale, true, true);
 +    } else if (PDFView.currentScale === UNKNOWN_SCALE) {
 +      PDFView.setScale(DEFAULT_SCALE, true, true);
 +    }
 +
 +    if (scale === 'page-fit' && !dest[4]) {
 +      scrollIntoView(div);
 +      return;
 +    }
 +
 +    var boundingRect = [
 +      this.viewport.convertToViewportPoint(x, y),
 +      this.viewport.convertToViewportPoint(x + width, y + height)
 +    ];
 +    var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
 +    var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
 +
 +    scrollIntoView(div, { left: left, top: top });
 +  };
 +
 +  this.getTextContent = function pageviewGetTextContent() {
 +    return PDFView.getPage(this.id).then(function(pdfPage) {
 +      return pdfPage.getTextContent();
 +    });
 +  };
 +
 +  this.draw = function pageviewDraw(callback) {
 +    var pdfPage = this.pdfPage;
 +
 +    if (this.pagePdfPromise) {
 +      return;
 +    }
 +    if (!pdfPage) {
 +      var promise = PDFView.getPage(this.id);
 +      promise.then(function(pdfPage) {
 +        delete this.pagePdfPromise;
 +        this.setPdfPage(pdfPage);
 +        this.draw(callback);
 +      }.bind(this));
 +      this.pagePdfPromise = promise;
 +      return;
 +    }
 +
 +    if (this.renderingState !== RenderingStates.INITIAL) {
 +      console.error('Must be in new state before drawing');
 +    }
 +
 +    this.renderingState = RenderingStates.RUNNING;
 +
 +    var viewport = this.viewport;
 +    // Wrap the canvas so if it has a css transform for highdpi the overflow
 +    // will be hidden in FF.
 +    var canvasWrapper = document.createElement('div');
 +    canvasWrapper.style.width = div.style.width;
 +    canvasWrapper.style.height = div.style.height;
 +    canvasWrapper.classList.add('canvasWrapper');
 +
 +    var canvas = document.createElement('canvas');
 +    canvas.id = 'page' + this.id;
 +    canvasWrapper.appendChild(canvas);
 +    div.appendChild(canvasWrapper);
 +    this.canvas = canvas;
 +
 +    var scale = this.scale;
 +    var ctx = canvas.getContext('2d');
 +    var outputScale = getOutputScale(ctx);
 +
 +    if (USE_ONLY_CSS_ZOOM) {
 +      var actualSizeViewport = viewport.clone({ scale: CSS_UNITS });
 +      // Use a scale that will make the canvas be the original intended size
 +      // of the page.
 +      outputScale.sx *= actualSizeViewport.width / viewport.width;
 +      outputScale.sy *= actualSizeViewport.height / viewport.height;
 +      outputScale.scaled = true;
 +    }
 +
 +    canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0;
 +    canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0;
 +    canvas.style.width = Math.floor(viewport.width) + 'px';
 +    canvas.style.height = Math.floor(viewport.height) + 'px';
 +    // Add the viewport so it's known what it was originally drawn with.
 +    canvas._viewport = viewport;
 +
 +    var textLayerDiv = null;
 +    if (!PDFJS.disableTextLayer) {
 +      textLayerDiv = document.createElement('div');
 +      textLayerDiv.className = 'textLayer';
 +      textLayerDiv.style.width = canvas.width + 'px';
 +      textLayerDiv.style.height = canvas.height + 'px';
 +      div.appendChild(textLayerDiv);
 +    }
 +    var textLayer = this.textLayer =
 +      textLayerDiv ? new TextLayerBuilder({
 +        textLayerDiv: textLayerDiv,
 +        pageIndex: this.id - 1,
 +        lastScrollSource: PDFView,
 +        viewport: this.viewport,
 +        isViewerInPresentationMode: PresentationMode.active
 +      }) : null;
 +    // TODO(mack): use data attributes to store these
 +    ctx._scaleX = outputScale.sx;
 +    ctx._scaleY = outputScale.sy;
 +    if (outputScale.scaled) {
 +      ctx.scale(outputScale.sx, outputScale.sy);
 +    }
 +    if (outputScale.scaled && textLayerDiv) {
 +      var cssScale = 'scale(' + (1 / outputScale.sx) + ', ' +
 +                                (1 / outputScale.sy) + ')';
 +      CustomStyle.setProp('transform' , textLayerDiv, cssScale);
 +      CustomStyle.setProp('transformOrigin' , textLayerDiv, '0% 0%');
 +      textLayerDiv.dataset._scaleX = outputScale.sx;
 +      textLayerDiv.dataset._scaleY = outputScale.sy;
 +    }
 +
 +    // Rendering area
 +
 +    var self = this;
 +    function pageViewDrawCallback(error) {
 +      // The renderTask may have been replaced by a new one, so only remove the
 +      // reference to the renderTask if it matches the one that is triggering
 +      // this callback.
 +      if (renderTask === self.renderTask) {
 +        self.renderTask = null;
 +      }
 +
 +      if (error === 'cancelled') {
 +        return;
 +      }
 +
 +      self.renderingState = RenderingStates.FINISHED;
 +
 +      if (self.loadingIconDiv) {
 +        div.removeChild(self.loadingIconDiv);
 +        delete self.loadingIconDiv;
 +      }
 +
 +      if (self.zoomLayer) {
 +        div.removeChild(self.zoomLayer);
 +        self.zoomLayer = null;
 +      }
 +
 +      if (error) {
 +        PDFView.error(mozL10n.get('rendering_error', null,
 +          'An error occurred while rendering the page.'), error);
 +      }
 +
 +      self.stats = pdfPage.stats;
 +      self.updateStats();
 +      if (self.onAfterDraw) {
 +        self.onAfterDraw();
 +      }
 +
 +      cache.push(self);
 +
 +      var event = document.createEvent('CustomEvent');
 +      event.initCustomEvent('pagerender', true, true, {
 +        pageNumber: pdfPage.pageNumber
 +      });
 +      div.dispatchEvent(event);
 +
 +      callback();
 +    }
 +
 +    var renderContext = {
 +      canvasContext: ctx,
 +      viewport: this.viewport,
 +      textLayer: textLayer,
 +      // intent: 'default', // === 'display'
 +      continueCallback: function pdfViewcContinueCallback(cont) {
 +        if (PDFView.highestPriorityPage !== 'page' + self.id) {
 +          self.renderingState = RenderingStates.PAUSED;
 +          self.resume = function resumeCallback() {
 +            self.renderingState = RenderingStates.RUNNING;
 +            cont();
 +          };
 +          return;
 +        }
 +        cont();
 +      }
 +    };
 +    var renderTask = this.renderTask = this.pdfPage.render(renderContext);
 +
 +    this.renderTask.promise.then(
 +      function pdfPageRenderCallback() {
 +        pageViewDrawCallback(null);
 +      },
 +      function pdfPageRenderError(error) {
 +        pageViewDrawCallback(error);
 +      }
 +    );
 +
 +    if (textLayer) {
 +      this.getTextContent().then(
 +        function textContentResolved(textContent) {
 +          textLayer.setTextContent(textContent);
 +        }
 +      );
 +    }
 +
 +    setupAnnotations(div, pdfPage, this.viewport);
 +    div.setAttribute('data-loaded', true);
 +  };
 +
 +  this.beforePrint = function pageViewBeforePrint() {
 +    var pdfPage = this.pdfPage;
 +
 +    var viewport = pdfPage.getViewport(1);
 +    // Use the same hack we use for high dpi displays for printing to get better
 +    // output until bug 811002 is fixed in FF.
 +    var PRINT_OUTPUT_SCALE = 2;
 +    var canvas = document.createElement('canvas');
 +    canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE;
 +    canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE;
 +    canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt';
 +    canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt';
 +    var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' +
 +                              (1 / PRINT_OUTPUT_SCALE) + ')';
 +    CustomStyle.setProp('transform' , canvas, cssScale);
 +    CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
 +
 +    var printContainer = document.getElementById('printContainer');
 +    var canvasWrapper = document.createElement('div');
 +    canvasWrapper.style.width = viewport.width + 'pt';
 +    canvasWrapper.style.height = viewport.height + 'pt';
 +    canvasWrapper.appendChild(canvas);
 +    printContainer.appendChild(canvasWrapper);
 +
 +    var self = this;
 +    canvas.mozPrintCallback = function(obj) {
 +      var ctx = obj.context;
 +
 +      ctx.save();
 +      ctx.fillStyle = 'rgb(255, 255, 255)';
 +      ctx.fillRect(0, 0, canvas.width, canvas.height);
 +      ctx.restore();
 +      ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE);
 +
 +      var renderContext = {
 +        canvasContext: ctx,
 +        viewport: viewport,
 +        intent: 'print'
 +      };
 +
 +      pdfPage.render(renderContext).promise.then(function() {
 +        // Tell the printEngine that rendering this canvas/page has finished.
 +        obj.done();
 +      }, function(error) {
 +        console.error(error);
 +        // Tell the printEngine that rendering this canvas/page has failed.
 +        // This will make the print proces stop.
 +        if ('abort' in obj) {
 +          obj.abort();
 +        } else {
 +          obj.done();
 +        }
 +      });
 +    };
 +  };
 +
 +  this.updateStats = function pageViewUpdateStats() {
 +    if (!this.stats) {
 +      return;
 +    }
 +
 +    if (PDFJS.pdfBug && Stats.enabled) {
 +      var stats = this.stats;
 +      Stats.add(this.id, stats);
 +    }
 +  };
 +};
 +
 +
 +var ThumbnailView = function thumbnailView(container, id, defaultViewport) {
 +  var anchor = document.createElement('a');
 +  anchor.href = PDFView.getAnchorUrl('#page=' + id);
 +  anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}');
 +  anchor.onclick = function stopNavigation() {
 +    PDFView.page = id;
 +    return false;
 +  };
 +
 +  this.pdfPage = undefined;
 +  this.viewport = defaultViewport;
 +  this.pdfPageRotate = defaultViewport.rotation;
 +
 +  this.rotation = 0;
 +  this.pageWidth = this.viewport.width;
 +  this.pageHeight = this.viewport.height;
 +  this.pageRatio = this.pageWidth / this.pageHeight;
 +  this.id = id;
 +
 +  this.canvasWidth = 98;
 +  this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
 +  this.scale = (this.canvasWidth / this.pageWidth);
 +
 +  var div = this.el = document.createElement('div');
 +  div.id = 'thumbnailContainer' + id;
 +  div.className = 'thumbnail';
 +
 +  if (id === 1) {
 +    // Highlight the thumbnail of the first page when no page number is
 +    // specified (or exists in cache) when the document is loaded.
 +    div.classList.add('selected');
 +  }
 +
 +  var ring = document.createElement('div');
 +  ring.className = 'thumbnailSelectionRing';
 +  ring.style.width = this.canvasWidth + 'px';
 +  ring.style.height = this.canvasHeight + 'px';
 +
 +  div.appendChild(ring);
 +  anchor.appendChild(div);
 +  container.appendChild(anchor);
 +
 +  this.hasImage = false;
 +  this.renderingState = RenderingStates.INITIAL;
 +
 +  this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) {
 +    this.pdfPage = pdfPage;
 +    this.pdfPageRotate = pdfPage.rotate;
 +    var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
 +    this.viewport = pdfPage.getViewport(1, totalRotation);
 +    this.update();
 +  };
 +
 +  this.update = function thumbnailViewUpdate(rotation) {
 +    if (rotation !== undefined) {
 +      this.rotation = rotation;
 +    }
 +    var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
 +    this.viewport = this.viewport.clone({
 +      scale: 1,
 +      rotation: totalRotation
 +    });
 +    this.pageWidth = this.viewport.width;
 +    this.pageHeight = this.viewport.height;
 +    this.pageRatio = this.pageWidth / this.pageHeight;
 +
 +    this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
 +    this.scale = (this.canvasWidth / this.pageWidth);
 +
 +    div.removeAttribute('data-loaded');
 +    ring.textContent = '';
 +    ring.style.width = this.canvasWidth + 'px';
 +    ring.style.height = this.canvasHeight + 'px';
 +
 +    this.hasImage = false;
 +    this.renderingState = RenderingStates.INITIAL;
 +    this.resume = null;
 +  };
 +
 +  this.getPageDrawContext = function thumbnailViewGetPageDrawContext() {
 +    var canvas = document.createElement('canvas');
 +    canvas.id = 'thumbnail' + id;
 +
 +    canvas.width = this.canvasWidth;
 +    canvas.height = this.canvasHeight;
 +    canvas.className = 'thumbnailImage';
 +    canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
 +      {page: id}, 'Thumbnail of Page {{page}}'));
 +
 +    div.setAttribute('data-loaded', true);
 +
 +    ring.appendChild(canvas);
 +
 +    var ctx = canvas.getContext('2d');
 +    ctx.save();
 +    ctx.fillStyle = 'rgb(255, 255, 255)';
 +    ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
 +    ctx.restore();
 +    return ctx;
 +  };
 +
 +  this.drawingRequired = function thumbnailViewDrawingRequired() {
 +    return !this.hasImage;
 +  };
 +
 +  this.draw = function thumbnailViewDraw(callback) {
 +    if (!this.pdfPage) {
 +      var promise = PDFView.getPage(this.id);
 +      promise.then(function(pdfPage) {
 +        this.setPdfPage(pdfPage);
 +        this.draw(callback);
 +      }.bind(this));
 +      return;
 +    }
 +
 +    if (this.renderingState !== RenderingStates.INITIAL) {
 +      console.error('Must be in new state before drawing');
 +    }
 +
 +    this.renderingState = RenderingStates.RUNNING;
 +    if (this.hasImage) {
 +      callback();
 +      return;
 +    }
 +
 +    var self = this;
 +    var ctx = this.getPageDrawContext();
 +    var drawViewport = this.viewport.clone({ scale: this.scale });
 +    var renderContext = {
 +      canvasContext: ctx,
 +      viewport: drawViewport,
 +      continueCallback: function(cont) {
 +        if (PDFView.highestPriorityPage !== 'thumbnail' + self.id) {
 +          self.renderingState = RenderingStates.PAUSED;
 +          self.resume = function() {
 +            self.renderingState = RenderingStates.RUNNING;
 +            cont();
 +          };
 +          return;
 +        }
 +        cont();
 +      }
 +    };
 +    this.pdfPage.render(renderContext).promise.then(
 +      function pdfPageRenderCallback() {
 +        self.renderingState = RenderingStates.FINISHED;
 +        callback();
 +      },
 +      function pdfPageRenderError(error) {
 +        self.renderingState = RenderingStates.FINISHED;
 +        callback();
 +      }
 +    );
 +    this.hasImage = true;
 +  };
 +
 +  this.setImage = function thumbnailViewSetImage(img) {
 +    if (!this.pdfPage) {
 +      var promise = PDFView.getPage(this.id);
 +      promise.then(function(pdfPage) {
 +        this.setPdfPage(pdfPage);
 +        this.setImage(img);
 +      }.bind(this));
 +      return;
 +    }
 +    if (this.hasImage || !img) {
 +      return;
 +    }
 +    this.renderingState = RenderingStates.FINISHED;
 +    var ctx = this.getPageDrawContext();
 +    ctx.drawImage(img, 0, 0, img.width, img.height,
 +                  0, 0, ctx.canvas.width, ctx.canvas.height);
 +
 +    this.hasImage = true;
 +  };
 +};
 +
 +
 +var FIND_SCROLL_OFFSET_TOP = -50;
 +var FIND_SCROLL_OFFSET_LEFT = -400;
 +
 +/**
 + * TextLayerBuilder provides text-selection
 + * functionality for the PDF. It does this
 + * by creating overlay divs over the PDF
 + * text. This divs contain text that matches
 + * the PDF text they are overlaying. This
 + * object also provides for a way to highlight
 + * text that is being searched for.
 + */
 +var TextLayerBuilder = function textLayerBuilder(options) {
 +  var textLayerFrag = document.createDocumentFragment();
 +
 +  this.textLayerDiv = options.textLayerDiv;
 +  this.layoutDone = false;
 +  this.divContentDone = false;
 +  this.pageIdx = options.pageIndex;
 +  this.matches = [];
 +  this.lastScrollSource = options.lastScrollSource;
 +  this.viewport = options.viewport;
 +  this.isViewerInPresentationMode = options.isViewerInPresentationMode;
 +
 +  if(typeof PDFFindController === 'undefined') {
 +      window.PDFFindController = null;
 +  }
 +
 +  if(typeof this.lastScrollSource === 'undefined') {
 +      this.lastScrollSource = null;
 +  }
 +
 +  this.beginLayout = function textLayerBuilderBeginLayout() {
 +    this.textDivs = [];
 +    this.renderingDone = false;
 +  };
 +
 +  this.endLayout = function textLayerBuilderEndLayout() {
 +    this.layoutDone = true;
 +    this.insertDivContent();
 +  };
 +
 +  this.renderLayer = function textLayerBuilderRenderLayer() {
 +    var textDivs = this.textDivs;
 +    var canvas = document.createElement('canvas');
 +    var ctx = canvas.getContext('2d');
 +
 +    // No point in rendering so many divs as it'd make the browser unusable
 +    // even after the divs are rendered
 +    var MAX_TEXT_DIVS_TO_RENDER = 100000;
 +    if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER)
 +      return;
 +
 +    for (var i = 0, ii = textDivs.length; i < ii; i++) {
 +      var textDiv = textDivs[i];
 +      if ('isWhitespace' in textDiv.dataset) {
 +        continue;
 +      }
 +
 +      ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily;
 +      var width = ctx.measureText(textDiv.textContent).width;
 +
 +      if (width > 0) {
 +        textLayerFrag.appendChild(textDiv);
 +        var textScale = textDiv.dataset.canvasWidth / width;
 +        var rotation = textDiv.dataset.angle;
 +        var transform = 'scale(' + textScale + ', 1)';
 +        transform = 'rotate(' + rotation + 'deg) ' + transform;
 +        CustomStyle.setProp('transform' , textDiv, transform);
 +        CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%');
 +      }
 +    }
 +
 +    this.textLayerDiv.appendChild(textLayerFrag);
 +    this.renderingDone = true;
 +    this.updateMatches();
 +  };
 +
 +  this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() {
 +    // Schedule renderLayout() if user has been scrolling, otherwise
 +    // run it right away
 +    var RENDER_DELAY = 200; // in ms
 +    var self = this;
 +    var lastScroll = this.lastScrollSource === null ?
 +        0 : this.lastScrollSource.lastScroll;
 +
 +    if (Date.now() - lastScroll > RENDER_DELAY) {
 +      // Render right away
 +      this.renderLayer();
 +    } else {
 +      // Schedule
 +      if (this.renderTimer)
 +        clearTimeout(this.renderTimer);
 +      this.renderTimer = setTimeout(function() {
 +        self.setupRenderLayoutTimer();
 +      }, RENDER_DELAY);
 +    }
 +  };
 +
 +  this.appendText = function textLayerBuilderAppendText(geom) {
 +    var textDiv = document.createElement('div');
 +
 +    // vScale and hScale already contain the scaling to pixel units
 +    var fontHeight = geom.fontSize * Math.abs(geom.vScale);
 +    textDiv.dataset.canvasWidth = geom.canvasWidth * Math.abs(geom.hScale);
 +    textDiv.dataset.fontName = geom.fontName;
 +    textDiv.dataset.angle = geom.angle * (180 / Math.PI);
 +
 +    textDiv.style.fontSize = fontHeight + 'px';
 +    textDiv.style.fontFamily = geom.fontFamily;
 +    var fontAscent = geom.ascent ? geom.ascent * fontHeight :
 +      geom.descent ? (1 + geom.descent) * fontHeight : fontHeight;
 +    textDiv.style.left = (geom.x + (fontAscent * Math.sin(geom.angle))) + 'px';
 +    textDiv.style.top = (geom.y - (fontAscent * Math.cos(geom.angle))) + 'px';
 +
 +    // The content of the div is set in the `setTextContent` function.
 +
 +    this.textDivs.push(textDiv);
 +  };
 +
 +  this.insertDivContent = function textLayerUpdateTextContent() {
 +    // Only set the content of the divs once layout has finished, the content
 +    // for the divs is available and content is not yet set on the divs.
 +    if (!this.layoutDone || this.divContentDone || !this.textContent)
 +      return;
 +
 +    this.divContentDone = true;
 +
 +    var textDivs = this.textDivs;
 +    var bidiTexts = this.textContent;
 +
 +    for (var i = 0; i < bidiTexts.length; i++) {
 +      var bidiText = bidiTexts[i];
 +      var textDiv = textDivs[i];
 +      if (!/\S/.test(bidiText.str)) {
 +        textDiv.dataset.isWhitespace = true;
 +        continue;
 +      }
 +
 +      textDiv.textContent = bidiText.str;
 +      // TODO refactor text layer to use text content position
 +      /**
 +       * var arr = this.viewport.convertToViewportPoint(bidiText.x, bidiText.y);
 +       * textDiv.style.left = arr[0] + 'px';
 +       * textDiv.style.top = arr[1] + 'px';
 +       */
 +      // bidiText.dir may be 'ttb' for vertical texts.
 +      textDiv.dir = bidiText.dir;
 +    }
 +
 +    this.setupRenderLayoutTimer();
 +  };
 +
 +  this.setTextContent = function textLayerBuilderSetTextContent(textContent) {
 +    this.textContent = textContent;
 +    this.insertDivContent();
 +  };
 +
 +  this.convertMatches = function textLayerBuilderConvertMatches(matches) {
 +    var i = 0;
 +    var iIndex = 0;
 +    var bidiTexts = this.textContent;
 +    var end = bidiTexts.length - 1;
 +    var queryLen = PDFFindController === null ?
 +        0 : PDFFindController.state.query.length;
 +
 +    var lastDivIdx = -1;
 +    var pos;
 +
 +    var ret = [];
 +
 +    // Loop over all the matches.
 +    for (var m = 0; m < matches.length; m++) {
 +      var matchIdx = matches[m];
 +      // # Calculate the begin position.
 +
 +      // Loop over the divIdxs.
 +      while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) {
 +        iIndex += bidiTexts[i].str.length;
 +        i++;
 +      }
 +
 +      // TODO: Do proper handling here if something goes wrong.
 +      if (i == bidiTexts.length) {
 +        console.error('Could not find matching mapping');
 +      }
 +
 +      var match = {
 +        begin: {
 +          divIdx: i,
 +          offset: matchIdx - iIndex
 +        }
 +      };
 +
 +      // # Calculate the end position.
 +      matchIdx += queryLen;
 +
 +      // Somewhat same array as above, but use a > instead of >= to get the end
 +      // position right.
 +      while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) {
 +        iIndex += bidiTexts[i].str.length;
 +        i++;
 +      }
 +
 +      match.end = {
 +        divIdx: i,
 +        offset: matchIdx - iIndex
 +      };
 +      ret.push(match);
 +    }
 +
 +    return ret;
 +  };
 +
 +  this.renderMatches = function textLayerBuilder_renderMatches(matches) {
 +    // Early exit if there is nothing to render.
 +    if (matches.length === 0) {
 +      return;
 +    }
 +
 +    var bidiTexts = this.textContent;
 +    var textDivs = this.textDivs;
 +    var prevEnd = null;
 +    var isSelectedPage = PDFFindController === null ?
 +        false : (this.pageIdx === PDFFindController.selected.pageIdx);
 +
 +    var selectedMatchIdx = PDFFindController === null ?
 +        -1 : PDFFindController.selected.matchIdx;
 +
 +    var highlightAll = PDFFindController === null ?
 +        false : PDFFindController.state.highlightAll;
 +
 +    var infty = {
 +      divIdx: -1,
 +      offset: undefined
 +    };
 +
 +    function beginText(begin, className) {
 +      var divIdx = begin.divIdx;
 +      var div = textDivs[divIdx];
 +      div.textContent = '';
 +
 +      var content = bidiTexts[divIdx].str.substring(0, begin.offset);
 +      var node = document.createTextNode(content);
 +      if (className) {
 +        var isSelected = isSelectedPage &&
 +                          divIdx === selectedMatchIdx;
 +        var span = document.createElement('span');
 +        span.className = className + (isSelected ? ' selected' : '');
 +        span.appendChild(node);
 +        div.appendChild(span);
 +        return;
 +      }
 +      div.appendChild(node);
 +    }
 +
 +    function appendText(from, to, className) {
 +      var divIdx = from.divIdx;
 +      var div = textDivs[divIdx];
 +
 +      var content = bidiTexts[divIdx].str.substring(from.offset, to.offset);
 +      var node = document.createTextNode(content);
 +      if (className) {
 +        var span = document.createElement('span');
 +        span.className = className;
 +        span.appendChild(node);
 +        div.appendChild(span);
 +        return;
 +      }
 +      div.appendChild(node);
 +    }
 +
 +    function highlightDiv(divIdx, className) {
 +      textDivs[divIdx].className = className;
 +    }
 +
 +    var i0 = selectedMatchIdx, i1 = i0 + 1, i;
 +
 +    if (highlightAll) {
 +      i0 = 0;
 +      i1 = matches.length;
 +    } else if (!isSelectedPage) {
 +      // Not highlighting all and this isn't the selected page, so do nothing.
 +      return;
 +    }
 +
 +    for (i = i0; i < i1; i++) {
 +      var match = matches[i];
 +      var begin = match.begin;
 +      var end = match.end;
 +
 +      var isSelected = isSelectedPage && i === selectedMatchIdx;
 +      var highlightSuffix = (isSelected ? ' selected' : '');
 +      if (isSelected && !this.isViewerInPresentationMode) {
 +        scrollIntoView(textDivs[begin.divIdx], { top: FIND_SCROLL_OFFSET_TOP,
 +                                               left: FIND_SCROLL_OFFSET_LEFT });
 +      }
 +
 +      // Match inside new div.
 +      if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
 +        // If there was a previous div, then add the text at the end
 +        if (prevEnd !== null) {
 +          appendText(prevEnd, infty);
 +        }
 +        // clears the divs and set the content until the begin point.
 +        beginText(begin);
 +      } else {
 +        appendText(prevEnd, begin);
 +      }
 +
 +      if (begin.divIdx === end.divIdx) {
 +        appendText(begin, end, 'highlight' + highlightSuffix);
 +      } else {
 +        appendText(begin, infty, 'highlight begin' + highlightSuffix);
 +        for (var n = begin.divIdx + 1; n < end.divIdx; n++) {
 +          highlightDiv(n, 'highlight middle' + highlightSuffix);
 +        }
 +        beginText(end, 'highlight end' + highlightSuffix);
 +      }
 +      prevEnd = end;
 +    }
 +
 +    if (prevEnd) {
 +      appendText(prevEnd, infty);
 +    }
 +  };
 +
 +  this.updateMatches = function textLayerUpdateMatches() {
 +    // Only show matches, once all rendering is done.
 +    if (!this.renderingDone)
 +      return;
 +
 +    // Clear out all matches.
 +    var matches = this.matches;
 +    var textDivs = this.textDivs;
 +    var bidiTexts = this.textContent;
 +    var clearedUntilDivIdx = -1;
 +
 +    // Clear out all current matches.
 +    for (var i = 0; i < matches.length; i++) {
 +      var match = matches[i];
 +      var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
 +      for (var n = begin; n <= match.end.divIdx; n++) {
 +        var div = textDivs[n];
 +        div.textContent = bidiTexts[n].str;
 +        div.className = '';
 +      }
 +      clearedUntilDivIdx = match.end.divIdx + 1;
 +    }
 +
 +    if (PDFFindController === null || !PDFFindController.active)
 +      return;
 +
 +    // Convert the matches on the page controller into the match format used
 +    // for the textLayer.
 +    this.matches = matches =
 +      this.convertMatches(PDFFindController === null ?
 +          [] : (PDFFindController.pageMatches[this.pageIdx] || []));
 +
 +    this.renderMatches(this.matches);
 +  };
 +};
 +
 +
 +
 +var DocumentOutlineView = function documentOutlineView(outline) {
 +  var outlineView = document.getElementById('outlineView');
 +  var outlineButton = document.getElementById('viewOutline');
 +  while (outlineView.firstChild)
 +    outlineView.removeChild(outlineView.firstChild);
 +
 +  if (!outline) {
 +    if (!outlineView.classList.contains('hidden'))
 +      PDFView.switchSidebarView('thumbs');
 +
 +    return;
 +  }
 +
 +  function bindItemLink(domObj, item) {
 +    domObj.href = PDFView.getDestinationHash(item.dest);
 +    domObj.onclick = function documentOutlineViewOnclick(e) {
 +      PDFView.navigateTo(item.dest);
 +      return false;
 +    };
 +  }
 +
 +
 +  var queue = [{parent: outlineView, items: outline}];
 +  while (queue.length > 0) {
 +    var levelData = queue.shift();
 +    var i, n = levelData.items.length;
 +    for (i = 0; i < n; i++) {
 +      var item = levelData.items[i];
 +      var div = document.createElement('div');
 +      div.className = 'outlineItem';
 +      var a = document.createElement('a');
 +      bindItemLink(a, item);
 +      a.textContent = item.title;
 +      div.appendChild(a);
 +
 +      if (item.items.length > 0) {
 +        var itemsDiv = document.createElement('div');
 +        itemsDiv.className = 'outlineItems';
 +        div.appendChild(itemsDiv);
 +        queue.push({parent: itemsDiv, items: item.items});
 +      }
 +
 +      levelData.parent.appendChild(div);
 +    }
 +  }
 +};
 +
 +
 +function webViewerLoad(evt) {
 +  PDFView.initialize();
 +
 +  var params = PDFView.parseQueryString(document.location.search.substring(1));
 +  var file = 'file' in params ? params.file : DEFAULT_URL;
 +
 +  var fileInput = document.createElement('input');
 +  fileInput.id = 'fileInput';
 +  fileInput.className = 'fileInput';
 +  fileInput.setAttribute('type', 'file');
 +  fileInput.oncontextmenu = noContextMenuHandler;
 +  document.body.appendChild(fileInput);
 +
 +  if (!window.File || !window.FileReader || !window.FileList || !window.Blob) {
 +    document.getElementById('openFile').setAttribute('hidden', 'true');
 +    document.getElementById('secondaryOpenFile').setAttribute('hidden', 'true');
 +  } else {
 +    document.getElementById('fileInput').value = null;
 +  }
 +
 +  // Special debugging flags in the hash section of the URL.
 +  var hash = document.location.hash.substring(1);
 +  var hashParams = PDFView.parseQueryString(hash);
 +
 +  if ('disableWorker' in hashParams) {
 +    PDFJS.disableWorker = (hashParams['disableWorker'] === 'true');
 +  }
 +
 +  if ('disableRange' in hashParams) {
 +    PDFJS.disableRange = (hashParams['disableRange'] === 'true');
 +  }
 +
 +  if ('disableAutoFetch' in hashParams) {
 +    PDFJS.disableAutoFetch = (hashParams['disableAutoFetch'] === 'true');
 +  }
 +
 +  if ('disableFontFace' in hashParams) {
 +    PDFJS.disableFontFace = (hashParams['disableFontFace'] === 'true');
 +  }
 +
 +  if ('disableHistory' in hashParams) {
 +    PDFJS.disableHistory = (hashParams['disableHistory'] === 'true');
 +  }
 +
 +  if ('useOnlyCssZoom' in hashParams) {
 +    USE_ONLY_CSS_ZOOM = (hashParams['useOnlyCssZoom'] === 'true');
 +  }
 +
 +  if ('verbosity' in hashParams) {
 +    PDFJS.verbosity = hashParams['verbosity'] | 0;
 +  }
 +
 +  if ('ignoreCurrentPositionOnZoom' in hashParams) {
 +    IGNORE_CURRENT_POSITION_ON_ZOOM =
 +      (hashParams['ignoreCurrentPositionOnZoom'] === 'true');
 +  }
 +
 +  var locale = PDFJS.locale || navigator.language;
 +  if ('locale' in hashParams)
 +    locale = hashParams['locale'];
 +  mozL10n.setLanguage(locale);
 +
 +  if ('textLayer' in hashParams) {
 +    switch (hashParams['textLayer']) {
 +      case 'off':
 +        PDFJS.disableTextLayer = true;
 +        break;
 +      case 'visible':
 +      case 'shadow':
 +      case 'hover':
 +        var viewer = document.getElementById('viewer');
 +        viewer.classList.add('textLayer-' + hashParams['textLayer']);
 +        break;
 +    }
 +  }
 +
 +  if ('pdfBug' in hashParams) {
 +    PDFJS.pdfBug = true;
 +    var pdfBug = hashParams['pdfBug'];
 +    var enabled = pdfBug.split(',');
 +    PDFBug.enable(enabled);
 +    PDFBug.init();
 +  }
 +
 +  if (!PDFView.supportsPrinting) {
 +    document.getElementById('print').classList.add('hidden');
 +    document.getElementById('secondaryPrint').classList.add('hidden');
 +  }
 +
 +  if (!PDFView.supportsFullscreen) {
 +    document.getElementById('presentationMode').classList.add('hidden');
 +    document.getElementById('secondaryPresentationMode').
 +      classList.add('hidden');
 +  }
 +
 +  if (PDFView.supportsIntegratedFind) {
 +    document.getElementById('viewFind').classList.add('hidden');
 +  }
 +
 +  // Listen for unsuporrted features to trigger the fallback UI.
 +  PDFJS.UnsupportedManager.listen(PDFView.fallback.bind(PDFView));
 +
 +  // Suppress context menus for some controls
 +  document.getElementById('scaleSelect').oncontextmenu = noContextMenuHandler;
 +
 +  var mainContainer = document.getElementById('mainContainer');
 +  var outerContainer = document.getElementById('outerContainer');
 +  mainContainer.addEventListener('transitionend', function(e) {
 +    if (e.target == mainContainer) {
 +      var event = document.createEvent('UIEvents');
 +      event.initUIEvent('resize', false, false, window, 0);
 +      window.dispatchEvent(event);
 +      outerContainer.classList.remove('sidebarMoving');
 +    }
 +  }, true);
 +
 +  document.getElementById('sidebarToggle').addEventListener('click',
 +    function() {
 +      this.classList.toggle('toggled');
 +      outerContainer.classList.add('sidebarMoving');
 +      outerContainer.classList.toggle('sidebarOpen');
 +      PDFView.sidebarOpen = outerContainer.classList.contains('sidebarOpen');
 +      PDFView.renderHighestPriority();
 +    });
 +
 +  document.getElementById('viewThumbnail').addEventListener('click',
 +    function() {
 +      PDFView.switchSidebarView('thumbs');
 +    });
 +
 +  document.getElementById('viewOutline').addEventListener('click',
 +    function() {
 +      PDFView.switchSidebarView('outline');
 +    });
 +
 +  document.getElementById('previous').addEventListener('click',
 +    function() {
 +      PDFView.page--;
 +    });
 +
 +  document.getElementById('next').addEventListener('click',
 +    function() {
 +      PDFView.page++;
 +    });
 +
 +  document.getElementById('zoomIn').addEventListener('click',
 +    function() {
 +      PDFView.zoomIn();
 +    });
 +
 +  document.getElementById('zoomOut').addEventListener('click',
 +    function() {
 +      PDFView.zoomOut();
 +    });
 +
 +  document.getElementById('pageNumber').addEventListener('click',
 +    function() {
 +      this.select();
 +    });
 +
 +  document.getElementById('pageNumber').addEventListener('change',
 +    function() {
 +      // Handle the user inputting a floating point number.
 +      PDFView.page = (this.value | 0);
 +
 +      if (this.value !== (this.value | 0).toString()) {
 +        this.value = PDFView.page;
 +      }
 +    });
 +
 +  document.getElementById('scaleSelect').addEventListener('change',
 +    function() {
 +      PDFView.setScale(this.value);
 +    });
 +
 +  document.getElementById('presentationMode').addEventListener('click',
 +    SecondaryToolbar.presentationModeClick.bind(SecondaryToolbar));
 +
 +  document.getElementById('print').addEventListener('click',
 +    SecondaryToolbar.printClick.bind(SecondaryToolbar));
 +
 +  document.getElementById('download').addEventListener('click',
 +    SecondaryToolbar.downloadClick.bind(SecondaryToolbar));
 +
 +
 +  // owncould customization to load file
 +  if (file && dir !== '') {
-   	// Logged in
-   	PDFView.open(OC.linkTo('files', 'ajax/download.php')+"?files="+decodeURIComponent(window.file)+"&dir="+decodeURIComponent(window.dir), 1.0);
++  	// Logged in       
++  	PDFView.open(OC.linkTo('files', 'ajax/download.php')+"?files="+window.file+"&dir="+window.dir, 1.0);
 +  } else {
 +  	// Public view
-   	PDFView.open(OC.linkTo('', 'public.php')+'?service=files'+"&t="+decodeURIComponent(window.file)+"&download", 1.0);
++  	PDFView.open(OC.linkTo('', 'public.php')+'?service=files'+"&t="+window.file+"&download", 1.0);
 +  }
 +
 +}
 +
 +document.addEventListener('DOMContentLoaded', webViewerLoad, true);
 +
 +function updateViewarea() {
 +
 +  if (!PDFView.initialized)
 +    return;
 +  var visible = PDFView.getVisiblePages();
 +  var visiblePages = visible.views;
 +  if (visiblePages.length === 0) {
 +    return;
 +  }
 +
 +  PDFView.renderHighestPriority();
 +
 +  var currentId = PDFView.page;
 +  var firstPage = visible.first;
 +
 +  for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
 +       i < ii; ++i) {
 +    var page = visiblePages[i];
 +
 +    if (page.percent < 100)
 +      break;
 +
 +    if (page.id === PDFView.page) {
 +      stillFullyVisible = true;
 +      break;
 +    }
 +  }
 +
 +  if (!stillFullyVisible) {
 +    currentId = visiblePages[0].id;
 +  }
 +
 +  if (!PresentationMode.active) {
 +    updateViewarea.inProgress = true; // used in "set page"
 +    PDFView.page = currentId;
 +    updateViewarea.inProgress = false;
 +  }
 +
 +  var currentScale = PDFView.currentScale;
 +  var currentScaleValue = PDFView.currentScaleValue;
 +  var normalizedScaleValue = parseFloat(currentScaleValue) === currentScale ?
 +    Math.round(currentScale * 10000) / 100 : currentScaleValue;
 +
 +  var pageNumber = firstPage.id;
 +  var pdfOpenParams = '#page=' + pageNumber;
 +  pdfOpenParams += '&zoom=' + normalizedScaleValue;
 +  var currentPage = PDFView.pages[pageNumber - 1];
 +  var container = PDFView.container;
 +  var topLeft = currentPage.getPagePoint((container.scrollLeft - firstPage.x),
 +                                         (container.scrollTop - firstPage.y));
 +  var intLeft = Math.round(topLeft[0]);
 +  var intTop = Math.round(topLeft[1]);
 +  pdfOpenParams += ',' + intLeft + ',' + intTop;
 +
 +  if (PresentationMode.active || PresentationMode.switchInProgress) {
 +    PDFView.currentPosition = null;
 +  } else {
 +    PDFView.currentPosition = { page: pageNumber, left: intLeft, top: intTop };
 +  }
 +
 +  var store = PDFView.store;
 +  store.initializedPromise.then(function() {
 +    store.set('exists', true);
 +    store.set('page', pageNumber);
 +    store.set('zoom', normalizedScaleValue);
 +    store.set('scrollLeft', intLeft);
 +    store.set('scrollTop', intTop);
 +  });
 +  var href = PDFView.getAnchorUrl(pdfOpenParams);
 +  document.getElementById('viewBookmark').href = href;
 +  document.getElementById('secondaryViewBookmark').href = href;
 +
 +  // Update the current bookmark in the browsing history.
 +  PDFHistory.updateCurrentBookmark(pdfOpenParams, pageNumber);
 +}
 +
 +window.addEventListener('resize', function webViewerResize(evt) {
 +  if (PDFView.initialized &&
 +      (document.getElementById('pageWidthOption').selected ||
 +       document.getElementById('pageFitOption').selected ||
 +       document.getElementById('pageAutoOption').selected)) {
 +    PDFView.setScale(document.getElementById('scaleSelect').value);
 +  }
 +  updateViewarea();
 +
 +  // Set the 'max-height' CSS property of the secondary toolbar.
 +  SecondaryToolbar.setMaxHeight(PDFView.container);
 +});
 +
 +window.addEventListener('hashchange', function webViewerHashchange(evt) {
 +  if (PDFHistory.isHashChangeUnlocked) {
 +    PDFView.setHash(document.location.hash.substring(1));
 +  }
 +});
 +
 +window.addEventListener('change', function webViewerChange(evt) {
 +  var files = evt.target.files;
 +  if (!files || files.length === 0)
 +    return;
 +
 +  var file = files[0];
 +
 +  if (!PDFJS.disableCreateObjectURL &&
 +      typeof URL !== 'undefined' && URL.createObjectURL) {
 +    PDFView.open(URL.createObjectURL(file), 0);
 +  } else {
 +    // Read the local file into a Uint8Array.
 +    var fileReader = new FileReader();
 +    fileReader.onload = function webViewerChangeFileReaderOnload(evt) {
 +      var buffer = evt.target.result;
 +      var uint8Array = new Uint8Array(buffer);
 +      PDFView.open(uint8Array, 0);
 +    };
 +    fileReader.readAsArrayBuffer(file);
 +  }
 +
 +  PDFView.setTitleUsingUrl(file.name);
 +
 +  // URL does not reflect proper document location - hiding some icons.
 +  document.getElementById('viewBookmark').setAttribute('hidden', 'true');
 +  document.getElementById('secondaryViewBookmark').
 +    setAttribute('hidden', 'true');
 +  document.getElementById('download').setAttribute('hidden', 'true');
 +  document.getElementById('secondaryDownload').setAttribute('hidden', 'true');
 +}, true);
 +
 +function selectScaleOption(value) {
 +  var options = document.getElementById('scaleSelect').options;
 +  var predefinedValueFound = false;
 +  for (var i = 0; i < options.length; i++) {
 +    var option = options[i];
 +    if (option.value != value) {
 +      option.selected = false;
 +      continue;
 +    }
 +    option.selected = true;
 +    predefinedValueFound = true;
 +  }
 +  return predefinedValueFound;
 +}
 +
 +window.addEventListener('localized', function localized(evt) {
 +  document.getElementsByTagName('html')[0].dir = mozL10n.getDirection();
 +
 +  PDFView.animationStartedPromise.then(function() {
 +    // Adjust the width of the zoom box to fit the content.
 +    // Note: This is only done if the zoom box is actually visible,
 +    // since otherwise element.clientWidth will return 0.
 +    var container = document.getElementById('scaleSelectContainer');
 +    if (container.clientWidth > 0) {
 +      var select = document.getElementById('scaleSelect');
 +      select.setAttribute('style', 'min-width: inherit;');
 +      var width = select.clientWidth + SCALE_SELECT_CONTAINER_PADDING;
 +      select.setAttribute('style', 'min-width: ' +
 +                                   (width + SCALE_SELECT_PADDING) + 'px;');
 +      container.setAttribute('style', 'min-width: ' + width + 'px; ' +
 +                                      'max-width: ' + width + 'px;');
 +    }
 +
 +    // Set the 'max-height' CSS property of the secondary toolbar.
 +    SecondaryToolbar.setMaxHeight(PDFView.container);
 +  });
 +}, true);
 +
 +window.addEventListener('scalechange', function scalechange(evt) {
 +  document.getElementById('zoomOut').disabled = (evt.scale === MIN_SCALE);
 +  document.getElementById('zoomIn').disabled = (evt.scale === MAX_SCALE);
 +
 +  var customScaleOption = document.getElementById('customScaleOption');
 +  customScaleOption.selected = false;
 +
 +  if (!evt.resetAutoSettings &&
 +      (document.getElementById('pageWidthOption').selected ||
 +       document.getElementById('pageFitOption').selected ||
 +       document.getElementById('pageAutoOption').selected)) {
 +    updateViewarea();
 +    return;
 +  }
 +
 +  var predefinedValueFound = selectScaleOption('' + evt.scale);
 +  if (!predefinedValueFound) {
 +    customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%';
 +    customScaleOption.selected = true;
 +  }
 +  updateViewarea();
 +}, true);
 +
 +window.addEventListener('pagechange', function pagechange(evt) {
 +  var page = evt.pageNumber;
 +  if (PDFView.previousPageNumber !== page) {
 +    document.getElementById('pageNumber').value = page;
 +    var selected = document.querySelector('.thumbnail.selected');
 +    if (selected) {
 +      selected.classList.remove('selected');
 +    }
 +    var thumbnail = document.getElementById('thumbnailContainer' + page);
 +    thumbnail.classList.add('selected');
 +    var visibleThumbs = PDFView.getVisibleThumbs();
 +    var numVisibleThumbs = visibleThumbs.views.length;
 +
 +    // If the thumbnail isn't currently visible, scroll it into view.
 +    if (numVisibleThumbs > 0) {
 +      var first = visibleThumbs.first.id;
 +      // Account for only one thumbnail being visible.
 +      var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first);
 +      if (page <= first || page >= last) {
 +        scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN });
 +      }
 +    }
 +  }
 +  document.getElementById('previous').disabled = (page <= 1);
 +  document.getElementById('next').disabled = (page >= PDFView.pages.length);
 +}, true);
 +
 +function handleMouseWheel(evt) {
 +  var MOUSE_WHEEL_DELTA_FACTOR = 40;
 +  var ticks = (evt.type === 'DOMMouseScroll') ? -evt.detail :
 +              evt.wheelDelta / MOUSE_WHEEL_DELTA_FACTOR;
 +  var direction = (ticks < 0) ? 'zoomOut' : 'zoomIn';
 +
 +  if (evt.ctrlKey) { // Only zoom the pages, not the entire viewer
 +    evt.preventDefault();
 +    PDFView[direction](Math.abs(ticks));
 +  } else if (PresentationMode.active) {
 +    PDFView.mouseScroll(ticks * MOUSE_WHEEL_DELTA_FACTOR);
 +  }
 +}
 +
 +window.addEventListener('DOMMouseScroll', handleMouseWheel);
 +window.addEventListener('mousewheel', handleMouseWheel);
 +
 +window.addEventListener('click', function click(evt) {
 +  if (!PresentationMode.active) {
 +    if (SecondaryToolbar.opened && PDFView.container.contains(evt.target)) {
 +      SecondaryToolbar.close();
 +    }
 +  } else if (evt.button === 0) {
 +    // Necessary since preventDefault() in 'mousedown' won't stop
 +    // the event propagation in all circumstances in presentation mode.
 +    evt.preventDefault();
 +  }
 +}, false);
 +
 +window.addEventListener('keydown', function keydown(evt) {
 +  if (PasswordPrompt.visible) {
 +    return;
 +  }
 +
 +  var handled = false;
 +  var cmd = (evt.ctrlKey ? 1 : 0) |
 +            (evt.altKey ? 2 : 0) |
 +            (evt.shiftKey ? 4 : 0) |
 +            (evt.metaKey ? 8 : 0);
 +
 +  // First, handle the key bindings that are independent whether an input
 +  // control is selected or not.
 +  if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
 +    // either CTRL or META key with optional SHIFT.
 +    switch (evt.keyCode) {
 +      case 70: // f
 +        if (!PDFView.supportsIntegratedFind) {
 +          PDFFindBar.open();
 +          handled = true;
 +        }
 +        break;
 +      case 71: // g
 +        if (!PDFView.supportsIntegratedFind) {
 +          PDFFindBar.dispatchEvent('again', cmd === 5 || cmd === 12);
 +          handled = true;
 +        }
 +        break;
 +      case 61: // FF/Mac '='
 +      case 107: // FF '+' and '='
 +      case 187: // Chrome '+'
 +      case 171: // FF with German keyboard
 +        PDFView.zoomIn();
 +        handled = true;
 +        break;
 +      case 173: // FF/Mac '-'
 +      case 109: // FF '-'
 +      case 189: // Chrome '-'
 +        PDFView.zoomOut();
 +        handled = true;
 +        break;
 +      case 48: // '0'
 +      case 96: // '0' on Numpad of Swedish keyboard
 +        // keeping it unhandled (to restore page zoom to 100%)
 +        setTimeout(function () {
 +          // ... and resetting the scale after browser adjusts its scale
 +          PDFView.setScale(DEFAULT_SCALE, true);
 +        });
 +        handled = false;
 +        break;
 +    }
 +  }
 +
 +  // CTRL or META without shift
 +  if (cmd === 1 || cmd === 8) {
 +    switch (evt.keyCode) {
 +      case 83: // s
 +        PDFView.download();
 +        handled = true;
 +        break;
 +    }
 +  }
 +
 +  // CTRL+ALT or Option+Command
 +  if (cmd === 3 || cmd === 10) {
 +    switch (evt.keyCode) {
 +      case 80: // p
 +        SecondaryToolbar.presentationModeClick();
 +        handled = true;
 +        break;
 +      case 71: // g
 +        // focuses input#pageNumber field
 +        document.getElementById('pageNumber').select();
 +        handled = true;
 +        break;
 +    }
 +  }
 +
 +  if (handled) {
 +    evt.preventDefault();
 +    return;
 +  }
 +
 +  // Some shortcuts should not get handled if a control/input element
 +  // is selected.
 +  var curElement = document.activeElement || document.querySelector(':focus');
 +  var curElementTagName = curElement && curElement.tagName.toUpperCase();
 +  if (curElementTagName === 'INPUT' ||
 +      curElementTagName === 'TEXTAREA' ||
 +      curElementTagName === 'SELECT') {
 +    // Make sure that the secondary toolbar is closed when Escape is pressed.
 +    if (evt.keyCode !== 27) { // 'Esc'
 +      return;
 +    }
 +  }
 +
 +  if (cmd === 0) { // no control key pressed at all.
 +    switch (evt.keyCode) {
 +      case 38: // up arrow
 +      case 33: // pg up
 +      case 8: // backspace
 +        if (!PresentationMode.active &&
 +            PDFView.currentScaleValue !== 'page-fit') {
 +          break;
 +        }
 +        /* in presentation mode */
 +        /* falls through */
 +      case 37: // left arrow
 +        // horizontal scrolling using arrow keys
 +        if (PDFView.isHorizontalScrollbarEnabled) {
 +          break;
 +        }
 +        /* falls through */
 +      case 75: // 'k'
 +      case 80: // 'p'
 +        PDFView.page--;
 +        handled = true;
 +        break;
 +      case 27: // esc key
 +        if (SecondaryToolbar.opened) {
 +          SecondaryToolbar.close();
 +          handled = true;
 +        }
 +        if (!PDFView.supportsIntegratedFind && PDFFindBar.opened) {
 +          PDFFindBar.close();
 +          handled = true;
 +        }
 +        break;
 +      case 40: // down arrow
 +      case 34: // pg down
 +      case 32: // spacebar
 +        if (!PresentationMode.active &&
 +            PDFView.currentScaleValue !== 'page-fit') {
 +          break;
 +        }
 +        /* falls through */
 +      case 39: // right arrow
 +        // horizontal scrolling using arrow keys
 +        if (PDFView.isHorizontalScrollbarEnabled) {
 +          break;
 +        }
 +        /* falls through */
 +      case 74: // 'j'
 +      case 78: // 'n'
 +        PDFView.page++;
 +        handled = true;
 +        break;
 +
 +      case 36: // home
 +        if (PresentationMode.active) {
 +          PDFView.page = 1;
 +          handled = true;
 +        }
 +        break;
 +      case 35: // end
 +        if (PresentationMode.active) {
 +          PDFView.page = PDFView.pdfDocument.numPages;
 +          handled = true;
 +        }
 +        break;
 +
 +      case 72: // 'h'
 +        if (!PresentationMode.active) {
 +          HandTool.toggle();
 +        }
 +        break;
 +      case 82: // 'r'
 +        PDFView.rotatePages(90);
 +        break;
 +    }
 +  }
 +
 +  if (cmd === 4) { // shift-key
 +    switch (evt.keyCode) {
 +      case 32: // spacebar
 +        if (!PresentationMode.active &&
 +            PDFView.currentScaleValue !== 'page-fit') {
 +          break;
 +        }
 +        PDFView.page--;
 +        handled = true;
 +        break;
 +
 +      case 82: // 'r'
 +        PDFView.rotatePages(-90);
 +        break;
 +    }
 +  }
 +
 +  if (!handled && !PresentationMode.active) {
 +    // 33=Page Up  34=Page Down  35=End    36=Home
 +    // 37=Left     38=Up         39=Right  40=Down
 +    if (evt.keyCode >= 33 && evt.keyCode <= 40 &&
 +        !PDFView.container.contains(curElement)) {
 +      // The page container is not focused, but a page navigation key has been
 +      // pressed. Change the focus to the viewer container to make sure that
 +      // navigation by keyboard works as expected.
 +      PDFView.container.focus();
 +    }
 +    // 32=Spacebar
 +    if (evt.keyCode === 32 && curElementTagName !== 'BUTTON') {
 +      if (!PDFView.container.contains(curElement)) {
 +        PDFView.container.focus();
 +      }
 +    }
 +  }
 +
 +  if (cmd === 2) { // alt-key
 +    switch (evt.keyCode) {
 +      case 37: // left arrow
 +        if (PresentationMode.active) {
 +          PDFHistory.back();
 +          handled = true;
 +        }
 +        break;
 +      case 39: // right arrow
 +        if (PresentationMode.active) {
 +          PDFHistory.forward();
 +          handled = true;
 +        }
 +        break;
 +    }
 +  }
 +
 +  if (handled) {
 +    evt.preventDefault();
 +    PDFView.clearMouseScrollState();
 +  }
 +});
 +
 +window.addEventListener('beforeprint', function beforePrint(evt) {
 +  PDFView.beforePrint();
 +});
 +
 +window.addEventListener('afterprint', function afterPrint(evt) {
 +  PDFView.afterPrint();
 +});
 +
 +(function animationStartedClosure() {
 +  // The offsetParent is not set until the pdf.js iframe or object is visible.
 +  // Waiting for first animation.
 +  var requestAnimationFrame = window.requestAnimationFrame ||
 +                              window.mozRequestAnimationFrame ||
 +                              window.webkitRequestAnimationFrame ||
 +                              window.oRequestAnimationFrame ||
 +                              window.msRequestAnimationFrame ||
 +                              function startAtOnce(callback) { callback(); };
 +  PDFView.animationStartedPromise = new Promise(function (resolve) {
 +    requestAnimationFrame(function onAnimationFrame() {
 +      resolve();
 +    });
 +  });
 +})();
 +
 +
diff --cc apps/files_pdfviewer/appinfo/info.xml
index ddfd267,0000000..4764299
mode 100644,000000..100644
--- a/apps/files_pdfviewer/appinfo/info.xml
+++ b/apps/files_pdfviewer/appinfo/info.xml
@@@ -1,11 -1,0 +1,12 @@@
 +<?xml version="1.0"?>
 +<info>
 +    <id>files_pdfviewer</id>
 +    <name>PDF Viewer</name>
 +    <description>Inline PDF viewer (pdfjs-based)</description>
 +    <licence>Apache 2.0</licence>
 +    <author>Thomas Müller (ownCloud), Lukas Reschke (ownCloud), pdf.js: Andreas Gal, Chris G Jones, Shaon Barman, Vivien Nicolas, Justin D'Arcangelo, Yury Delendik, Kalervo Kujala, Adil Allawi, Jakob Miland, Artur Adib, Brendan Dahl, David Quintana, Julian Viereck</author>
 +    <require>4.93</require>
 +    <shipped>true</shipped>
 +    <default_enable/>
++    <ocsid>166049</ocsid>
 +</info>
diff --cc apps/files_pdfviewer/appinfo/version
index bd73f47,0000000..2eb3c4f
mode 100644,000000..100644
--- a/apps/files_pdfviewer/appinfo/version
+++ b/apps/files_pdfviewer/appinfo/version
@@@ -1,1 -1,0 +1,1 @@@
- 0.4
++0.5
diff --cc apps/files_pdfviewer/js/loader.js
index c31de2e,0000000..22dedce
mode 100644,000000..100644
--- a/apps/files_pdfviewer/js/loader.js
+++ b/apps/files_pdfviewer/js/loader.js
@@@ -1,71 -1,0 +1,71 @@@
 +/* globals FileList, FileActions */
 +function hidePDFviewer() {
 +	$('#pdframe, #pdfbar').remove();
 +	if ($('#isPublic').val() && $('#filesApp').val()){
 +		$('#controls').removeClass('hidden');
 +	}
 +	FileList.setViewerMode(false);
 +	// replace the controls with our own
 +	$('#app-content #controls').removeClass('hidden');
 +}
 +
 +function showPDFviewer(dir, filename) {
 +	if(!showPDFviewer.shown) {
 +		var $iframe;
 +		var viewer = OC.linkTo('files_pdfviewer', 'viewer.php')+'?dir='+encodeURIComponent(dir).replace(/%2F/g, '/')+'&file='+encodeURIComponent(filename);
 +		$iframe = $('<iframe id="pdframe" style="width:100%;height:100%;display:block;position:absolute;top:0;" src="'+viewer+'" sandbox="allow-scripts allow-same-origin" /><div id="pdfbar"><a id="close" title="Close">X</a></div>');
 +		if ($('#isPublic').val()) {
 +			// force the preview to adjust its height
 +			$('#preview').append($iframe).css({height: '100%'});
 +			$('body').css({height: '100%'});
 +			$('footer').addClass('hidden');
 +			$('#imgframe').addClass('hidden');
 +			$('.directLink').addClass('hidden');
 +			$('.directDownload').addClass('hidden');
 +			$('#controls').addClass('hidden');
 +		} else {
 +			FileList.setViewerMode(true);
 +			$('#app-content').append($iframe);
 +		}
 +
 +		$("#pageWidthOption").attr("selected","selected");
 +		// replace the controls with our own
 +		$('#app-content #controls').addClass('hidden');
 +		$('#pdfbar').css({position:'absolute',top:'6px',right:'5px'});
 +		// if a filelist is present, the PDF viewer can be closed to go back there
 +		if ($('#fileList').length) {
 +				$('#close').css({display:'block',padding:'0 5px',color:'#BBBBBB','font-weight':'900','font-size':'16px',height:'18px',background:'transparent'}).click(function(){
 +				hidePDFviewer();
 +			});
 +		} else {
 +			$('#close').addClass('hidden');
 +		}
 +	}
 +}
 +showPDFviewer.oldCode='';
 +showPDFviewer.lastTitle='';
 +
 +
 +$(document).ready(function(){
 +	// The PDF viewer doesn't work in Internet Explorer 8 and below
 +	if(!$.browser.msie || ($.browser.msie && $.browser.version >= 9)){
 +		var sharingToken = $('#sharingToken').val();
 +
 +		// Logged-in view
 +		if ($('#filesApp').val() && typeof FileActions !=='undefined'){
 + 			FileActions.register('application/pdf','Edit', OC.PERMISSION_READ, '',function(filename){
 +				if($('#isPublic').val()) {
- 					showPDFviewer('', encodeURIComponent(sharingToken)+"&files="+filename+"&path="+FileList.getCurrentDirectory());
++					showPDFviewer('', encodeURIComponent(sharingToken)+"&files="+encodeURIComponent(filename)+"&path="+encodeURIComponent(FileList.getCurrentDirectory()));
 +				} else {
- 					showPDFviewer(FileList.getCurrentDirectory(), filename);
++					showPDFviewer(encodeURIComponent(FileList.getCurrentDirectory()), encodeURIComponent(filename));
 +				}
 +			});
 +			FileActions.setDefault('application/pdf','Edit');
 +		}
 +
 +		// Public view
 +		if ($('#isPublic').val() && $('#mimetype').val() === 'application/pdf') {
 +			showPDFviewer('', sharingToken);
 +		}
 +	}
 +});
diff --cc apps/files_sharing/js/sharedfilelist.js
index 304f77a,861bbaf..d5c65a6
--- a/apps/files_sharing/js/sharedfilelist.js
+++ b/apps/files_sharing/js/sharedfilelist.js
@@@ -237,6 -233,6 +233,7 @@@
  				.each(function(data) {
  					// convert the recipients map to a flat
  					// array of sorted names
++					data.mountType = 'shared';
  					data.recipients = _.keys(data.recipients);
  					data.recipientsDisplayName = OCA.Sharing.Util.formatRecipients(
  						data.recipients,
diff --cc apps/gallery/appinfo/info.xml
index 55b52f1,0000000..c3ca34a
mode 100644,000000..100644
--- a/apps/gallery/appinfo/info.xml
+++ b/apps/gallery/appinfo/info.xml
@@@ -1,19 -1,0 +1,20 @@@
 +<?xml version="1.0"?> 
 +<info>
 +	<id>gallery</id>
 +	<name>Pictures</name>
 +	<licence>AGPL</licence>
 +	<author>Robin Appelman</author>
 +	<require>4.93</require>
 +	<shipped>true</shipped>
 +	<description>Dedicated pictures application</description>
 +	<standalone/>
 +	<default_enable/>
 +	<types>
 +		<!-- update thumbnails when saving file -->
 +		<filesystem/>
 +	</types>
 +	<public>
 +		<gallery>public.php</gallery>
 +	</public>
++	<ocsid>166056</ocsid>
 +</info>
diff --cc apps/gallery/appinfo/version
index be14282,0000000..7d85683
mode 100644,000000..100644
--- a/apps/gallery/appinfo/version
+++ b/apps/gallery/appinfo/version
@@@ -1,1 -1,0 +1,1 @@@
- 0.5.3
++0.5.4
diff --cc apps/gallery/css/public.css
index 6f4aad0,0000000..e2bc5ef
mode 100644,000000..100644
--- a/apps/gallery/css/public.css
+++ b/apps/gallery/css/public.css
@@@ -1,64 -1,0 +1,60 @@@
 +#gallery {
 +	margin-top: 45px;
 +}
 +
 +#public_upload,
 +#download {
 +	font-weight:700;
 +	margin: 0 0.4em 0 0;
 +	padding: 0 5px;
 +	height: 32px;
 +	float: left;
 +
 +}
 +
 +body {
 +	position: absolute;
 +	height: 100%;
 +	width: 100%;
 +	text-align: center;
- 	background-color: #404040;
 +}
 +
 +/* toggle for opening shared picture view as file list */
 +#openAsFileListButton {
 +	position: absolute;
 +	right: 0;
 +	top: 0;
 +	font-weight: normal;
 +}
 +#openAsFileListButton img {
 +	vertical-align: text-top;
 +	-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
 +	filter: alpha(opacity=50);
 +	opacity: .5;
 +}
 +
 +
 +/* transfer to core after body has the id #body-public / #body-public-dark */
 +
 +footer {
 +	text-align: center;
- 	color: #ccc;
- 	-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)";
- 	filter: alpha(opacity=60);
- 	opacity: .6;
++	color: #777;
 +}
 +footer p.info a {
 +	font-weight: bold;
- 	color: #ccc;
++	color: #777;
 +	padding: 13px;
 +	margin: -13px;
 +}
 +
 +/* Sticky footer */
 +body .wrapper {
 +	min-height: 86%;
 +	margin: 0 auto -50px;
 +}
 +body footer, body .push {
 +	height: 70px;
 +}
 +footer p.info {
 +	padding-top: 30px;
 +}
diff --cc apps/templateeditor/lib/mailtemplate.php
index 2be66a7,0000000..3fcbe85
mode 100644,000000..100644
--- a/apps/templateeditor/lib/mailtemplate.php
+++ b/apps/templateeditor/lib/mailtemplate.php
@@@ -1,155 -1,0 +1,162 @@@
 +<?php
 +
 +/**
 + * ownCloud - Template Editor
 + *
 + * @author Jörn Dreyer
 + * @copyright 2014 Jörn Dreyer <jfd at 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\TemplateEditor;
 +
 +use OCP\Files\NotPermittedException;
 +use OC\AppFramework\Middleware\Security\SecurityException;
 +use OCA\TemplateEditor\Http\MailTemplateResponse;
 +
 +class MailTemplate extends \OC_Template {
 +
 +	private $path;
 +	private $theme;
 +	private $editableThemes;
 +	private $editableTemplates;
 +
 +	public function __construct($theme, $path) {
 +		$this->theme = $theme;
 +		$this->path = $path;
 +
 +		//determine valid theme names
 +		$this->editableThemes = self::getEditableThemes();
 +		//for now hard code the valid mail template paths
 +		$this->editableTemplates = self::getEditableTemplates();
 +	}
 +	
 +	/**
- 	 * 
 +	 * @return \OCA\TemplateEditor\Http\MailTemplateResponse
 +	 * @throws \OC\AppFramework\Middleware\Security\SecurityException
 +	 */
 +	public function getResponse() {
 +		if($this->isEditable()) {
 +			list($app, $filename) = explode('/templates/', $this->path, 2);
 +			$name = substr($filename, 0, -4);
 +			list(, $template) = $this->findTemplate($this->theme, $app, $name, '');
 +			return new MailTemplateResponse($template);
 +		}
 +		throw new SecurityException('Template not editable.', 403);
 +	}
 +
 +	public function renderContent() {
 +		if($this->isEditable()) {
 +			list($app, $filename) = explode('/templates/', $this->path, 2);
 +			$name = substr($filename, 0, -4);
 +			list(, $template) = $this->findTemplate($this->theme, $app, $name, '');
 +			\OC_Response::sendFile($template);
 +		} else {
 +			throw new SecurityException('Template not editable.', 403);
 +		}
 +	}
 +
 +	public function isEditable() {
 +		if (isset($this->editableThemes[$this->theme])
 +			&& isset($this->editableTemplates[$this->path])
 +		) {
 +			return true;
 +		}
 +		return false;
 +	}
 +
 +	public function setContent($data) {
 +		if($this->isEditable()) {
 +			//save default templates in default folder to overwrite core template
 +			$absolutePath = \OC::$SERVERROOT.'/themes/'.$this->theme.'/'.$this->path;
 +			$parent = dirname($absolutePath);
 +			if ( ! is_dir($parent) ) {
 +				if ( ! mkdir(dirname($absolutePath), 0777, true) ){
 +					throw new \Exception('Could not create directory.', 500);
 +				}
 +			}
 +			if ( $this->theme !== 'default' && is_file($absolutePath) ) {
 +				if ( ! copy($absolutePath, $absolutePath.'.bak') ){
 +					throw new \Exception('Could not overwrite template.', 500);
 +				}
 +			}
 +			//overwrite theme templates? use versions?
 +			return file_put_contents($absolutePath, $data);
 +		}
 +		throw new SecurityException('Template not editable.', 403);
 +	}
 +
 +	public function reset(){
 +		if($this->isEditable()) {
 +			$absolutePath = \OC::$SERVERROOT.'/themes/'.$this->theme.'/'.$this->path;
 +			if ($this->theme === 'default') {
 +				//templates can simply be deleted in the themes folder
 +				if (unlink($absolutePath)) {
 +					return true;
 +				}
 +			} else {
 +				//if a bak file exists overwrite the template with it
 +				if (is_file($absolutePath.'.bak')) {
 +					if (rename($absolutePath.'.bak', $absolutePath)) {
 +						return true;
 +					}
 +				}
 +			}
 +			return false;
 +		}
 +		throw new NotPermittedException('Template not editable.', 403);
 +	}
 +
 +	/**
 +	 * @return array with available themes. consists of core and subfolders in the themes folder
 +	 */
 +	public static function getEditableThemes() {
 +		$themes = array(
 +			'default' => true
 +		);
 +		if ($handle = opendir(\OC::$SERVERROOT.'/themes')) {
 +			while (false !== ($entry = readdir($handle))) {
 +				if ($entry != '.' && $entry != '..' && $entry != 'default') {
 +					if (is_dir(\OC::$SERVERROOT.'/themes/'.$entry)) {
 +						$themes[$entry] = true;
 +					}
 +				}
 +			}
 +			closedir($handle);
 +		}
 +		return $themes;
 +	}
 +
 +	/**
 +	 * @return array with keys containing the path and values containing the name of a template
 +	 */
 +	public static function getEditableTemplates() {
 +		$l10n = \OC_L10N::get('templateeditor');
- 		return array(
++		$templates = array(
 +			'core/templates/mail.php' => $l10n->t('Sharing email (http)'),
 +			'core/templates/altmail.php' => $l10n->t('Sharing email'),
 +			'core/lostpassword/templates/email.php' => $l10n->t('Lost password mail'),
 +		);
++
++		if (\OCP\App::isEnabled('activity')) {
++			$tmplPath = \OC_App::getAppPath('activity') . '/templates/email.notification.php';
++			$path = substr($tmplPath, strlen(\OC::$SERVERROOT) + 1);
++			$templates[$path] = $l10n->t('Activity notification mail');
++		}
++
++		return $templates;
 +	}
 +}
diff --cc apps/user_webdavauth/appinfo/info.xml
index 16e6942,4e82b81..4e82b81
mode 100644,100755..100644
--- a/apps/user_webdavauth/appinfo/info.xml
+++ b/apps/user_webdavauth/appinfo/info.xml
diff --cc config/config.sample.php
index e613609,1bfae36..1bfae36
mode 100644,100755..100644
--- a/config/config.sample.php
+++ b/config/config.sample.php
diff --cc core/doc/admin/_sources/installation/installation_windows.txt
index 5806bf6,0000000..078313f
mode 100644,000000..100644
--- a/core/doc/admin/_sources/installation/installation_windows.txt
+++ b/core/doc/admin/_sources/installation/installation_windows.txt
@@@ -1,266 -1,0 +1,271 @@@
 +Windows 7 and Windows Server 2008
 +---------------------------------
 +
 +.. note:: You must move the data directory outside of your public root (See
 +          advanced install settings)
 +
 +This section describes how to install ownCloud on Windows with :abbr:`IIS
 +(Internet Information Services)`.
 +
 +It assumes that you have a vanilla, non-IIS enabled Windows
 +machine – Windows 7 or Server 2008. After enabling IIS, the steps are
 +essentially identical for Windows 7 and Windows Server 2008.
 +
 +For installing ownCloud physical access or a remote desktop connection is
 +required. You should leverage MySQL as the backend database for ownCloud. If you
 +do not want to use MySQL, it is possible to use Postgres or SQLite instead.
 +Microsoft SQL Server is not yet support.
 +
 +Enabling SSL is not yet covered by this section.
 +
 +.. note:: If you make your desktop machine or server available outside of your
 +          LAN, you must maintain it. Monitor the logs, manage the access, apply patches to
 +          avoid compromising the system at large.
 +
 +There are 4 primary steps to the installation, and then a 5th step
 +required for configuring everything to allow files larger than the
 +default 2MB.
 +
 +#. Install IIS with CGI support – enable IIS on your Windows machine.
 +#. Install PHP – Grab, download and install PHP.
 +#. Install MySQL – Setup the MySQL server manager and enable ownCloud to create
 +   an instance.
 +#. Install ownCloud – The whole reason we are here!
 +#. Configure upload sizes and timeouts to enable large file uploads – So that
 +   you can upload larger files.
 +
 +Activate IIS with CGI Support
 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 +
 +Windows 7
 +^^^^^^^^^
 +
 +#. Go to :guilabel:`Start --> Control Panel --> Programs`.
 +#. Under Programs and Features, there is link titled :guilabel:`Turn Windows Features on
 +   and Off`. Click on it.
 +#. There is a box labeled Internet Information Services, expand it.
 +#. Expand World Wide Web Services and all the folders underneath.
 +#. Select the folders as illustrated in the picture below to get your IIS
 +   server up and running.
 +
 +.. figure:: ../images/win7features.jpg
 +    :width: 250px
 +    :align: center
 +    :alt: Windows features required for ownCloud on Windows 7
 +    :figclass: align-center
 +
 +    Windows Features required for ownCloud on Windows 7
 +
 +You do not need an FTP server running, so you should tune
 +that feature off for your server. You definitely need the IIS Management
 +Console, as that is the easiest way to start, stop, restart you server,
 +as well as where you change certificate options and manage items like
 +file upload size. You must check the CGI box under Application
 +Development Features, because CGI is how you enable PHP on IIS.
 +
 +You have to turn off WebDAV publishing or the Windows WebDAV
 +conflicts with the ownCloud WebDAV interface. This might already be
 +turned off for you, just make sure it stays that way. The common HTTP
 +features are the features you would expect from a web server.
 +With the selections on this page, IIS will now serve up a web page for you.
 +
 +Restart IIS by going to the IIS manager (:guilabel:`Start --> IIS Manager`).
 +
 +Select your website, and on the far right side is a section titled
 +:guilabel:`Manage Server`. Make sure that the service is started, or click
 +:guilabel:`Start` to start the services selected. Once this is complete, you
 +should be able to go to a web browser and navigate to http://localhost.
 +
 +This should open the standard IIS 7 splash page, which is just a static image
 +that says your web server is running. Assuming you were able to get the
 +splash page, it is safe to say your web server is now up and running.
 +
++Continue by `installing PHP`_.
++
 +
 +Windows Server 2008
 +^^^^^^^^^^^^^^^^^^^
 +
 +#. Go to :guilabel:`Start --> Control Panel --> Programs`.
 +#. Under Programs and Features, there is link titled
 +   :guilabel:`Turn Windows Features on and Off`. Click on it.
 +#. This will bring up the Server Manager.
 +#. In the server manager, Click on Roles, and then click Add Roles.
 +#. Use the :guilabel:`Add Roles Wizard` to add the web server role.
 +
 +.. figure:: ../images/winserverroles.jpg
 +    :width: 300px
 +    :align: center
 +    :alt: server roles required for ownCloud
 +    :figclass: align-center
 +
 +    Server roles required for ownCloud
 +
 +6. Make sure that, at a minimum, the same boxes are checked in this wizard that
 +   are checked in the Windows 7 Section. For example, make sure that the CGI box
 +   is checked under Application Development Features, and that WebDAV Publishing
 +   is turned off. With Remote Desktop Sharing turned on, the detailed role
 +   service list looks like the figure “Role Services”.
 +7. Restart IIS by going to the IIS manager (:guilabel:`Start --> IIS Manager`).
 +8. Select your website, and on the far right side is a section titled Manage
 +   server. Make sure that the service is started, or click “Start” to start the
 +   services selected.
 +
 +9. Once this is complete, you should be able to go to a web browser and type
 +   `localhost`. This should open the standard IIS 7 splash page, which is just a
-    static image that says your web server is running.Assuming you were able to get
-    the splash page, it is safe to say your web server is now up and running. The
-    next part of this “how to” installs PHP on the server.
++   static image that says your web server is running. Assuming you were able to get
++   the splash page, it is safe to say your web server is now up and running.
++
++Continue by `installing PHP`_.
 +
 +Installing PHP
 +~~~~~~~~~~~~~~
 +
- This part is also straightforward, but it is necessary to remind you that this
- is for IIS only.
- 
- 1. Go to the following link and grab the `PHP installer
-    <http://windows.php.net/download/>`_ for version "VC9 Non Thread Safe" 32 or
-    64 bit based on your system.
++1. Go to the `PHP for Windows`_ download page.
 +
- .. note:: If you are using Apache, make sure you grab VC6 instead, lower on the page.
++.. note:: The instructions below are for IIS only. If using a different server
++          software, make sure to follow the hints on "Which version do I
++          choose" on the left hand side of the page linked above.
 +
- 2. Once through that login, select the location that is closest to you
-    geographically.
- 3. Run that install wizard once it is downloaded. Read the license agreement,
-    agree, select an install directory.
- 4. Then select IIS FastCGI as the install server. 
- 5. Take the default selections for the items to install, and click next.
++2. Download the Installer for PHP 5.3, the "VC9 Non Thread Safe" version,
++   either 32 or 64 bit, depending on your system.
++3. Run the downloaded installation executable.
++4. Read the license agreement, agree, select an install directory.
++5. Then select IIS FastCGI as the install server.
++6. Take the default selections for the items to install, and click next.
 +   Then click `install`.
- 6. After a few minutes, PHP will be installed. On to MySQL.
++7. Once the installer is finished, PHP is installed.
++
++Continue by `installing MySQL`_.
 +
 +Installing MySQL
 +~~~~~~~~~~~~~~~~
 +
 +This part installs MySQL on your Windows machine.
 +
 +#. Point your browser to http://dev.mysql.com/downloads/ and download the latest
 +   community edition for your OS – the 32 or 64 bit version. Please download the
 +   **MSI Installer** as it will make life easier.
 +#. Once downloaded, install MySQL (5.5 at the time of writing). Select the
 +   Typical installation.
 +#. When that finishes, check the box to launch the MySQL Instance Configuration
 +   Wizard and click Finish.
 +#. Select a standard configuration, as this will be the only version of MySQL on
 +   this machine.
 +#. Select to install as a windows service, and Check the Launch the MySQL Server
 +   Automatically button.
 +#. Select the modify security settings box on the next page, and enter a
 +   password you will remember. You will need this password when you configure
 +   ownCloud.
 +#. Uncheck “enable root access from remote machines” for security reasons.
 +#. Click execute, and wait while the instance is created and launched.
 +#. Click Finish when this is all complete.
 +
 +.. You can make some pretty good educated guesses on the type of install needed for ownCloud. %% That's not really useful, clarify!
 +
 +Take particular note of your MySQL password, as the user name **root**
 +and the password you select will be necessary later on in the ownCloud
 +installation. As an aside, this link is an excellent resource for questions on
 +how to configure your MySQL instance, and also to configure PHP to work with
 +MySQL. This, however, is not strictly necessary as much of this is handled when
 +you download ownCloud.
 +
 +More information in this topic can be found in a `tutorial on the IIS web site`_.
 +
 +.. _tutorial on the IIS web site:
 +   http://learn.iis.net/page.aspx/353/install-and-configure-mysql-for-php-applications-on-iis-7-and-above/
 +
 +Installing ownCloud
 +~~~~~~~~~~~~~~~~~~~
 +
 +1. Download the latest version of ownCloud from http://owncloud.org/download.
 +2. It will arrive as a tar.bz2 file, and I recommend something like jZip for a
 +   free utility to unzip it.
 +3. Once you have the ownCloud directory unzipped and saved locally, copy it into
 +   your wwwroot directory (probably **C:\\inetpub\\wwwroot**).
 +
 +.. note:: You cannot install directly into the directory **wwwroot** from jzip,
 +          as only the administrator can unzip into the **wwwroot** directory. If you save
 +          it in a different folder, and then move the files into **wwwroot** in windows
 +          explorer, it works. This will install ownCloud locally in your root web
 +          directory. You can use a subdirectory called owncloud, or whatever you want –
 +          the www root, or something else.
 +
 +4. It is now time to give write access to the ownCloud directory to the ownCloud
 +   server: Navigate your windows explorer over to  **inetpub/wwwroot/owncloud** (or
 +   your installation directory if you selected something different).
 +5. Right click and select properties. Click on the security tab, and click the
 +   button “to change permissions, click edit”.
 +6. Select the “users” user from the list, and check the box “write”.
 +7. Apply these settings and close out.
 +
 +Continue by following the :doc:`installation_wizard`.
 +Select MySQL as the database, and enter your MySQL database user name,
 +password and desired instance name – use the user name and password you setup
 +during MySQL installation, and pick any name for the database instance.
 +
 +Ensure Proper HTTP-Verb handling
 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 +
 +IIS must pass all HTTP and WebDAV verbs to the PHP/CGI handler, and must not try
 +to handle them by itself. If it does, syncing with the Desktop and Mobile
 +Clients will fail. Here is how to ensure your configuration is correct:
 +
 +#. Open IIS Manager7.
 +#. In the `Connections` bar, pick your site below `Sites`, or choose the top
 +   level entry if you want to modify the machine-wide settings.
 +#. Choose the `Handler Mappings` feature click `PHP_via_fastCGI`.
 +#. Choose `Request Restrictions` and find the `Verbs` tab.
 +#. Ensure `All Verbs` is checked.
 +#. Click `OK`.
 +#. Next, choose `Request Filtering` feature from IIS Manager.
 +#. Ensure that all verbs are permitted (or none are forbidden) in the `Verbs`
 +   tab.
 +
 +Also, ensure that you did not enable the WebDAV authoring module, since ownCloud
 +needs to be able to handle WebDAV on the application level.
 +
 +
 +Configuring ownCloud, PHP and IIS for Large File Uploads
 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 +
 +Before going too nuts on ownCloud, it is important to do a couple of
 +configuration changes to make this a useful service for you. You will probably
 +want to increase the **max upload size**, for example. The default upload is
 +set to **2MB**, which is too small for even most MP3 files.
 +
 +To do that, simply go into your **PHP.ini** file, which can be found in your
 +**C:\\Program Files (x86)\\PHP** folder. In here, you will find a **PHP.ini**
 +file. Open this in a text editor, and look for a few key attributes to
 +change:
 +
 ++ **upload_max_filesize** – change this to something good, like 1G, and you
 +  will get to upload much larger files.
 ++ **post_max_size** – also change this size, and make it larger than the max
 +  upload size you chose, like 1G.
 +
 +There are other changes you can make, such as the timeout duration for
 +uploads, but for now you should be all set in the **PHP.ini** file.
 +
 +Now you have to go back to IIS manager and make one last change to enable file
 +uploads on the web server larger than 30MB.
 +
 +1. Go to the start menu, and type **iis manager**.
 +2. Open IIS Manager Select the website you want enable to accept large file
 +   uploads.
 +3. In the main window in the middle double click on the icon **Request
 +   filtering**.
 +4. Once the window is opened you will see a bunch of tabs across the top of the
 +   far right,
 +
 +   Select :guilabel:`Edit Feature Settings` and modify the :guilabel:`Maximum
 +   allowed content length (bytes)`
 +
 +5. In here, you can change this to up to 4.1 GB.
 +
 +.. note:: This entry is in BYTES, not KB.
 +
 +You should now have ownCloud configured and ready for use.
++
++
++.. _PHP For Windows: http://windows.php.net/download/
diff --cc core/doc/admin/installation/installation_windows.html
index fee3edf,0000000..99787ce
mode 100644,000000..100644
--- a/core/doc/admin/installation/installation_windows.html
+++ b/core/doc/admin/installation/installation_windows.html
@@@ -1,419 -1,0 +1,420 @@@
 +
 +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 +  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 +
 +
 +<html xmlns="http://www.w3.org/1999/xhtml">
 +  <head>
 +    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 +    
 +    <title>Windows 7 and Windows Server 2008 — ownCloud Administrators Manual 6.0 documentation</title>
 +    
 +    <link rel="stylesheet" href="../_static/style.css" type="text/css" />
 +    <link rel="stylesheet" href="../_static/pygments.css" type="text/css" />
 +    <link rel="stylesheet" href="../_static/style.css" type="text/css" />
 +    <link rel="stylesheet" href="../_static/bootstrap-sphinx.css" type="text/css" />
 +    
 +    <script type="text/javascript">
 +      var DOCUMENTATION_OPTIONS = {
 +        URL_ROOT:    '../',
 +        VERSION:     '6.0',
 +        COLLAPSE_INDEX: false,
 +        FILE_SUFFIX: '.html',
 +        HAS_SOURCE:  true
 +      };
 +    </script>
 +    <script type="text/javascript" src="../_static/jquery.js"></script>
 +    <script type="text/javascript" src="../_static/underscore.js"></script>
 +    <script type="text/javascript" src="../_static/doctools.js"></script>
 +    <script type="text/javascript" src="../_static/bootstrap.js"></script>
 +    <link rel="top" title="ownCloud Administrators Manual 6.0 documentation" href="../index.html" />
 +    <link rel="up" title="Installation" href="index.html" />
 +    <link rel="next" title="Univention Corporate Server" href="installation_ucs.html" />
 +    <link rel="prev" title="Mac OS X" href="installation_macos.html" />
 +<script type="text/javascript">
 +(function () {
 +  /**
 +   * Patch TOC list.
 +   *
 +   * Will mutate the underlying span to have a correct ul for nav.
 +   *
 +   * @param $span: Span containing nested UL's to mutate.
 +   * @param minLevel: Starting level for nested lists. (1: global, 2: local).
 +   */
 +  var patchToc = function ($ul, minLevel) {
 +    var findA;
 +
 +    // Find all a "internal" tags, traversing recursively.
 +    findA = function ($elem, level) {
 +      var level = level || 0,
 +        $items = $elem.find("> li > a.internal, > ul, > li > ul");
 +
 +      // Iterate everything in order.
 +      $items.each(function (index, item) {
 +        var $item = $(item),
 +          tag = item.tagName.toLowerCase(),
 +          pad = 15 + ((level - minLevel) * 10);
 +
 +        if (tag === 'a' && level >= minLevel) {
 +          // Add to existing padding.
 +          $item.css('padding-left', pad + "px");
 +          console.log(level, $item, 'padding-left', pad + "px");
 +        } else if (tag === 'ul') {
 +          // Recurse.
 +          findA($item, level + 1);
 +        }
 +      });
 +    };
 +
 +    console.log("HERE");
 +    findA($ul);
 +  };
 +
 +  $(document).ready(function () {
 +    // Add styling, structure to TOC's.
 +    $(".dropdown-menu").each(function () {
 +      $(this).find("ul").each(function (index, item){
 +        var $item = $(item);
 +        $item.addClass('unstyled');
 +      });
 +      $(this).find("li").each(function () {
 +        $(this).parent().append(this);
 +      });
 +    });
 +
 +    // Patch in level.
 +    patchToc($("ul.globaltoc"), 2);
 +    patchToc($("ul.localtoc"), 2);
 +
 +    // Enable dropdown.
 +    $('.dropdown-toggle').dropdown();
 +  });
 +}());
 +</script>
 +
 +  </head>
 +  <body>
 +  
 +
 +<div class="container">
 +  <div class="content">
 +    <div class="page-header">
 +      <h1><a href="../contents.html">ownCloud Administrators Manual</a></h1>
 +
 +    </div>
 +    
 +			<div class="row">
 +				<div class="span3">
 +					<div class="sidebar">
 +						<div class="well">
 +							<div class="menu-support-container">
 +								<ul id="menu-support" class="menu">
 +									<ul>
 +										<li><a href="../contents.html">Overview</a></li>
 +									</ul>
 +                  <ul>
 +<li class="toctree-l1"><a class="reference internal" href="../index.html">ownCloud 6.0 Admin Documentation</a></li>
 +</ul>
 +<ul class="current">
 +<li class="toctree-l1 current"><a class="reference internal" href="index.html">Installation</a><ul class="current">
 +<li class="toctree-l2"><a class="reference internal" href="installation_appliance.html">Appliances</a></li>
 +<li class="toctree-l2"><a class="reference internal" href="installation_linux.html">Linux Distributions</a></li>
 +<li class="toctree-l2"><a class="reference internal" href="installation_macos.html">Mac OS X</a></li>
 +<li class="toctree-l2 current"><a class="current reference internal" href="">Windows 7 and Windows Server 2008</a><ul>
 +<li class="toctree-l3"><a class="reference internal" href="#activate-iis-with-cgi-support">Activate IIS with CGI Support</a></li>
 +<li class="toctree-l3"><a class="reference internal" href="#installing-php">Installing PHP</a></li>
 +<li class="toctree-l3"><a class="reference internal" href="#installing-mysql">Installing MySQL</a></li>
 +<li class="toctree-l3"><a class="reference internal" href="#installing-owncloud">Installing ownCloud</a></li>
 +<li class="toctree-l3"><a class="reference internal" href="#ensure-proper-http-verb-handling">Ensure Proper HTTP-Verb handling</a></li>
 +<li class="toctree-l3"><a class="reference internal" href="#configuring-owncloud-php-and-iis-for-large-file-uploads">Configuring ownCloud, PHP and IIS for Large File Uploads</a></li>
 +</ul>
 +</li>
 +<li class="toctree-l2"><a class="reference internal" href="installation_ucs.html">Univention Corporate Server</a></li>
 +<li class="toctree-l2"><a class="reference internal" href="installation_source.html">Manual Installation</a></li>
 +<li class="toctree-l2"><a class="reference internal" href="installation_others.html">Other Installation Methods</a></li>
 +<li class="toctree-l2"><a class="reference internal" href="installation_wizard.html">Installation Wizard</a></li>
 +</ul>
 +</li>
 +<li class="toctree-l1"><a class="reference internal" href="../configuration/index.html">Configuration</a></li>
 +<li class="toctree-l1"><a class="reference internal" href="../apps/index.html">Apps</a></li>
 +<li class="toctree-l1"><a class="reference internal" href="../maintenance/index.html">Maintenance</a></li>
 +<li class="toctree-l1"><a class="reference internal" href="../cron/index.html">Driving Events with Cron</a></li>
 +<li class="toctree-l1"><a class="reference internal" href="../quota/index.html">Quota Calculation</a></li>
 +<li class="toctree-l1"><a class="reference internal" href="../sharing_api/index.html">Sharing API</a></li>
 +<li class="toctree-l1"><a class="reference internal" href="../config/index.html">The Configuration File</a></li>
 +<li class="toctree-l1"><a class="reference internal" href="../issues/index.html">Issues</a></li>
 +</ul>
 +
 +								</ul>
 +							</div>
 +						</div>
 +					</div>
 +				</div>
 +        
 +
 +				<div class="span9">
 +					<div class="page-content">
 +						
 +  <div class="section" id="windows-7-and-windows-server-2008">
 +<h1>Windows 7 and Windows Server 2008<a class="headerlink" href="#windows-7-and-windows-server-2008" title="Permalink to this headline">¶</a></h1>
 +<div class="admonition note">
 +<p class="first admonition-title">Note</p>
 +<p class="last">You must move the data directory outside of your public root (See
 +advanced install settings)</p>
 +</div>
 +<p>This section describes how to install ownCloud on Windows with <abbr title="Internet Information Services">IIS</abbr>.</p>
 +<p>It assumes that you have a vanilla, non-IIS enabled Windows
 +machine – Windows 7 or Server 2008. After enabling IIS, the steps are
 +essentially identical for Windows 7 and Windows Server 2008.</p>
 +<p>For installing ownCloud physical access or a remote desktop connection is
 +required. You should leverage MySQL as the backend database for ownCloud. If you
 +do not want to use MySQL, it is possible to use Postgres or SQLite instead.
 +Microsoft SQL Server is not yet support.</p>
 +<p>Enabling SSL is not yet covered by this section.</p>
 +<div class="admonition note">
 +<p class="first admonition-title">Note</p>
 +<p class="last">If you make your desktop machine or server available outside of your
 +LAN, you must maintain it. Monitor the logs, manage the access, apply patches to
 +avoid compromising the system at large.</p>
 +</div>
 +<p>There are 4 primary steps to the installation, and then a 5th step
 +required for configuring everything to allow files larger than the
 +default 2MB.</p>
 +<ol class="arabic simple">
 +<li>Install IIS with CGI support – enable IIS on your Windows machine.</li>
 +<li>Install PHP – Grab, download and install PHP.</li>
 +<li>Install MySQL – Setup the MySQL server manager and enable ownCloud to create
 +an instance.</li>
 +<li>Install ownCloud – The whole reason we are here!</li>
 +<li>Configure upload sizes and timeouts to enable large file uploads – So that
 +you can upload larger files.</li>
 +</ol>
 +<div class="section" id="activate-iis-with-cgi-support">
 +<h2>Activate IIS with CGI Support<a class="headerlink" href="#activate-iis-with-cgi-support" title="Permalink to this headline">¶</a></h2>
 +<div class="section" id="windows-7">
 +<h3>Windows 7<a class="headerlink" href="#windows-7" title="Permalink to this headline">¶</a></h3>
 +<ol class="arabic simple">
 +<li>Go to <em class="guilabel">Start –> Control Panel –> Programs</em>.</li>
 +<li>Under Programs and Features, there is link titled <em class="guilabel">Turn Windows Features on
 +and Off</em>. Click on it.</li>
 +<li>There is a box labeled Internet Information Services, expand it.</li>
 +<li>Expand World Wide Web Services and all the folders underneath.</li>
 +<li>Select the folders as illustrated in the picture below to get your IIS
 +server up and running.</li>
 +</ol>
 +<div class="align-center figure align-center">
 +<img alt="Windows features required for ownCloud on Windows 7" src="../_images/win7features.jpg" style="width: 250px;" />
 +<p class="caption">Windows Features required for ownCloud on Windows 7</p>
 +</div>
 +<p>You do not need an FTP server running, so you should tune
 +that feature off for your server. You definitely need the IIS Management
 +Console, as that is the easiest way to start, stop, restart you server,
 +as well as where you change certificate options and manage items like
 +file upload size. You must check the CGI box under Application
 +Development Features, because CGI is how you enable PHP on IIS.</p>
 +<p>You have to turn off WebDAV publishing or the Windows WebDAV
 +conflicts with the ownCloud WebDAV interface. This might already be
 +turned off for you, just make sure it stays that way. The common HTTP
 +features are the features you would expect from a web server.
 +With the selections on this page, IIS will now serve up a web page for you.</p>
 +<p>Restart IIS by going to the IIS manager (<em class="guilabel">Start –> IIS Manager</em>).</p>
 +<p>Select your website, and on the far right side is a section titled
 +<em class="guilabel">Manage Server</em>. Make sure that the service is started, or click
 +<em class="guilabel">Start</em> to start the services selected. Once this is complete, you
 +should be able to go to a web browser and navigate to <a class="reference external" href="http://localhost">http://localhost</a>.</p>
 +<p>This should open the standard IIS 7 splash page, which is just a static image
 +that says your web server is running. Assuming you were able to get the
 +splash page, it is safe to say your web server is now up and running.</p>
++<p>Continue by <a class="reference internal" href="#installing-php">installing PHP</a>.</p>
 +</div>
 +<div class="section" id="windows-server-2008">
 +<h3>Windows Server 2008<a class="headerlink" href="#windows-server-2008" title="Permalink to this headline">¶</a></h3>
 +<ol class="arabic simple">
 +<li>Go to <em class="guilabel">Start –> Control Panel –> Programs</em>.</li>
 +<li>Under Programs and Features, there is link titled
 +<em class="guilabel">Turn Windows Features on and Off</em>. Click on it.</li>
 +<li>This will bring up the Server Manager.</li>
 +<li>In the server manager, Click on Roles, and then click Add Roles.</li>
 +<li>Use the <em class="guilabel">Add Roles Wizard</em> to add the web server role.</li>
 +</ol>
 +<div class="align-center figure align-center">
 +<img alt="server roles required for ownCloud" src="../_images/winserverroles.jpg" style="width: 300px;" />
 +<p class="caption">Server roles required for ownCloud</p>
 +</div>
 +<ol class="arabic simple" start="6">
 +<li>Make sure that, at a minimum, the same boxes are checked in this wizard that
 +are checked in the Windows 7 Section. For example, make sure that the CGI box
 +is checked under Application Development Features, and that WebDAV Publishing
 +is turned off. With Remote Desktop Sharing turned on, the detailed role
 +service list looks like the figure “Role Services”.</li>
 +<li>Restart IIS by going to the IIS manager (<em class="guilabel">Start –> IIS Manager</em>).</li>
 +<li>Select your website, and on the far right side is a section titled Manage
 +server. Make sure that the service is started, or click “Start” to start the
 +services selected.</li>
 +<li>Once this is complete, you should be able to go to a web browser and type
 +<cite>localhost</cite>. This should open the standard IIS 7 splash page, which is just a
- static image that says your web server is running.Assuming you were able to get
- the splash page, it is safe to say your web server is now up and running. The
- next part of this “how to” installs PHP on the server.</li>
++static image that says your web server is running. Assuming you were able to get
++the splash page, it is safe to say your web server is now up and running.</li>
 +</ol>
++<p>Continue by <a class="reference internal" href="#installing-php">installing PHP</a>.</p>
 +</div>
 +</div>
 +<div class="section" id="installing-php">
 +<h2>Installing PHP<a class="headerlink" href="#installing-php" title="Permalink to this headline">¶</a></h2>
- <p>This part is also straightforward, but it is necessary to remind you that this
- is for IIS only.</p>
 +<ol class="arabic simple">
- <li>Go to the following link and grab the <a class="reference external" href="http://windows.php.net/download/">PHP installer</a> for version “VC9 Non Thread Safe” 32 or
- 64 bit based on your system.</li>
++<li>Go to the <a class="reference external" href="http://windows.php.net/download/">PHP for Windows</a> download page.</li>
 +</ol>
 +<div class="admonition note">
 +<p class="first admonition-title">Note</p>
- <p class="last">If you are using Apache, make sure you grab VC6 instead, lower on the page.</p>
++<p class="last">The instructions below are for IIS only. If using a different server
++software, make sure to follow the hints on “Which version do I
++choose” on the left hand side of the page linked above.</p>
 +</div>
 +<ol class="arabic simple" start="2">
- <li>Once through that login, select the location that is closest to you
- geographically.</li>
- <li>Run that install wizard once it is downloaded. Read the license agreement,
- agree, select an install directory.</li>
++<li>Download the Installer for PHP 5.3, the “VC9 Non Thread Safe” version,
++either 32 or 64 bit, depending on your system.</li>
++<li>Run the downloaded installation executable.</li>
++<li>Read the license agreement, agree, select an install directory.</li>
 +<li>Then select IIS FastCGI as the install server.</li>
 +<li>Take the default selections for the items to install, and click next.
 +Then click <cite>install</cite>.</li>
- <li>After a few minutes, PHP will be installed. On to MySQL.</li>
++<li>Once the installer is finished, PHP is installed.</li>
 +</ol>
++<p>Continue by <a class="reference internal" href="#installing-mysql">installing MySQL</a>.</p>
 +</div>
 +<div class="section" id="installing-mysql">
 +<h2>Installing MySQL<a class="headerlink" href="#installing-mysql" title="Permalink to this headline">¶</a></h2>
 +<p>This part installs MySQL on your Windows machine.</p>
 +<ol class="arabic simple">
 +<li>Point your browser to <a class="reference external" href="http://dev.mysql.com/downloads/">http://dev.mysql.com/downloads/</a> and download the latest
 +community edition for your OS – the 32 or 64 bit version. Please download the
 +<strong>MSI Installer</strong> as it will make life easier.</li>
 +<li>Once downloaded, install MySQL (5.5 at the time of writing). Select the
 +Typical installation.</li>
 +<li>When that finishes, check the box to launch the MySQL Instance Configuration
 +Wizard and click Finish.</li>
 +<li>Select a standard configuration, as this will be the only version of MySQL on
 +this machine.</li>
 +<li>Select to install as a windows service, and Check the Launch the MySQL Server
 +Automatically button.</li>
 +<li>Select the modify security settings box on the next page, and enter a
 +password you will remember. You will need this password when you configure
 +ownCloud.</li>
 +<li>Uncheck “enable root access from remote machines” for security reasons.</li>
 +<li>Click execute, and wait while the instance is created and launched.</li>
 +<li>Click Finish when this is all complete.</li>
 +</ol>
 +<p>Take particular note of your MySQL password, as the user name <strong>root</strong>
 +and the password you select will be necessary later on in the ownCloud
 +installation. As an aside, this link is an excellent resource for questions on
 +how to configure your MySQL instance, and also to configure PHP to work with
 +MySQL. This, however, is not strictly necessary as much of this is handled when
 +you download ownCloud.</p>
 +<p>More information in this topic can be found in a <a class="reference external" href="http://learn.iis.net/page.aspx/353/install-and-configure-mysql-for-php-applications-on-iis-7-and-above/">tutorial on the IIS web site</a>.</p>
 +</div>
 +<div class="section" id="installing-owncloud">
 +<h2>Installing ownCloud<a class="headerlink" href="#installing-owncloud" title="Permalink to this headline">¶</a></h2>
 +<ol class="arabic simple">
 +<li>Download the latest version of ownCloud from <a class="reference external" href="http://owncloud.org/download">http://owncloud.org/download</a>.</li>
 +<li>It will arrive as a tar.bz2 file, and I recommend something like jZip for a
 +free utility to unzip it.</li>
 +<li>Once you have the ownCloud directory unzipped and saved locally, copy it into
 +your wwwroot directory (probably <strong>C:\inetpub\wwwroot</strong>).</li>
 +</ol>
 +<div class="admonition note">
 +<p class="first admonition-title">Note</p>
 +<p class="last">You cannot install directly into the directory <strong>wwwroot</strong> from jzip,
 +as only the administrator can unzip into the <strong>wwwroot</strong> directory. If you save
 +it in a different folder, and then move the files into <strong>wwwroot</strong> in windows
 +explorer, it works. This will install ownCloud locally in your root web
 +directory. You can use a subdirectory called owncloud, or whatever you want –
 +the www root, or something else.</p>
 +</div>
 +<ol class="arabic simple" start="4">
 +<li>It is now time to give write access to the ownCloud directory to the ownCloud
 +server: Navigate your windows explorer over to  <strong>inetpub/wwwroot/owncloud</strong> (or
 +your installation directory if you selected something different).</li>
 +<li>Right click and select properties. Click on the security tab, and click the
 +button “to change permissions, click edit”.</li>
 +<li>Select the “users” user from the list, and check the box “write”.</li>
 +<li>Apply these settings and close out.</li>
 +</ol>
 +<p>Continue by following the <a class="reference internal" href="installation_wizard.html"><em>Installation Wizard</em></a>.
 +Select MySQL as the database, and enter your MySQL database user name,
 +password and desired instance name – use the user name and password you setup
 +during MySQL installation, and pick any name for the database instance.</p>
 +</div>
 +<div class="section" id="ensure-proper-http-verb-handling">
 +<h2>Ensure Proper HTTP-Verb handling<a class="headerlink" href="#ensure-proper-http-verb-handling" title="Permalink to this headline">¶</a></h2>
 +<p>IIS must pass all HTTP and WebDAV verbs to the PHP/CGI handler, and must not try
 +to handle them by itself. If it does, syncing with the Desktop and Mobile
 +Clients will fail. Here is how to ensure your configuration is correct:</p>
 +<ol class="arabic simple">
 +<li>Open IIS Manager7.</li>
 +<li>In the <cite>Connections</cite> bar, pick your site below <cite>Sites</cite>, or choose the top
 +level entry if you want to modify the machine-wide settings.</li>
 +<li>Choose the <cite>Handler Mappings</cite> feature click <cite>PHP_via_fastCGI</cite>.</li>
 +<li>Choose <cite>Request Restrictions</cite> and find the <cite>Verbs</cite> tab.</li>
 +<li>Ensure <cite>All Verbs</cite> is checked.</li>
 +<li>Click <cite>OK</cite>.</li>
 +<li>Next, choose <cite>Request Filtering</cite> feature from IIS Manager.</li>
 +<li>Ensure that all verbs are permitted (or none are forbidden) in the <cite>Verbs</cite>
 +tab.</li>
 +</ol>
 +<p>Also, ensure that you did not enable the WebDAV authoring module, since ownCloud
 +needs to be able to handle WebDAV on the application level.</p>
 +</div>
 +<div class="section" id="configuring-owncloud-php-and-iis-for-large-file-uploads">
 +<h2>Configuring ownCloud, PHP and IIS for Large File Uploads<a class="headerlink" href="#configuring-owncloud-php-and-iis-for-large-file-uploads" title="Permalink to this headline">¶</a></h2>
 +<p>Before going too nuts on ownCloud, it is important to do a couple of
 +configuration changes to make this a useful service for you. You will probably
 +want to increase the <strong>max upload size</strong>, for example. The default upload is
 +set to <strong>2MB</strong>, which is too small for even most MP3 files.</p>
 +<p>To do that, simply go into your <strong>PHP.ini</strong> file, which can be found in your
 +<strong>C:\Program Files (x86)\PHP</strong> folder. In here, you will find a <strong>PHP.ini</strong>
 +file. Open this in a text editor, and look for a few key attributes to
 +change:</p>
 +<ul class="simple">
 +<li><strong>upload_max_filesize</strong> – change this to something good, like 1G, and you
 +will get to upload much larger files.</li>
 +<li><strong>post_max_size</strong> – also change this size, and make it larger than the max
 +upload size you chose, like 1G.</li>
 +</ul>
 +<p>There are other changes you can make, such as the timeout duration for
 +uploads, but for now you should be all set in the <strong>PHP.ini</strong> file.</p>
 +<p>Now you have to go back to IIS manager and make one last change to enable file
 +uploads on the web server larger than 30MB.</p>
 +<ol class="arabic">
 +<li><p class="first">Go to the start menu, and type <strong>iis manager</strong>.</p>
 +</li>
 +<li><p class="first">Open IIS Manager Select the website you want enable to accept large file
 +uploads.</p>
 +</li>
 +<li><p class="first">In the main window in the middle double click on the icon <strong>Request
 +filtering</strong>.</p>
 +</li>
 +<li><p class="first">Once the window is opened you will see a bunch of tabs across the top of the
 +far right,</p>
 +<p>Select <em class="guilabel">Edit Feature Settings</em> and modify the <em class="guilabel">Maximum
 +allowed content length (bytes)</em></p>
 +</li>
 +<li><p class="first">In here, you can change this to up to 4.1 GB.</p>
 +</li>
 +</ol>
 +<div class="admonition note">
 +<p class="first admonition-title">Note</p>
 +<p class="last">This entry is in BYTES, not KB.</p>
 +</div>
 +<p>You should now have ownCloud configured and ready for use.</p>
 +</div>
 +</div>
 +
 +
 +					</div>
 +				</div>
 +			</div>
 +    
 +  </div>
 +</div>
 +  </body>
 +</html>
diff --cc core/js/snap.js
index 0bbefe4,19942e8..19942e8
mode 100644,100755..100644
--- a/core/js/snap.js
+++ b/core/js/snap.js
diff --cc lib/private/activitymanager.php
index 66aa039,f31b121..f31b121
mode 100644,100755..100644
--- a/lib/private/activitymanager.php
+++ b/lib/private/activitymanager.php
diff --cc lib/private/app.php
index 0ca2ca3,81e74a5..7bf04f1
--- a/lib/private/app.php
+++ b/lib/private/app.php
@@@ -823,13 -831,13 +831,8 @@@ class OC_App 
  			// Remove duplicates
  			foreach ($appList as $app) {
  				foreach ($remoteApps AS $key => $remote) {
--					if (
--						$app['name'] == $remote['name']
--						// To set duplicate detection to use OCS ID instead of string name,
--						// enable this code, remove the line of code above,
--						// and add <ocs_id>[ID]</ocs_id> to info.xml of each 3rd party app:
--						// OR $app['ocs_id'] == $remote['ocs_id']
--					) {
++					if ($app['name'] === $remote['name'] ||
++						$app['ocsid'] ===  $remote['id']) {
  						unset($remoteApps[$key]);
  					}
  				}
diff --cc lib/private/installer.php
index 29470db,29470db..f9d0b36
--- a/lib/private/installer.php
+++ b/lib/private/installer.php
@@@ -377,9 -377,9 +377,8 @@@ class OC_Installer
  			$ocsdata=OC_OCSClient::getApplication($ocsid);
  			$ocsversion= (string) $ocsdata['version'];
  			$currentversion=OC_App::getAppVersion($app);
--			if($ocsversion<>$currentversion) {
++			if (version_compare($ocsversion, $currentversion, '>')) {
  				return($ocsversion);
--
  			}else{
  				return false;
  			}
diff --cc lib/private/request.php
index 619eae3,5fd5b3a..5fd5b3a
mode 100644,100755..100644
--- a/lib/private/request.php
+++ b/lib/private/request.php
diff --cc lib/private/share/share.php
index c06ea72,d356d60..691a205
--- a/lib/private/share/share.php
+++ b/lib/private/share/share.php
@@@ -946,22 -946,22 +946,19 @@@ class Share extends \OC\Share\Constant
  		$query->bindValue(4, $user);
  		$query->bindValue(5, \OCP\Share::SHARE_TYPE_LINK);
  
--		$result = $query->execute();
--
--		if ($result === 1) {
--			\OC_Hook::emit('OCP\Share', 'post_set_expiration_date', array(
--				'itemType' => $itemType,
--				'itemSource' => $itemSource,
--				'date' => $date,
--				'uidOwner' => $user
--			));
--		} else {
--			\OCP\Util::writeLog('sharing', "Couldn't set expire date'", \OCP\Util::ERROR);
--		}
 -
 -		return ($result === 1) ? true : false;
 -	}
 -
++		$query->execute();
 +
- 		return ($result === 1) ? true : false;
- 	}
++		\OC_Hook::emit('OCP\Share', 'post_set_expiration_date', array(
++			'itemType' => $itemType,
++			'itemSource' => $itemSource,
++			'date' => $date,
++			'uidOwner' => $user
++		));
++	
++		return true;
 +
++        }
++        
  	/**
  	 * Checks whether a share has expired, calls unshareItem() if yes.
  	 * @param array $item Share data (usually database row)
diff --cc version.php
index 14ed24f,7f3403b..cf4ca14
--- a/version.php
+++ b/version.php
@@@ -1,6 -1,19 +1,6 @@@
 -<?php
 -
 -// We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades
 -// between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel
 -// when updating major/minor version number.
 -$OC_Version=array(7, 0, 0, 5);
 -
 -// The human readable string
 -$OC_VersionString='7.0 RC 2';
 -
 -// The ownCloud edition
 -$OC_Edition='';
 -
 -// The ownCloud channel
 -$OC_Channel='git';
 -
 -// The build number
 -$OC_Build='';
 -
 +<?php 
- $OC_Version = array(7,0,0,4);
- $OC_VersionString = '7.0 RC 1';
++$OC_Version = array(7,0,0,5);
++$OC_VersionString = '7.0 RC 2';
 +$OC_Edition = '';
 +$OC_Channel = 'testing';
- $OC_Build = '2014-07-04T00:34:56+00:00';
++$OC_Build = '2014-07-14T11:28:12+00:00';

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-owncloud/owncloud.git



More information about the Pkg-owncloud-commits mailing list